由段错误引申出的缓冲区溢出攻击分析

    前段时间在写《段错误造成的常见诡异宕机情况总结(中)》时,分析到 程序中数据写超时有可能写到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

posted on 2012-10-24 16:51 peakflys 阅读(2399) 评论(2)  编辑 收藏 引用 所属分类: C++服务器

评论

# re: 由段错误引申出的缓冲区溢出攻击分析 2012-10-25 10:30 zuhd

楼主这个例子很不错,但是有几点没讲清楚哈,主要是以下两句话
int *ret = (int *)(a + 24);
*ret -= 0x48;
我补充一下,班门弄斧了。
int *ret = (int *)(a + 24);
这句话的意思是取栈中的一个值,这个值其实就是call test之后下一条指令的地址偏移,也就是eip。为什么是24呢,首先char a[4]会被编译器优化扩展成int,这是4*4=16,然后是test内部的push ebp4个字节,然后是push eip的4个字节,一共24个。拿到这个地址后,修改里面的内容即可。为什么是0x48呢,因为这个时候,真正的eip值是0x40085b,减去0x48=0x400818,也就是hack中的cout部分了。
  回复  更多评论   

# re: 由段错误引申出的缓冲区溢出攻击分析 2012-10-25 10:44 peakflys

24那个值看一下汇编 或者 info frame 看一下堆栈帧也可以看出来 但是你计算的不对,上面的编译环境使用的寄存器是rbp、rsp、rip(从上面汇编也可以看出来) 这些都是64位的,24的具体计算过程是这样的:a数据扩展成4个int是16个字节,然后加上test函数调用时压进堆栈的rbp 8个字节,总共是24个字节,这个位置就是调用test函数后的返回地址,修改内容即可,具体可以用gdb调试看一下 @zuhd
  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2012年10月>
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910

导航

统计

公告

人不淡定的时候,就爱表现出来,敲代码如此,偶尔的灵感亦如此……

常用链接

留言簿(4)

随笔分类

随笔档案

文章档案

搜索

最新评论

阅读排行榜

评论排行榜