首页 > 代码库 > how

how

实验环境是在64位linux下使用g++编译器
 
 下面是Mark Gordon的答案
 
 1 The below one works on my system, cant guarantee results though. 2  3     #include <iostream> 4     #include <stdlib.h> 5     int num; 6     void(**rptr)(); 7     void foo() { 8       if(num >= 100) exit(0); 9       std::cout << ++num << std::endl;10       *rptr++ = foo;11     }12     int main() {13       rptr = (void(**)())alloca(sizeof(*rptr) * 200) - 1;14       foo();15       return 0;16     }

 

注意他说在其他人的电脑不一定work,也就是说跟编译器或者机器型号有关

 

下面是他本人对这个答案的解释

技术分享
 
 
Mark Gordon
16 votes
Show
It‘s sort of a variation of the return to libc attack.

The alloca call grows the stack downward and gives me a pointer to the top (lowest address) on the stack.  It‘s right after this where the return address will be pushed on the stack when
I call foo so that‘s where I initialize rptr.  Then in foo I just write over the return address with foo so when foo returns it jumps back to the beginning.  Each time we return we move down the
stack (higher addresses) so I increment rptr.
 
大概说的是:这是一种在linux c函数库下 利用return 的一种变种攻击.
 
默认stack(栈)的生长是向下的(也就是push时RSP地址会减少);alloca 的调用 使得 return 时候使用的返回给RIP的地址 (此时的栈顶地址假设为100减1)赋给 rptr ;当函数进行调用的时候,本来栈顶 放的是call foo这个指令的下一个地址,但是被*rptr ++ = foo();
替换掉了; 返回后将继续执行  foo()的首地址; 每次return 我们将 移出栈,所以我累加 rptr 并赋值;
 
 
 
看到这里,我是很疑惑的,为什么要累加??函数的调用明明有一次push,一次pop(针对RIP指令地址寄存器)
 
是的,正常情况下,foo(); 汇编会执行call foo  ,但是,我们直接将foo()的入口地址赋值给了栈顶,也就是说,每次return,我们将 直接执行foo(),而没有使用call
 
这会造成什么样的后果呢?
首先,我们知道call会将下一个指令地址push到栈,然后函数调用结束前ret,将之前push的地址放到RIP,再pop,结束子函数。
 
现在,RIP放的是foo()入口地址,没有入栈的过程,当第一次foo()调用结束后(正常call),直接执行foo入口指令,此时RSP指向没有调用函数前的位置0x100,没有call使得RSP不能push返回的地址,但是
会执行ret,会出栈,我们都知道在这里默认stack是向下生长,pop会使RSP地址自加,变成0x108(为什么是加8,因为一个指针(64位)8个字节)那么如果我们在ret之前将0x104的内容替换成foo();的进入地址,
不断重复,将会达到不断调用foo()函数的目的;
 
 
但是这种方法的结果是:破坏了stack原来保存的信息,我们知道,stack是局部变量和子函数调用时指针地址存放的地方,这样破坏的数据真是太多了。
 
 
下面是实验结果:
0x40070d: foo入口地址
 
0x400738:将foo入口地址赋值到rptr指向的栈空间
 
0x400788:call 指令
技术分享
 
 
下面是gdb单步调试结果
 
Breakpoint 1, 0x0000000000400745 in main ()(gdb) si0x000000000040074a in main ()(gdb) si0x000000000040074e in main ()(gdb) si0x0000000000400753 in main ()(gdb) si0x0000000000400757 in main ()(gdb) si0x000000000040075a in main ()(gdb) si0x000000000040075f in main ()(gdb) si0x0000000000400764 in main ()(gdb) si0x0000000000400767 in main ()(gdb) si0x000000000040076b in main ()(gdb) si0x000000000040076e in main ()(gdb) si0x0000000000400771 in main ()(gdb) si0x0000000000400775 in main ()(gdb) si0x0000000000400779 in main ()(gdb) info registers rsprsp            0x7fffffffd300    0x7fffffffd300(gdb) si0x000000000040077d in main ()(gdb) info registers rsprsp            0x7fffffffd300    0x7fffffffd300(gdb) si0x0000000000400781 in main ()(gdb) info registers rsprsp            0x7fffffffd300    0x7fffffffd300(gdb) si0x0000000000400788 in main ()(gdb) info registers rsprsp            0x7fffffffd300    0x7fffffffd300(gdb) si0x000000000040070d in foo() ()(gdb) info registers rsprsp            0x7fffffffd2f8    0x7fffffffd2f8(gdb) si0x000000000040070e in foo() ()(gdb) info registers rsprsp            0x7fffffffd2f0    0x7fffffffd2f0(gdb) si0x0000000000400711 in foo() ()(gdb) info registers rsprsp            0x7fffffffd2f0    0x7fffffffd2f0(gdb) si0x0000000000400717 in foo() ()(gdb) info registers rsprsp            0x7fffffffd2f0    0x7fffffffd2f0(gdb) si0x000000000040071a in foo() ()(gdb) info registers rsprsp            0x7fffffffd2f0    0x7fffffffd2f0(gdb) si0x0000000000400726 in foo() ()(gdb) si0x000000000040072d in foo() ()(gdb) si0x0000000000400731 in foo() ()(gdb) si0x0000000000400738 in foo() ()(gdb) si0x000000000040073f in foo() ()(gdb) si0x0000000000400740 in foo() ()(gdb) si0x000000000040070d in foo() ()(gdb) info registers rsprsp            0x7fffffffd300    0x7fffffffd300

注意标注红色的地方RSP的变化

 

最后一条语句

0x000000000040070d in foo() ()

是直接从ret跳过来的,像上面说的一样,没有使用call,最后会导致rsp不断地址不断增长,并不断进入foo()函数



如果上面有什么说得不对的地方,请多多包涵。

how