picoctf_2018_rop chain

分析
- checksec
1 | checksec --file=PicoCTF_2018_rop_chain |
- ida
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
查看vlun函数:
1 | char *vuln() |
gets函数存在溢出。
查看一下字符串,发现有flag.txt,点进去,函数如下:
1 | int __cdecl flag(int a1) |
逻辑就是打开一个stream,如果打开,把stream拷贝给s,并且如果参数a1=-559039827,并且win1和win2为非0,那么就打印s,即flag。so,a1后续可以控制输入,看一下win1和win2如何赋值:
1 | void win_function1() |
1 | int __cdecl win_function2(int a1) |
win1直接赋值1,win2在满足win1非0情况下a1=-1163220307即可,所以我们基本思路就有了:
- 调用win_function1,赋值win1=1
- 调用win_function2,传参-1163220307,赋值win2=1
- 最后调用flag函数,传参-559039827即可
利用
只需要注意32位传参是栈传参即可。
- call win_func1
- win_func2
- flag
- win_func2参数
- flag参数
- win_func2参数
- flag
- win_func2
1 | from pwn import * |
这里存在两个问题:
一、关键地址在ida里面和gdb里面不对,具体来讲就是会发现在ida中,win_func1地址不是0x080485CB,win_func2,flag同理,而且如果在gdb中进行断点,会发现这个地址和在ida里面看到的一样,但是这个地址不是最后代码地址,具体原因不详,目前还不知道,不过最后如何找到调用地址的,是反汇编F5后,再次按F5,会发现地址会有刷新,刷新后的就是代码地址,关于这个问题,后续再分析。
二、反汇编中的参数条件满足的数值,如何转为代码里面写的数值,直接选中数值,然后右键,点击hexadecimal即可。比如-1163220307,经过上述操作转为0xBAAAAAAD,是通过 补码表示(Two’s Complement) 实现的,具体如下:
- 取绝对值:
1163220307
的十六进制是0x45555553
。 - 取反(Invert):
0x45555553
→0xBAAAAAAC
(按位取反)。 - 加 1:
0xBAAAAAAC + 1 = 0xBAAAAAAD
。
这里要区分一下python hex函数,hex()
函数和计算机存储负数的补码方式不同
Python 的 hex()
函数会直接显示 负数的数学十六进制值,而不是其在内存中的补码表示:
1 | hex(-1163220307) |
计算过程:
- 先取绝对值:
1163220307
的十六进制是0x45555553
。 - 然后加上负号:
-0x45555553
Python 不会自动转换成补码形式,因为它的整数是任意精度的,不直接映射到固定位宽(如 32 位或 64 位)的存储方式
用c可以打印内存中负数16进制表达:
1 | int x = -1163220307; // 实际存储为 0xBAAAAAAD(补码) |
python中如下:
1 | def to_32bit_hex(num): |
num & 0xFFFFFFFF
将数值限制在 32 位无符号整数 范围内。- 负数会以补码形式计算,因此
-1163220307
变成0xBAAAAAAD