逆向工程代码审计经验谈:如何快速定位大型固件中的危险函数 在物联网设备、智能路由器以及网络安全网关的固件安全审计中逆向工程师往往面对成百上千个交叉编译的 ELF 二进制文件、数十个共享动态库以及错综复杂的 IPC 通信体系。在时间有限的项目周期内如果逐行在 IDA Pro 或 Ghidra 中单步反编译效率将极为低下。快速定位关键脆弱点依赖于一套从解包、符号恢复、自动化危险 Sink 检索到 Source-to-Sink 污点追溯的成熟方法论。固件解包与运行环境模拟固件审计的第一步是将二进制镜像还原为可分析的根文件系统Rootfs# 1. 递归提取固件镜像 binwalk -Me router_firmware_v2.4.bin # 2. 识别提取出的架构与关键主程序 cd _router_firmware_v2.4.bin.extracted/squashfs-root file bin/busybox sbin/httpd usr/sbin/nvram # 3. 使用 QEMU 用户态配合 chroot 快速启动测试可执行文件 cp $(which qemu-arm-static) . chroot . ./qemu-arm-static ./usr/sbin/nvram get http_port通过环境识别我们能迅速了解目标固件的 CPU 架构如 MIPS 32-bit Big Endian、ARM Cortex-A 或 AArch64以及系统的初始化入口etc/init.d/rcS、etc/services/。基于 IDAPython 的危险函数交叉引用自动化扫描大型固件中最致命的两类高危漏洞是命令注入Command Injection与格式化字符串/栈溢出。我们可以编写 IDAPython 脚本在批量反编译时自动搜寻危险 Sink如system,popen,do_system,sprintf,strcpy,sscanf并提取其调用上下文与交叉引用点# ida_danger_finder.py import ida_nalt import ida_funcs import ida_xref import ida_name import ida_hexrays DANGEROUS_FUNCTIONS [ system, popen, do_system, execve, execl, strcpy, sprintf, vsprintf, strcat, gets ] def scan_dangerous_sinks(): print([*] Starting dangerous sinks automated audit...) for func_name in DANGEROUS_FUNCTIONS: ea ida_name.get_name_ea(ida_nalt.BADADDR, func_name) if ea ida_nalt.BADADDR: continue print(f\n[] Inspecting xrefs to sink: {func_name} (Addr: {hex(ea)})) # 遍历所有指向该危险函数的交叉引用 xref ida_xref.get_first_cref_to(ea) while xref ! ida_nalt.BADADDR: parent_func ida_funcs.get_func(xref) if parent_func: func_start parent_func.start_ea func_symbol ida_funcs.get_func_name(func_start) print(f - Called at {hex(xref)} inside function [{func_symbol}]) # 尝试生成该函数的伪代码进行快速关键词匹配 try: cfunc ida_hexrays.decompile(func_start) if cfunc: # 检查伪代码中是否存在常见的用户输入源变量 (如 nvram_get, websGetVar) str_code str(cfunc) if websGetVar in str_code or nvram_get in str_code: print(f [CRITICAL HIGHLIGHT] Tainted source found in {func_symbol} calling {func_name}!) except Exception: pass xref ida_xref.get_next_cref_to(ea, xref) if __name__ __main__: scan_dangerous_sinks()Source 到 Sink 的污点溯源实战在某知名企业级 AP 固件的httpd守护进程中利用上述方法直接锁定了一个未授权命令注入漏洞// 伪代码逆向还原Web 请求处理函数 sub_0040AB20 int handle_diag_ping(request_t *req) { char ip_buffer[128]; char cmd_buffer[256]; // Source: 从外部 HTTP POST 请求体获取用户输入 char *user_input websGetVar(req, target_ip, ); if (!user_input || strlen(user_input) 0) { return -1; } // 逻辑缺陷只做了简单的长度截断未过滤分号、管道符与反引号 strncpy(ip_buffer, user_input, sizeof(ip_buffer) - 1); ip_buffer[sizeof(ip_buffer) - 1] \0; // Sink: 直接拼接待执行命令并传入 system snprintf(cmd_buffer, sizeof(cmd_buffer), /bin/ping -c 4 %s /tmp/ping.log, ip_buffer); // 触发执行例如 target_ip127.0.0.1;id/tmp/res return system(cmd_buffer); }在该案例中websGetVar作为污点源Source未经白名单校验如inet_pton或严格正则^[0-9\.]$直接流入了格式化字符串snprintf并被systemSink消费。固件审计防守建议与加固清单全面替换非安全 C 标准库函数在固件 SDK 中全面弃用strcpy/sprintf改用带有显式边界检查的snprintf/strlcpy。禁止直接调用system()/popen()执行 Shell 拼接命令改用execve/posix_spawn系列 API直接传递固定参数数组从底层阻断 Shell 元字符; | $ ()解析。固件编译链启用全套缓解机制开启-fstack-protector-all、-pie以及 RELRO消除由于历史编译器遗留带来的低成本溢出利用。