IOT FUZZ学习ing
BooFuzz入门-Vivotek摄像头栈溢出漏洞 BooFuzz介绍 Boofuzz是一个基于生成的黑盒协议模糊测试框架,继承自Sulley,基于python语言。
BooFuzz使用 官方文档 ,有User Guide和API文档
环境配置 官方建议在python虚拟环境中使用boofuzz,同时python版本不能太低,具体可以看文档中的changelog
安装虚拟环境及boofuzz
1 sudo apt-get install python3-venv build-essential
在当前目录创建虚拟环境env,env是可以自定义的环境名称
激活虚拟环境,激活后终端前面会标识(env)
1 source ./env/bin/activate
安装最新版的pip和setuptools再安装boofuzz包
1 2 pip install -U pip setuptools pip install boofuzz
fuzz脚本编写 该文章 实现了一个boofuzz脚本生成器,可以导入请求的文本文件生成对应的boofuzz脚本
下面是boofuzz的基本步骤以及一些不充分的解释,要熟练掌握boofuzz还得是结合api文档读源码,下面贴一张网上找的源码结构图
创建Session会话 1 2 3 4 session = Session( target=Target( connection=SocketConnection("127.0.0.1" , 8080 , proto='tcp' )))
构造请求 静态协议定义api
以下面代码为例,定义了部分http请求
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 s_initialize(name="Request" ) with s_block("Request-Line" ): s_static("POST" , name="Method" ) s_delim(" " , name='space-1' ) s_string("/fromLogin" , name='Request-URI' ) s_delim(" " , name='space-2' ) s_static('HTTP/1.1' , name='HTTP-Version' ) s_static("\r\n" ) s_static("Host" , name="Host" ) s_static(": " ) s_static("192.168.10.1" , name="ip" ) s_static("\r\n" ) s_static('Content-Length' ) s_static(': ' ) s_size('data' , output_format='ascii' , fuzzable=True ) s_static('\r\n' ) with s_block('data' ): s_static('login_name=&curTime=1581845487827&setLang=&setNoAutoLang=&login_n=admin&login_pass=' ) s_string('123456' , max_len=1024 ) s_static('&languageSel=1' )
s_initialize
s_static生成Static primitives,是固定的,在模糊测试时不会突变
s_delim,s_string是动态的,不指定fuzzable = false的情况下在模糊测试时会发生突变
s_size与第一个name参数对应的block块绑定,以ascii的形式返回绑定的块的大小
最终定义的消息如下,对/fromLogin以及前后的空格,login_pass=后的123456字段进行了fuzz
1 2 3 4 5 POST /fromLogin HTTP/1.1 Host : 192.168.10.1Content-Length : 103login_name = & curTime = 1581845487827 & setLang = & setNoAutoLang = &login_n =admin&login_pass =123456 & languageSel = 1
链接请求,构建状态图 初始的Session中是没有请求的,需要手动将之前定义的请求按照一定的先后顺序链接起来,如果只有单个请求需要fuzz,那么直接添加即可。同时也可以在两个请求之间注册回调函数,这个回调函数会在状态转移时被调用,可以来收集返回包信息等。
例如以下代码
1 2 3 4 session.connect(s_get('login' )) session.connect(s_get('login' ), s_get('setsysemailsettings' ), callback=add_auth_callback) session.connect(s_get('login' ),s_get('setsyslogsettings' ), callback=add_auth_callback) session.connect(s_get('login' ),s_get('setschedulesettings' ), callback=add_auth_callback)
对应的协议树如下
在发送完login请求完成登陆后,才能发送后面的几个请求,同时在login后添加了回调函数
开始fuzz 传入一个请求的name的话就会只fuzz这个请求,不传默认按建立的状态图去遍历着fuzz,fuzz时可以访问 http://127.0.0.1:26000/查看统计数据
添加对目标设备的监控和重启机制等 监视器用来检测目标是否崩溃,是否正常运行,收集目标程序返回包的信息,启动、停止和重新启动目标等。
Boofuzz 提供三种监视器:ProcessMonitor、NetworkMonitor、CallbackMonitor,它们都基于BaseMonitor,都具备BaseMonitor的基础功能。
一般可以在session中添加回调函数,回调函数有下面三种
pre_send_callbacks – 在每个模糊请求之前都会调用已注册的方法。 默认值:None。
post_test_case_callbacks – 在每个模糊测试用例之后都会调用已注册的方法。 默认值:None。
post_start_target_callbacks – 方法将在目标启动或重新启动后调用, 比如说,通过进程监视器。
BooFuzz练习-Vivotek摄像头固件栈溢出漏洞 参考
解包固件 1 binwalk -Me CC8160-VVTK-0100d.flash.pkg
文件结构解析 使用tree命令查看文件结构
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 _CC8160-VVTK-0100d.flash.pkg.extracted$ tree -d -L 5 . ├── _31.extracted │ ├── boot │ ├── defconf │ │ └── _CC8160.tar.bz2.extracted │ │ └── _0.extracted │ │ └── etc │ ├── dtb │ ├── _kernel.img.extracted │ │ └── _5FB4.extracted │ │ ├── cpio-root │ │ │ ├── dev │ │ │ └── root │ │ ├── dev │ │ └── root │ ├── nandspl │ └── _rootfs.img.extracted │ └── squashfs-root │ ├── bin │ ├── dev │ │ ├── misc │ │ ├── net │ │ ├── pts │ │ ├── shm │ │ └── snd │ ├── drivers │ ├── etc │ │ ├── default │ │ ├── init.d │ │ └── udhcpc │ ├── home │ ├── lib │ │ └── iptables │ ├── mnt │ │ ├── auto │ │ ├── flash │ │ ├── flash2 │ │ └── ramdisk │ ├── proc │ ├── root │ ├── sbin │ ├── sys │ ├── tmpfs │ │ ├── CF │ │ ├── samba │ │ ├── tmp │ │ └── var │ ├── usr │ │ ├── bin │ │ ├── lib │ │ ├── local │ │ ├── sbin │ │ └── share │ └── www │ ├── colorpicker │ ├── css │ ├── include │ ├── pic │ └── setup ├── _BD6E8E.extracted ├── _D48788.extracted └── _DC7909.extracted └── setup └── vadppkg ├── _genetec-vadp-1-0-2-7.tar.gz.extracted │ └── _0.extracted └── _stratocast-1-1-1-3.tar.gz.extracted └── _0.extracted
大部分内容都在_31.extracted文件夹中,其中boot 文件夹中是u-boot的主程序;dtb 文件夹中是内核的设备树文件;kernel.img.extracted 是提取后的内核镜像文件,没东西;nandspl 中没有提取出文件,但是nandspl本身是针对nandflash的二级程序加载器(secondary program loader),也是u-boot的一部分,在u-boot主程序前执行,进行最基本的硬件初始化,比如关闭中断,内存初始化,设置堆栈等。这几个文件可以总结为以下流程:
ROM code -> u-boot spl -> uboot主程序 ->加载kernel。
关于u-boot spl,具体可以看这里
然后就是最重要的rootfs.img.extracted ,这是提取后的文件系统。
提取的文件系统中etc目录下很多文件都是软链接,链接到了defconf目录下的etc文件夹中,可以用’’ls -l’’命令查看.
寻找服务程序 linux启动流程 ,大致为
init程序(linuxrc) -> busybox -> /etc/inittab -> /etc/init.d/rcS
所以进入文件系统找到”**/etc/init.d/rcS**”开机自启文件,在下面代码中用run-parts命令遍历执行了”/etc/rcS.d “目录下的所有可执行脚本。
这里注释标注的是Start daemons,即启动守护进程。守护进程是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或循环等待处理某些事件的发生。很多服务进程如httpd都是守护进程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # Apply patch brfore rcS.d echo "check apply_patch_before" if [ -f /mnt/flash2/patch/apply_patch_before ]; then chmod 777 /mnt/flash2/patch/apply_patch_before /mnt/flash2/patch/apply_patch_before fi# Start daemons echo "run-parts -a start /etc/rcS.d" run-parts -a start /etc/rcS.d# Apply patch after rcS.d echo "check apply_patch_after" if [ -f /mnt/flash2/patch/apply_patch_after ]; then chmod 777 /mnt/flash2/patch/apply_patch_after /mnt/flash2/patch/apply_patch_after fi logger "init-rcS end" exit 0
rcS.d在/etc中是个软链接,指向”../mnt/flash/etc/rcS.d”,在提取出的文件中位于”31.extracted/defconf/_CC8160.tar.bz2.extracted/_0.extracted/etc/“,该目录下的文件都被软链接到了/etc。
进入rcS.d,里面都是开机自启服务进程的执行脚本,发现执行脚本均为软连接文件,连接至”etc/init.d/“下的对应启动脚本文件,在启动脚本文件中可以找到可执行二进制程序的真正位置。
以httpd服务为例,启动过程如下:
/etc/init.d/rcS -> /etc/rcS.d/S31httpd -> /etc/init.d/httpd -> /usr/sbin/httpd
寻找漏洞点 结合对httpd程序的逆向,可以判断这里用的是boa二次开发的web服务器
ida中查找字符串Content-Length即可,这里会将Content-Length字段后从:到\n全都复制到栈上的dest变量,没有限制长度,导致了栈溢出。
固件仿真 查看漏洞程序信息
1 2 3 4 5 6 7 8 $ file httpd httpd: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped$ checksec httpd Arch: arm-32-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8000)
httpd的配置文件是/etc/conf.d/boa/boa.conf,conf.d是软链接的,这里失效了,所以要替换软链接,将31.extracted/defconf/_CC8160.tar.bz2.extracted/_0.extracted/etc中的文件复制到31.extracted/_rootfs.img.extracted/squashfs-root/etc,选择全部替换
然后用户态qemu模拟运行httpd程序,尝试运行报错,无/dev/null
1 2 3 $ cp /usr/bin/qemu-arm-static Path_to/squashfs-root$ sudo chroot ./squashfs-root/ /qemu-arm-static /usr/sbin/httpd src/boa.c:284 (main) - can't open /dev/null: No such file or directory
解决
继续运行,虽然输出Success,但是并没有对应的进程
1 2 3 4 5 $ sudo chroot ./squashfs-root/ /qemu-arm-static /usr/sbin/httpd -c /etc/conf.d/boa -d sendto() error 20 [debug]add server push uri 3 video3.mjpg [debug]add server push uri 4 video4.mjpg gethostbyname:: Success
用ida打开httpd,查看gethostbyname字符串交叉索引定位到函数。先获取hostname,再解析成ip。先./etc/hosts解析再dns解析,所以这里改hosts文件中127.0.0.1对应的hostname
先hostname获取主机名,再把hosts文件中的Network-Camera改成主机名
在/etc/init.d/httpd中找到httpd的启动参数,运行服务
1 2 3 4 5 6 7 8 9 $ sudo chroot ./ /qemu-arm-static /usr/sbin/httpd -c /etc/conf.d/boa -d sendto() error 20 [debug]add server push uri 3 video3.mjpg [debug]add server push uri 4 video4.mjpg [debug] after ini, server_push_uri[0] is /video3.mjpg [debug] after ini, server_push_uri[1] is /video4.mjpg fopen pid file: Not a directory [01/Nov/2023:04:13:00 +0000] boa: server version 1.32.1.10(Boa/0.94.14rc21) [01/Nov/2023:04:13:00 +0000] boa: starting server pid=4978, port 80
访问127.0.0.1发现服务正常,运行成功
进行fuzz 脚本来源
只对Content-Length字段进行fuzz
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 from boofuzz import * IP = "127.0.0.1" PORT = 80 def check_response (target,fuzz_data_logger,session,*args,**kwargs ): fuzz_data_logger.log_info("Checking for response ..." ) try : response = target.recv(512 ) except : fuzz_data_logger.log_fail("Unable to connect ..." ) return if not response: fuzz_data_logger.log_fail("Empty response ..." ) target.close() return fuzz_data_logger.log_info("Start checking ...\n" +response.decode()) target.close() return def main (): session = Session( target=Target(connection=SocketConnection(IP,PORT,proto="tcp" ), ), post_test_case_callbacks=[check_response], ) s_initialize(name="FUZZ" ) with s_block("Request-Line" ): s_group("Method" ,["POST" ]) s_delim(" " ,fuzzable=False ) s_string("/cgi-bin/admin/upgrade.cgi " ,fuzzable=False ,name="URI" ) s_static("HTTP/1.1" ,name="Version" ) s_static("\r\n" ,name="CRLF" ) s_static("Host" ) s_delim(": " ,fuzzable=False ) s_string("127.0.0.1" ,fuzzable=False ,name="IP" ) s_static("\r\n" ) s_static("Connection" ) s_delim(": " ,fuzzable=False ) s_string("Close" ,fuzzable=False ,name="Active" ) s_static("\r\n" ) s_static("Content-Length" ) s_delim(": " , fuzzable=False ) s_string("65" , fuzzable=True ) s_static("\r\n" ) s_static("\r\n" ) session.connect(s_get("FUZZ" )) session.fuzz()if __name__=="__main__" : main()
可以看到在Test Case 1中没有返回,程序已经崩溃
脚本还在运行时,可以访问localhost:26000查看本次fuzz相关信息(触发crash的case,case详细信息等)
reference boofuzz github
boofuzz手册
[csdn] BooFuzz协议漏洞挖掘入门教程与使用心得
[csdn] IoT 设备网络协议模糊测试工具boofuzz实战
[知乎] BooFuzz实战 Vivotek的Fuzzing 栈溢出
常见嵌入式Web服务器CGI处理功能简要分析 (aslr.io)
IOT固件安全all in one - 固件仿真