前段时间在写《段错误造成的常见诡异宕机情况总结(中)》时,分析到 程序中数据写超时有可能写到this指针所在的地址里面,导致最终诡异的宕机。其实网络攻防里常用的缓冲区溢出攻击也是这个道理,除了使用户程序甚至计算机挂掉外,还有可能执行攻击者想执行的任何程序,这篇文章主要详细剖析一下第二种攻击的方法以及现在Linux(包括各种修改版本,例如Android)、Windows下常使用的防范措施。
话不多说,先贴一则小例子:
1 **
2 *\author peakflys
3 *\email peakflys@gmail.com
4 *\brief Buffer overflow attack
5 */
6 #include <iostream>
7 using namespace std;
8
9 void hack()
10 {
11 cout<<"hacked"<<endl;
12 }
13
14 void test()
15 {
16 char a[4];
17 int *ret = (int *)(a + 24);
18 *ret -= 0x48;
19 }
20
21 int main()
22 {
23 test();
24 return 0;
25 }
运行结果:
hacked
整个过程没有显示调用hack函数,而最终hack函数却得以运行。下面给出三个函数的汇编代码:
0000000000400814 <_Z4hackv>:
void hack()
{
400814: 55 push %rbp
400815: 48 89 e5 mov %rsp,%rbp
cout<<"hacked"<<endl;
400818: be b8 09 40 00 mov $0x4009b8,%esi
40081d: bf 80 0d 60 00 mov $0x600d80,%edi
400822: e8 c1 fe ff ff callq 4006e8 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
400827: be 08 07 40 00 mov $0x400708,%esi
40082c: 48 89 c7 mov %rax,%rdi
40082f: e8 c4 fe ff ff callq 4006f8 <_ZNSolsEPFRSoS_E@plt>
400834: c9 leaveq
400835: c3 retq
}
0000000000400836 <_Z4testv>:
void test()
{
400836: 55 push %rbp
400837: 48 89 e5 mov %rsp,%rbp
char a[4];
int *ret = (int *)(a + 24);
40083a: 48 8d 45 f0 lea -0x10(%rbp),%rax
40083e: 48 83 c0 18 add $0x18,%rax
400842: 48 89 45 f8 mov %rax,-0x8(%rbp)
*ret -= 0x48;
400846: 48 8b 45 f8 mov -0x8(%rbp),%rax
40084a: 8b 00 mov (%rax),%eax
40084c: 8d 50 b8 lea -0x48(%rax),%edx
40084f: 48 8b 45 f8 mov -0x8(%rbp),%rax
400853: 89 10 mov %edx,(%rax)
400855: c9 leaveq
400856: c3 retq
}
0000000000400857 <main>:
int main()
{
400857: 55 push %rbp
400858: 48 89 e5 mov %rsp,%rbp
test();
40085b: e8 d6 ff ff ff callq 400836 <_Z4testv>
return 0;
400860: b8 00 00 00 00 mov $0x0,%eax
400865: c9 leaveq
400866: c3 retq
}
在调用test函数后,gdb打印结果:
(gdb) p $rsp
$1 = (void *) 0x7fffffffe2a0
(gdb) p $rbp
$2 = (void *) 0x7fffffffe2a0
(gdb) p /x *(0x7fffffffe2a8)
$3 = 0x400860
在test函数堆栈前面(如上,堆栈地址:
0x7fffffffe2a8 )有函数返回的地址(即
0x400860 )。test函数调用时的内存模型大致为:
估计这时候大家都已经知道整个实现的详细过程了,test函数中ret指针指向test函数返回地址0x7fffffffe2a8(注:这个很容易分析得到),然后通过函数偏移值0x48(注:这个根据特定平台和特定编译器来分析得出)来重定向返回值地址,即定向到hack函数,这段重定向代码 也就是常说的shellcode。
缓冲区溢出攻击在平时的网络攻击中能占到50%的比例,上面test函数可以轻易的让计算机崩溃,也可以轻易的执行任何病毒或者木马程序。
现在来分析一下防范措施。首先当然是作为程序员的我们需要注意的:写数据时,特别警惕数据边界,严防存在数据写溢出的情况,类似于strcpy等函数不要使用或者小心使用。其次当然是通过其他方式的防范,上面我也提到了,shellcode中有两个值是需要计算的,第一个是函数返回值地址,这个比较容易分析出来,因为编译器是有固定的入栈压栈规则的;第二个是两个函数之间的偏移地址,函数地址在编译阶段就已经在代码段固定下来了,偏移 只需要根据反汇编出的片段地址猜解出来(如果能完全反汇编出来,就不用猜了……)。这两个值只要增加任何一个的计算难度,都可以有效减少这种漏洞的攻击。目前Linux
(包括各种修改版本,例如Android) 、FreeBSD、Windows 等主流操作系统都已采用 ASLR(Address space layout randomization)技术(iOS系统自iOS 4.3以后也支持了ASLR技术 ),这种技术就是通过对堆、栈、共享库映射等线性区布局的随机化来达到增加猜解出函数偏移的目的。当然这种技术仅仅是减少而非杜绝缓冲区溢出攻击。
世界上没有完美的程序,只有暂时想不到的bug by peakflys