不安全的代码,产生的bug就不是bug那么简单了,而是exploit。这个buffer overflow attack (缓冲区溢出攻击)在安全界的知名度是不言而喻的,基本上属于最经典的漏洞之一。各种利用工具和shellcode何其多哉!
抱着亲身了解下缓冲区溢出攻击的过程,顺便体验下linux下强大的gdb的心态,就拿gets()写了个小程序练下手。
//attack.c
#include<stdio.h>
int main()
{
char s[8];
gets(s);
printf(s);
return 0;
}
生成的attack可执行文件用objdump反汇编:
080483f4 <main>:
80483f4: 55 push %ebp
80483f5: 89 e5 mov %esp,%ebp
80483f7: 83 e4 f0 and $0xfffffff0,%esp
80483fa: 83 ec 20 sub $0x20,%esp
80483fd: 8d 44 24 18 lea 0x18(%esp),%eax
8048401: 89 04 24 mov %eax,(%esp)
8048404: e8 fb fe ff ff call 8048304 <gets@plt>
8048409: 8d 44 24 18 lea 0x18(%esp),%eax
804840d: 89 04 24 mov %eax,(%esp)
8048410: e8 0f ff ff ff call 8048324 <printf@plt>
8048415: b8 00 00 00 00 mov $0x0,%eax
804841a: c9 leave
804841b: c3 ret
804841c: 90 nop
804841d: 90 nop
804841e: 90 nop
804841f: 90 nop
然后gdb attack
先尝试一下程序运行是否正确:
(gdb) run
Starting program: /home/shanks/Documents/attack
hello
hello
输入小于8字节的时候,程序运行是OK的~
开始debug,先设置断点:
(gdb) break *0x80483f4 //设置在main程序入口处
Breakpoint 1 at 0x80483f4
(gdb) run
Starting program: /home/shanks/Documents/attack
Breakpoint 1, 0x080483f4 in main ()
然后查看寄存器和栈帧情况:
可以得到的信息有,esp为0xbffff38c,ebp为0,eip指向main
注意下面的栈帧信息,进入main函数前保存的返值为 0x4f3683f3(这个到后面会被溢出修改)
单步执行,例行的ebp入栈,新ebp(0xbffff388)指向旧ebp(0)
查看属实:
$3 = (void *) 0xbffff388
(gdb) print *(int*)($ebp)
$5 = 0
继续单步
and一句 最低位置0,其实我也不太清楚具体作用,猜测是alignment的原因,但也不排除是编译器为了阻碍溢出攻击故意让esp有不确定的偏移量?(这个可能性略低 = =)
anyway,现在esp为:
$6 = (void *) 0xbffff380
sub栈延伸后为0xbffff360
继续,将esp+18的地址放到eax中
$10 = 0xbffff378
这个其实就是栈中为char s[8]开辟的内存空间,其具体地址为37f - 378 (前面那段一样的0xbffff就省略了)刚好8字节。同时注意存放顺序,也就是,378对应的是s[0](首地址),379对应s[1],当然,以一般的内存视图看起来,这个是反序的
继续,将eax,也就是char数组首地址,放入esp所指位置。这里其实效果和push eax差不多,都是将gets的参数入栈,之后调用gets(),函数细节就略过了,ret之后,我们输入的字符串就会出现在以378开始的内存单元中。即char数组s[8]。但问题是,如果我们输入的字符串不止8字节呢??
Breakpoint 2 at 0x8048409
(gdb) continue
Continuing.
00001111222233334444 //输入20字节,加上后面的'\0'共21字节
然后可以预计,从378-37b 也就是S[0] 到 s[3] 都是0。然后37c-37f都是1,依次类推。则388-38b都是4,转换成十六进制ASCII码也就是34。问题来了!前面说道388处存放的是旧ebp(也就是0),这里将会被覆盖。也就是原ebp会丢失,破坏了栈的结构,而且这里不止ebp被破坏,返值eip的最低位也被换成0(也就是字符串结束符'\0')
看看实际结果:
(gdb) print /x ($ebp)
$16 = 0xbffff388
(gdb) print /x *(int*) ($ebp)
$18 = 0x34343434
(gdb) print /x *(int*)($ebp+4)
$24 = 0x4f368300
(gdb) continue
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x4f34c8f8 in ?? ()
完全符合预测
可以看到,缓冲区溢出攻击是需要高精确度计算的攻击手段。实际应用中,必然没有俺这么容易就能利用的exploit。但是小小测试一下,对理解攻击手段,堆栈操作,构造特殊代码,编写shellcode还是有点帮助的~
没有评论:
发表评论