配套需求文档:
firmware-analysis-requirements.md最近更新:2026-08-25
-
2026-08-25 — v4.1(
ifda.__version__2.6.0 → 2.7.0):根目录占比图、对比扫描函数级 diff、扫描进度准确性。仪表盘新增根文件系统目录占比图。 按顶层目录(
/usr、/etc、/root等)展示磁盘占用 分布,标注版甜甜圈图:扇形内标目录名,扇形外用引线标百分比+格式化后的存储大小(占比过小 的扇区——真实固件根目录常见十几个顶层目录,大部分很小——只上色不加外部文字标注,避免堆成 一团反而看不清;旁边的 HTML 图例本来就完整列出了每个目录的精确数值,不算信息损失)。渲染 优先走 CUDA 逐像素光栅化(每个 GPU 线程从极坐标独立算出自己那个像素属于哪个扇形),没有 可用设备时退回 Pillow 做 CPU 矢量渲染;文字标注不管走哪条渲染路径,统一在 CPU 侧用 Pillow 叠加,因为字体光栅化本来就不适合塞进 CUDA kernel。用真实固件在真实 CUDA 设备上跑通,CPU 兜底路径也验证过。对比扫描新增函数级 diff。 在原有的基于 MD5 的文件级 diff 之外,反汇编阶段现在会给每个 函数算一个内容指纹(函数体内所有指令的助记符直方图哈希,复用已有的、本来就要抽取调用边的 那一遍指令遍历,不额外增加反汇编开销),前端对两次扫描里路径相同的二进制按函数名匹配做 diff:新增/删除/修改/未变化,可视化复用同一套 CUDA/CPU 渲染管线(饼图渲染器已泛化为接受任意 命名数值分片,不再局限于目录字节数)。这是按函数名匹配,不是 BinDiff 那种结构匹配——一个 只是被改名或重新去符号的函数,会显示为一增一减,不是"已修改"。
扫描进度准确性修复。
--decompile模式下 Ghidra 反编译阶段的进度百分比之前会明显 倒退(该阶段开始前的一个固定收尾阶段已经报了 99%,decompile 自己的区间却是从更低的数字 起步),然后在剩余时间里几乎不动——因为 decompile 可能多达数百个目标,却被压缩进固定 3 个 百分点的显示区间,和目标数量无关。现在整条阶段百分比曲线端到端单调递增,decompile 分到 的区间也按比例放宽了不少。有自然子任务计数的阶段(disassemble、decompile)现在还会在总 百分比之外额外带一个结构化的done/total字段,前端为其单独渲染一条子进度条。 disassemble 阶段(占扫描总耗时的大头)新增实时"已处理二进制按架构分布"图,同时展示纯前端 的动画 SVG 甜甜圈(数据一变就立即平滑动画,不用等网络往返)和仪表盘同款的 CUDA/CPU 服务端 渲染 PNG,两者并排对照。扫描页面还加了一条纯装饰性的插画(晶圆/芯片/硬盘/通用机器人与 机器狗图标,放大镜来回扫描),不涉及真实进度数据,prefers-reduced-motion时自动关闭动画。重新扫描。 每个已完成/失败的任务行新增"重新扫描"按钮,提交时带
force标志跳过去重 缓存——直接重新提交同一路径的话会被缓存命中、原样复制回旧报告,等于白跑。ifda.__version__同步从 2.6.0 升到 2.7.0,对应上面这些报告结构的实质变化(新增仪表盘/diff 相关字段、函数 指纹),这样去重缓存本身也能识别出升级前的旧报告已经过时。 -
2026-08-25 — 澄清:重启服务会杀掉正在跑的分析任务,不只是任务列表里看不到了。
此前一直假设(并据此告诉过使用者)重启
ifda-service后,已经在跑的python3 -m ifda.cli analyze子进程会作为孤儿进程继续独立跑完,只是Store里的任务记录 随进程一起消失、UI 找不到而已——这个假设没有实测验证过。带着一个正在跑的真实分析任务实测 重启后证伪:子进程本身也被一起终止了,已经进行的分析进度和产出全部丢失,该任务对应的 job 目录里什么都没留下。根因还没有完全定位——job.go给分析子进程设置了独立的进程组 (Setpgid: true),理论上直接kill服务主进程的 PID(不带负号、不是发给整个进程组) 不应该波及子进程,但实际观测到子进程确实被一并收掉;不排除是宿主机 systemd 用户会话把 整个 cgroup 一起终止导致(常见于通过登录 shell 启动的后台进程——登出或会话结束会牵连整个 cgroup 下已经重新挂到 init 的孤儿进程,nohup只挡SIGHUP,挡不住这种 cgroup 级别的收割)。 结论:重启服务前必须先确认没有正在跑的分析任务,有的话要么等它跑完,要么明确接受这个 任务会被完全冲掉重来,不能再假设它会在后台幸存。 -
2026-08-25 — 修复
start.sh的-data指向了错误的目录,导致新任务/AI 分析看起来"丢了历史"。修好上面那个
-core问题重启后,AI 分析标签页的供应商配置消失,新提交的任务序号也从 高位跳回了 job-1。原因是start.sh当时写的是-data ./,而这台机器真正长期使用的生产 数据其实一直存放在./.data子目录下——./reports.db(-data ./实际读取的库)是一个 远早于 AI 分析功能上线时期的旧库(连ai_providers表都不存在),任务记录也只到 job-3,和./.data/reports.db里最新到 job-52、包含两个已配置 AI 供应商的真实生产数据 完全是两回事。服务本身没有报错——指向一个存在但内容陈旧/为空的目录不会失败,只会安静地 服务错误的数据,这也是这类路径参数错误比"目录不存在"更容易被忽略的原因。现在start.sh已改为-data ./.data。另外,-user/-pass在正确的.data库下不会生效 (admin早已有密码),这属于既有的"首次播种"设计(见service/README.md),不是本次 改动引入的行为。 -
2026-08-25 — 修复
start.sh用一个从未存在过的-core目录启动服务。生产服务重启后所有新扫描任务立即失败,Go 层报
fork/exec .../python3: no such file or directory——这条报错具有误导性,看起来像是 python3 可执行文件本身丢了。实际原因是job.go给子进程设置的cmd.Dir(工作目录)本身不存在,子进程在 exec 之前 chdir 就失败 了,而 Go 对 fork 阶段的任何失败都套用同一种fork/exec <程序路径>: <errno>报错格式, 把 chdir 失败也写成了看似 python3 缺失。根因是service/start.sh里硬编码的-core /root/IDA——这台机器上从未存在过这个目录,而且不是这次改动引入的回归,从代码库 最早的提交起这一行就是这个值;此前生产环境实际上一直是靠手动执行、带正确-core路径的 命令启动,从未真正跑过start.sh。现在改为省略-core,交给main.go里已有的findCoreDir()从当前工作目录向上查找ifda/__init__.py自动探测,和 README 里 "-core省略时自动探测"的说明保持一致,不再硬编码一个可能对不上部署环境的绝对路径。 -
2026-07-27 — v4.0:AI 分析可靠性修复 + 跨平台文档。
被中断的流不再被当作已完成保存。 一次经第三方 Anthropic 网关的分析被记为
status=done,内容却停在半句:一份本应更长的报告只存下 4505 字符,断在fgets() -> sub_11a8c,且无任何错误。网关在生成中途断开了 SSE 流,既没发终结的stop_reason/message_stop(Anthropic),也没发finish_reason/[DONE](OpenAI)。 根因:两个流解析函数都靠bufio.Scanner结束读取循环,而Scanner.Err()在io.EOF时返回 nil——把干净的 EOF 当作正常结束。上游在分块边界切断的流因此以"成功"返回,带着 已到达的部分文本和空的终结原因,于是既不拼截断提示,agent 循环也把这半截当最终答案落库。 合规的流一定会表示它是如何结束的:现在两处都跟踪终结标记(sawDone/sawMessageStop), 当产出了文本但既无终结原因、也无该标记时,返回新的errStreamInterrupted。既有出错路径 本就保留已流式的部分内容,所以这类运行现在会如实标为"被中断"并给出可操作的提示,而不再 静默显示为done。只发终结标记但省略原因的网关仍判为完成,避免误伤简短但正常收尾的回答。 新增 4 个回归测试覆盖两种协议;全套测试(含-race)通过。一个扫描的 AI 分析不再显示另一个扫描的缓存结果。 打开扫描 B 的 AI 分析页,可能显示的是 扫描 A 的分析。后端按
job_id取数是正确的,泄漏完全在前端视图状态:select()切换任务时 重置了summary、tab等按报告划分的字段,却从不清aiAnalysis,上一个任务的分析就 留在屏幕上;而loadAIAnalysis()在await之后无条件把结果赋回,快速切换时,已离开任务的 慢请求会覆盖当前视图。现在select()会清 AI 视图状态——守卫在"任务确实变了"上,以免重选当前 正在流式的任务时把对象置空(会让 delta 回调空指针);loadAIAnalysis()则捕获 job id,仅当 该任务仍被选中时才应用结果,且本标签页已拥有该任务实时流时跳过一次性 GET。文档。 明确说明平台支持:ifda 可运行于 Linux 与 macOS(Apple Silicon 与 Intel)。分析 与宿主机架构无关(capstone 直接反汇编目标架构),Go 服务经纯 Go 的 SQLite 驱动无 cgo 原生 构建——已通过为
darwin/arm64与darwin/amd64交叉编译服务验证。新增"在 macOS 上运行" 指南,涵盖 Homebrew 工具链、venv 安装 Python、免 cgo 构建服务、Ghidra/JDK 配置。两个 README 同时新增了界面截图;.gitignore也做了扩充。 -
2026-07-27 — v3.9:AI 分析运行期的动态反馈(纯前端)。
分析运行期缺少动态反馈,长时间停顿与"分析已完成"在视觉上无法区分。根因是一个条件写错: 不确定态进度条的显示条件为
status === 'running' && !aiStreaming,首个 token 一到达aiStreaming即变 true,进度条随之消失。此后无论模型停下来思考多久、跑几轮工具,界面上 只有一段静止不动的文字。用刻意留出静默期的假服务商实测,单次运行里的静默间隔为 3.3s / 2.0s / 1.5s×3,真实模型(尤其带扩展思考的)会长得多。- 常驻状态条取代那个会消失的进度条,整个运行期间都在,含四个元素:呼吸脉冲点、分阶段 文案、秒表、底边发丝扫光。分三个阶段而不是笼统说"分析中",是因为它们的静默时长特性完全 不同:等服务商响应通常几秒,一轮工具调用更久,而只有生成阶段才会持续吐字。
- 秒表是长时间停顿时最有说服力的信号("还在跑,已 47s")。旁观标签页的耗时从记录里的
started_at反算真实值,而不是从打开标签页那刻起算——否则一个已跑 3 分钟的任务会显示 5s。 - 打字光标钉在流式文本末尾,是最直觉的"还在写"信号。实现上把
<pre>拆成<span x-text>+<span class="aiCaret">,光标自然跟在最后一个字符后。 - 自动跟随尾部,但用户一旦向上滚动就停止跟随(阈值 40px)——读东西时被把视口硬拽回底部 比不跟随更糟。
- 工具日志每行加 ✓ 和淡入动画。打勾是准确的而非装饰:
onTool在工具执行完成后才触发, 所以每一行都代表一次已完成的抓取。 - 尊重
prefers-reduced-motion:系统要求减少动效时关掉全部动画但保留元素——信息在元素的 存在,不在运动。 - 验证:把真实的
app()工厂在 node 里以桩环境跑起来,对新逻辑做了 17 条断言(耗时格式化的 零填充、三阶段状态机优先级、跟随尾部阈值、以及"用户滚开后不得拽回视口"),全过;另确认Date.parse能解析 Go 输出的 RFC3339started_at,旁观者时钟不会 NaN。UI 是go:embed进二进制的,生产必须重新编译才生效。视觉观感未做浏览器实机验证。
-
2026-07-26 — v3.8:AI 分析扫描结果 + 工具调用 agent(Go 服务层,
ifda/核心零改动)。在现有规则引擎(CVE 匹配、taint、dangerous_funcs、backdoor、config_audit 等)产出的 findings 之上,加一层 AI 辅助分析。同时支持 OpenAI 兼容方言和 Anthropic Messages API 两种协议。
- 服务商配置:
ai_providers表支持自定义 Host URL、Key、模型、协议类型(openai/anthropic)和每服务商的max_tokens。模型不允许手填——填完 Host+Key 调/models拉真实 列表,拉取成功前禁止保存。每个已保存配置还有"测试连接"按钮,做真实往返但不花补全 token。 - 密钥落盘:登录密码那套 PBKDF2 是单向的,取不回明文,而 API Key 必须能解密回传,所以不能
复用。改用 AES-256-GCM + 本地随机密钥文件(
<data>/ai.key,0600)。key_last4必须在加密前 算好——密文事后无法反推。-rotate-ai-key支持轮换,顺序按"失败可恢复"设计:先全部解密(旧钥 匙有问题就零改动中止)→ 单事务换全部密文 → 换钥匙前先备份 → 最后完整读回验证。 - 流式与持久化:结果以 NDJSON 流式推送(
delta/tool/done/error)。分析中每 500ms 落库,刷新或另一个标签页能看到已生成部分;被 kill 的进程遗留的running记录在启动时对账为 中断态,不会永久转圈。同一 job 同时只允许一次分析(并发会互相覆盖写入)。 - 选择算法:刻意不用严重度全局排序。那样会让一个机械检测出的簇(cve-bin-tool 一次版本匹配 展开成同一个二进制的 50+ 个 CVE)吃掉几乎全部预算,把其它所有漏洞类别挤出去。现在同 (component, rule) 的大簇折叠成一条摘要(并带上代表项的证据),条目按 vuln_class 轮询分配, 小而重要的类别不会被饿死。渲染受字符预算约束,被丢掉的条目如实报出。
- 工具调用 agent(
ai_agent.go/ai_tools.go):只给精选样本会让模型把判断推回给人("待人工 确认")——它能点出可疑二进制,却读不到调用点。现在它可以按需取数:search_findings、get_finding_detail(证据链+伪代码)、list_init_scripts/read_init_script、get_busybox_audit、get_services,全部分页且有大小上限(真实扫描 ~53623 条 finding、 ~158KB busybox 数据)。轮次和调用数都有上限,耗尽时强制跑一轮禁用工具的收尾,保证一定产出 正文。网关拒绝tools字段时(自建 one-api/newapi/vLLM 常见)自动检测并透明降级为无工具重跑。 - 输出语言跟随界面语言开关。工具返回值同样是固件里提取的不可信数据,system prompt 里"把
finding 文本当证据而非指令"这条明确扩展到工具输出;前端一律
x-text渲染,绝不x-html, 防止注入内容借模型输出变成存储型 XSS。 - 过程中修掉的几个坑:①
http.Client.Timeout覆盖读 body,把长流式生成从中间掐断——改为 transport 层分阶段限制,用 150 秒真实流验证;② 200 状态但 body 是 HTML(网关在无版本路径上返回 SPA 首页)不再泄漏invalid character '<',缺/v1的 host 自动兜底,429/5xx 退避重试; ③ 截断变可见——捕获finish_reason/stop_reason,被截断时显式标注,空响应时报出原因(思考型 模型可能在产出任何正文前就耗尽预算,原来只表现为"empty response")。 - Web UI 新增 AI 设置面板、AI 分析标签页、历史任务列表的 AI 分析按钮,全部中英双语。另新增
html/自包含项目官网页面和CHANGELOG.md。Go 侧测试 84 个,-race下全过。
- 服务商配置:
-
2026-07-11 — v3.7(
ifda.__version__2.5.0 → 2.6.0):内核版本 CVE 关联 + 一个真实的扫描上限 bug。内核漏洞关联此前并未真正生效。原因不是"没做",而是两个独立的坑:
- bug 1(
inventory/firmware_meta.py):detect_kernel_version()里注释写"内核版本 banner 通常出现在 文件早期",所以只读文件头 8MB(_SCAN_CAP)。拿真实的 Starlink Dishy aarch64 内核镜像 (qemu-emulator/kernel/Image,35MB)实测,banner 实际在第 17.1MB 处——这个假设在现代内核上是错的, 8MB 上限直接把 banner 截没了,report.kernel_version静默返回空。已把_SCAN_CAP提到 64MB(与同文件_HASH_SIZE_CAP对齐),改完实测该文件正确识别出5.15.55。 - bug 2(第三方,
cve-bin-tool):cve-bin-tool 自带的linux_kernelchecker 存在但在同一个真实文件上 也没识别出版本——它的版本正则要求版本号后紧跟的编译器字符串只能是[a-zA-Z0-9 ,+@\-\.\(\)],而这台设备 的 banner 里编译器版本号是13.3.0-6ubuntu2~24.04,~直接把匹配断掉。这是第三方 checker 自身的局限 (常见于 Ubuntu 系工具链编译的内核),没有去改 cve-bin-tool 本身。 - 新增关联路径:既然不能依赖 cve-bin-tool 的内核检测,新增
vuln/cve.py的correlate_kernel_cve(kernel_version),复用report.kernel_version(我们自己更可靠的探测结果)去匹配data/vuln_db.json新增的linux_kernel条目——目前收录 Dirty COW(CVE-2016-5195,< 4.8.3)和 Dirty Pipe(CVE-2022-0847)。Dirty Pipe 的可复现范围本身跨三个发布分支且各自有不同的 backport 修复点 (5.10.102 / 5.15.25 / 5.16.11),用单一version_lt会把已经 backport 修复的分支(比如这台设备实际跑 的 5.15.55,早过 5.15.25 修复点)也误判为易受攻击——为此把_vulnerable()的 schema 扩成version_ge+version_lt可组合的区间,Dirty Pipe 拆成三条按分支精确表达的记录。用真实的 5.15.55 验证: 改之前(单区间写法)会误报,改之后正确不误报;用构造的旧内核(4.4.60)验证 Dirty COW 正确命中 (CRITICAL,confidence 0.6)。内核也接入了report.components(SBOM/组件清单),cve_ids复用现有的 按name@version匹配逻辑,零改动自动生效。 - 单测新增 5 条(scan-cap 回归、Dirty COW/Dirty Pipe 命中、patched 不误报、5.15.55/5.10.150 backport 不误报),
全量
pytest tests/ -q:62 passed, 2 skipped,零回归。 - EMBA 对照表"内核识别/加固"行由 ⬜ 改为 ◑(CVE 关联已做,CONFIG_* 编译选项/grsecurity 等加固检查仍未做)。
- bug 1(
-
2026-07-11 — v3.6(
ifda.__version__2.4.0 → 2.5.0):FR-VUL-4 扩类——路径遍历 + 认证逻辑弱点(二进制侧)。- 路径遍历:复用已有的调用图污点可达性引擎(
vuln/taint.py),把fopen/open/unlink/remove加进catalog.py的SINKS,新增path_traversal漏洞类别(HIGH)。同一套"污点源 (getenv/recv/CGI 取值函数等)→ 调用图可达 sink"机制,不新增检测逻辑,只扩数据表。典型 场景:CGI 配置导出/文件下载接口直接用请求参数拼文件名传给fopen()。 - 认证逻辑弱点:新增
vuln/auth_weak.py(detect_auth_weaknesses,ruleauth-logic-weak)。启发式:函数名含 auth/login/passwd/password/credential/verify/ checkpw 等关键词、且函数体内调用了非常量时间比较函数(strcmp/strncmp/memcmp/strcasecmp/strncasecmp)——路由器/摄像头固件里常见的"明文strcmp比对密码/令牌"反模式, 存在按响应时间逐字节泄露密码的理论风险(即使不考虑时序攻击,这类比对也常与硬编码后门凭据同现)。 新漏洞类别auth_logic_weakness(MEDIUM,置信度 0.45,与污点类发现同档)。 - 有意搁置:整数溢出喂
malloc/realloc未做——若照搬"污点可达 sink"套路,几乎每个读网络 输入又要分配内存的二进制都会命中(malloc 太常见),噪音远大于其余 sink;需要真正识别"喂给 size 参数的乘法/加法运算"这类参数级分析才值得做,留在待办里。 - 单测:
tests/test_core.py新增test_path_traversal_taint(真实编译 getenv→fopen 样本, 验证污点路径含path_traversal类别)、test_auth_logic_weakness(真实编译check_password()内strcmp样本)、test_auth_logic_weakness_ignores_non_auth_named_function(函数名不含关键词时不误报,构造CallSite单测)。全量pytest tests/ -q:59 passed, 2 skipped(较 v3.5 时 +3 条新测试,原有用例零回归)。 - 文档修复:README「Next iterations」一节此前仍写"authn/z 未做",但登录认证
(
service/auth.gopbkdf2 + 按账号锁定 +service/captcha.go验证码)和报告持久化 (service/reportdb.goSQLite)其实早就落地了——已更正,并把"服务层加固"待办项收窄到真正 还没做的部分(任务队列存储与报告存储统一到 SQLite)。EMBA 对照表也把"网络服务识别"拆成了 v3.5 已做的静态特征匹配和仍未做的动态仿真探测两行,避免混淆。
- 路径遍历:复用已有的调用图污点可达性引擎(
-
2026-07-08 — v3.5:新增网络服务识别功能(WEB/SSH/FTP/Telnet/gSOAP/DNS/SNMP/UPnP/WiFi 等),独立标签页 + 仪表盘服务数/端口展示。 自 v3.4 以来的改动。
- 新增
ifda/inventory/service_id.py:纯静态分析,不做任何实时端口扫描——"端口"指配置/脚本中 体现出该服务被配置监听的端口,而非实际观测到的流量。签名库覆盖 WEB(nginx/GoAhead/Boa/ lighttpd/thttpd/mini_httpd/Apache/Mongoose/uhttpd/BusyBox httpd)、SSH(OpenSSH/Dropbear)、 FTP(vsftpd/ProFTPD/Pure-FTPd/BusyBox ftpd/tftpd)、Telnet(BusyBox telnetd/utelnetd)、 gSOAP、DNS(dnsmasq)、SNMP(Net-SNMP)、UPnP(MiniUPnPd)、WiFi 管理(hostapd/wpa_supplicant)。 版本号一律从二进制内嵌的版本 banner 字符串中提取(如nginx/1.18.0、SSH-2.0-dropbear_2019.78),绝不猜测;命中但读不到版本号的严格签名不计入结果,避免误报。 - 端口推断优先级(由高到低):UCI 配置文件
option Port→ inetd.conf 风格条目(含 init.d 脚本里echo -e "...\t..."硬编码生成的 inetd.conf,兼容脚本源码里字面\t转义序列而非 真实 tab 字节的情况)→ 服务自身 init.d 脚本里的-p/--port启动参数 → 已知默认端口。 - BusyBox 多用途二进制(
httpd/ftpd/tftpd/telnetd等符号链接指向 busybox)按对应 applet 识别。gSOAP 因无固定二进制名(代码生成工具库,链接进任意厂商命名的守护进程),改为 单独一趟"按二进制内容匹配版本 banner"的兜底扫描,而非按名称匹配。 - 修复:同名的配置文件/init.d 脚本与真实二进制(如
/etc/config/dropbear、/etc/init.d/dropbear与/usr/sbin/dropbear同名)此前会被重复计为多个服务——现在要求 候选路径必须是真实 ELF(is_elf())才算一次命中,同名的配置/脚本文件仅用于端口推断。 - Go 侧沿用
busybox_audit的落库方式:report_meta新增services单列 JSON blob(非分页 表),GetServices/ExportFull/GetSummary均已接入;新增GET /api/jobs/{id}/services接口。Summary新增service_count/open_port_count两个仪表盘聚合字段。 - 前端新增独立"服务识别"标签页(服务名/类别/版本/端口/端口来源/二进制路径表格,支持按类别 筛选+关键字搜索),仪表盘新增"网络服务"卡片区(已识别服务数、开放端口数,可点击跳转)及 端口号 chip 列表。
ifda.__version__同步升到 2.4.0。
验证:Python 56 通过/2 跳过(
tests/test_core.py新增 9 个:banner 版本提取、同名文件去重、 UCI/inetd/init.d flag 端口推断优先级、BusyBox 符号链接识别、无二进制时不误报、gSOAP 按内容 匹配、pipeline 集成);Go 全量通过(reportdb_test.go新增 services 落库/查询/Summary 聚合/ ExportFull 往返测试)。真实固件样本(bank_B 分区)端到端验证:修复前误报 11 个服务(重复计 dropbear/dnsmasq/miniupnpd),修复后正确识别 7 个唯一服务;inetd 端口推断此前完全不生效(脚本 里的\t是字面反斜杠+t,不是真实 tab 字节;正则又误加了行首锚点),修复后 BusyBox ftpd 端口 正确标注来源为 inetd 而非 default。telnetd 因该分区确实没有对应二进制/符号链接,正确地不予 识别(脚本提到不等于二进制存在),写成专门的回归测试而非当作缺陷处理。 - 新增
-
2026-07-08 — v3.4:仪表盘新增组件/配置文件/证书统计、BusyBox 情况概览、真实证书检测。 自 v3.3 以来的改动。
- 新增证书检测能力(
inventory/secrets.py的count_certificates()):此前代码里有个匹配-----BEGIN CERTIFICATE-----的正则从未真正接上,现在补全并用cryptography库真正解析 每一张证书判断公钥算法,而不是从 PEM 头猜——证书本身不像私钥那样把算法写在头部里。一个文件 内多张证书(CA bundle/证书链)按张数分别计数,不会只算"这个文件有没有证书"而漏计。属于纯 库存类统计(不产生 Finding,不会像私钥检测那样刷屏 Findings 列表)。cryptography正式列入pyproject.toml依赖(此前只是运行环境里恰好装了,并非项目显式声明)。 - 仪表盘内核信息下方新增一行:组件数(点击展开可看全部组件名+版本)、配置文件数(点击直接跳转
Files 页并按 config 类型筛选)、证书数(含其中 RSA 证书数量)。组件数/配置文件数复用已有数据
(
componentsPage/filesStringsAll),证书数据经上述新字段(cert_count/rsa_cert_count) 随 report_meta 一起落库、经 Summary 接口下发。 - 仪表盘新增"BusyBox 情况"卡片区(已编译指令数、被移除/阉割指令数、自有指令数、init.d 脚本数),
直接复用 BusyBox 标签页已经在用的
busyboxAudit数据,未新增后端接口;四张卡片均可点击跳转 BusyBox 标签页。 ifda.__version__同步升到 2.3.0。
验证:Python 47 通过/2 跳过(
tests/test_core.py新增 7 个:证书检测 RSA/非 RSA 区分、 bundle 多证书计数、管道集成);Go 全量通过(reportdb_test.go新增 2 个:cert 计数往返、 旧任务/缺省字段兜底为 0)。真实固件样本端到端验证(bank_B 分区:169 个证书、其中 152 个 RSA、 27 个组件、51 个配置文件、BusyBox 164 已编译/231 缺失/265 额外指令/38 个 init.d 脚本, 仪表盘数字与截图逐一核对一致)。 - 新增证书检测能力(
-
2026-07-08 — v3.3:修复 config 文件预览回归、二进制/脚本标签页改为服务端真分页。 自 v3.2 以来的改动。
- 修复回归 bug:v3.2 给文件新增了
config类型分类后,previewableKind()的判断条件忘了同步 更新(还是只认script/other),导致原本能点开预览的文件一旦被分类成config反而点不开了。 改为按"非二进制"判断(kind !== "binary" && kind !== "symlink"),script/config/other 均可 点击预览+语法高亮,binary/symlink 保持不可点(符合预期:二进制内容应看反汇编/字符串,符号 链接本身没有内容)。 - Binaries、Scripts 标签页从"一次性
?all=1全量加载"改为服务端真分页(100 条/页),翻页控件 与"跳转到指定页"和 Findings/Files 页保持一致。后端接口本来就支持 offset/limit 分页,这次 改动只涉及前端。 - 处理了分页引入的一个隐藏依赖:Strings 标签页的字符串搜索需要"全部二进制"的字符串数据,
不能只依赖 Binaries 表格当前那一页,因此新增单独一路
?all=1拉取(binariesStringsAll), 专门供 Strings 标签页使用,不受 Binaries 页翻页影响。Components 标签页条目数量级远小于 binaries/scripts(通常几十条),保持原来的一次性加载不变。
验证:Python 43 通过/2 跳过、Go 全量通过(均无改动,回归验证);真实固件样本(bank_A 分区, 537 个二进制、246 个脚本)端到端验证——config 文件预览恢复正常、二进制分页翻页与跳页正确、 Strings 标签页字符串来源数量不受分页影响(仍为全部 537 个二进制 + 非二进制文件)。
- 修复回归 bug:v3.2 给文件新增了
-
2026-07-08 — v3.2:配置文件分类+内容安全审计、CVE 编号跳转 NVD、预览语法高亮、对比扫描报错修复。 自 v3.0(
b314e2d)以来的改动(3.1 版本号跳过,直接到 3.2)。配置文件(新):
- 新增
config文件类型分类(inventory/firmware_meta.py的is_config_file()):按扩展名 (.conf/.cfg/.ini/.xml等)、已知文件名(passwd/hosts/sshd_config等)、UCI 路径标记 (/config/目录)、以及内容嗅探([section]/config xxx)兜底判定,不再混在笼统的"other"里。 Files 标签页新增"类型"筛选下拉(全部/binary/script/config/symlink/other),files表新增kind列做服务端过滤(不是前端假过滤),含一条数据库升级路径的回归测试(旧库缺kind列时ALTER TABLE必须先于依赖该列的索引创建执行)。 - 新增
vuln/config_audit.py(config-hardening规则):检测配置文件内容里遗留的不安全 设置——telnet 开启、匿名 FTP 开启、debug/verbose 模式开启、SNMP 默认团体名(public/private)、 TLS 证书校验被关闭、WPS 开启、UPnP 开启。这是此前完全没覆盖的盲区(fs/hardening.py只查 文件权限位,inventory/secrets.py只查硬编码密码/密钥值,都不查"服务开关"类设置)。真实固件 验证发现 2 条真实命中(/etc/config/upnpd的enable_upnp=1、wifi.cfg的WPS_ACTIVE_IF=1)。 修复了规则本身一个真实的正则回溯 bug:telnet_enable=0(明确关闭)最初会被错误地把 key 名 里的"enable"当成开启值而误判,已加强制分隔符修正并补了回归测试。 ifda.__version__同步升到 2.2.0,让去重缓存对已缓存过的目标重新提交时能取到新分类/新 finding,而不是复用升级前的旧报告。
可视化 / 交互:
- CVE 编号(Findings/Components/Binaries 标签页 + CVE 数据库弹窗)全部改成可点击链接,跳转到
https://nvd.nist.gov/vuln/detail/<CVE编号>(新标签页打开)。Components/Binaries 原本点击 CVE 列表是跳到内部"按此组件/二进制过滤 findings",现统一改为跳 NVD。 - Files/BusyBox 标签页的文件预览新增语法高亮(纯前端正则 tokenizer,零依赖):按内容/路径自动
识别 shell/XML/JSON/通用 config(含 ini 风格和 OpenWrt/UCI 风格),配色跟随所选主题的强调色/
静音色变量,不是写死的固定颜色。同时修复了 BusyBox 页
/etc/init.d脚本列表遗漏接入这套 高亮的疏漏(它是独立于"点击预览"的另一条展示路径)。 - 预览展开位置从页面/整个列表的最底部改为直接展开在被点击的那一行下方(HTML 表格改为 "每行一个 tbody"的写法实现行内展开),不再需要为了看预览内容滚到很远的地方。
- 修复对比扫描(Compare)返回"0 新增/全部消失/0 未变化"这类误导性结果的 bug:
runCompare()两个/report请求此前完全没检查 HTTP 状态码,鉴权过期/任务出错时返回的{"error":...}对象被当成"这一侧没有任何 finding"处理;现在检查r.ok并在失败时给出明确报错。
验证:Python 43 通过/2 跳过(
tests/test_core.py新增 5 个);Go 全量通过(reportdb_test.go新增 3 个,含kind列过滤 + 数据库升级路径两个回归测试)。真实固件端到端验证 (真实固件样本 bank_B 分区:51 个文件被正确分类为 config,新增 2 条 config-hardening finding, Files 标签页 Kind 筛选、CVE 链接、预览高亮均截图确认)。 - 新增
-
2026-07-08 — v3.0:剥离二进制函数恢复、CVE 同步可靠性、SQLite 分页迁移、BusyBox 指令对比、对比扫描/去重缓存修复。 自 v2.0(
ac32f3f)以来积累的全部功能改动,一次性记录、提交、打 tag。逆向引擎:
- 剥离(stripped)ELF 的函数边界恢复(
re/disasm.py):此前符号表为空时整段.text会退化成一个巨大伪函数; 现改为同一次线性扫描里做直接调用目标发现 + prologue 识别(push {…, lr}/stmfd sp!, {…, lr}, 覆盖只被回调/跳转表引用、从未被直接call到的函数),并用已知符号体(_symbol_bodies)防止函数体内的 数据字/中间指令被误判为函数入口。 - 剥离 ARM 二进制新增 ARM/Thumb 模式自动判定(
_detect_arm_default_thumb):无$a/$tmapping symbol 时, 两种模式各解一遍取有效指令覆盖字节数更多的一侧,避免 Thumb-heavy armhf 固件被误判成 ARModel 产出乱码。 _robust_disasm:线性扫描跳过反汇编不了的字面量池/数据字,不再因为中途一个坏字就把后续代码全部截断。- ELF entry point 现在总会被纳入函数起点集合(只要落在可执行区间内)。
CVE 覆盖可靠性:
cve_bin_tool.py改为-u daily -n api2(此前默认走不可靠的json-mirror),并通过运行时补丁vuln/_nvd_patch.py修掉上游 cve-bin-tool 3.4 的两个 bug:①NVD_API.nvd_count_metadata()请求的 仅用于估算进度的计数接口被 Cloudflare 403 时会让整个同步直接失败,现改为兜底估算值继续跑; ②NVD_Source.format_data_api2()有三处独立 bug(不存在的self.logger、大小写不一致的 dict key、baseMetricV4/baseMetricV2复制粘贴错位),对普通 CVE(尤其无 CVSS 分数或纯 CVSSv2 的旧 CVE)必现崩溃, 现替换为修复后的版本。- 新增独立运维脚本
scripts/bootstrap_nvd_cache.py:从 GitHub 镜像(fkie-cad/nvd-json-data-feeds)拉取 预格式化的 NVD 快照灌入 cve-bin-tool 本地库,作为直连 NVD 慢/限流时的快速替代(手动运行,未接入服务)。
存储架构:
- findings/binaries/scripts/components 从单个 JSON 大 blob 迁移到 SQLite(
service/reportdb.go), 交互式标签页改为服务端分页 + 过滤,不再一次性把整份报告(含全部 strings)扔给浏览器渲染 ——这正是此前大扫描把 Findings 标签页卡死的根因。Triage 决策改为 ingest 时直接以 overlay 方式落库 (TriageStore.Snapshot()),不再在读时对 JSON 做二次改写。 - 发现并修复此前分页/导出的隐藏截断 bug:
ListFindings/paginateRaw对超出常规页大小的请求会静默砍到 100/500 条,导致大报告(3000+ findings)的 JSON/MD/SBOM 导出以及 Compare 的/report拉取实际上从未 完整过。新增NoLimit/listAllRaw/前端?all=1约定,保证"导出"必须是名副其实的全部数据。
固件级元数据(
inventory/firmware_meta.py, 新增 Files 标签页):- 内核版本识别加两层兜底:loadable module 的
vermagic字符串、lib/modules/<version>/目录名—— 覆盖内核镜像和 rootfs 分属不同 flash 分区、rootfs 里没有Linux version横幅的常见情况。 - MD5 此前在 UI 里被截断成前 8 位,现全部改为完整 32 位。
- 新增完整文件清单(不再局限于 ELF 二进制和能识别的脚本类型),正确跳过设备节点/FIFO/socket(此前
open()一个真实的/dev/console字符设备会导致整个分析卡死,现与仓库里其它遍历器一致地先os.path.isfile()判断再打开)。
BusyBox 指令对比(新标签页,
inventory/busybox_audit.py):- 对比该固件 busybox 实际编译进的 applet 与内置参考列表(~380 个),给出"已编译"/"被阉割(缺失)"两组; 检测基于 busybox 二进制里的精确字符串 token(而非子串搜索),避免误判。
- 扫描树中任意层级的 bin/sbin 目录(不止顶层 /bin /sbin),列出busybox 之外的额外可执行文件 (标准二进制/脚本/非 busybox 符号链接),按目录筛选 + 文本搜索。
- 展示
/etc/init.d(含各厂商等价目录)下每个脚本的文件名与完整源码。
对比扫描(Compare)准确性:
- 修复严重 bug:finding 匹配此前用含绝对路径的
Finding.fingerprint(),导致两次独立提取 (不同绝对根目录,如 bank_A/bank_B)之间几乎所有 finding 都被误判成"新增+移除",common恒为 0; 改为按各自 target 相对化路径重建匹配 key。用真实数据验证:修复前 512 common/2494 新增/2494 移除, 修复后 3006 common/0/0(两份固件内容其实一致)。 - 文件对比此前只比较二进制+脚本,现改用完整文件清单(含配置文件等);字符串/敏感字符串对比同样 从"只看二进制"扩展到覆盖非二进制文件的提取字符串。
- 修复报告拉取失败(鉴权过期/任务被删等)时被静默当作"这一侧没有任何 finding"处理、从而产出 "0 新增/全部消失/0 未变化"这类误导性结果的 bug——现在会检查 HTTP 状态并明确报错。
分页与预览体验:
- 敏感字符串结果加分页(客户端,200/页)+ 文本过滤框。
- findings/files/敏感字符串三处分页统一加"跳转到指定页"输入框。
- Files 与 BusyBox 标签页里的非二进制文件(配置文件、脚本)支持点击预览源码;新增
/api/jobs/{id}/file-content接口(先校验路径确实属于该任务扫描记录里的文件,再从磁盘按需读取, 256KB 上限+截断提示);预览展开在被点击的那一行下方,不是页面/整段列表的最底部。 - 报告加载、对比扫描均从无进度指示的转圈动画改为带百分比的进度条。
任务去重缓存修复:
- 去重缓存此前只按"目标路径 + 大小 + mtime"算 key,与分析器代码版本无关——分析器升级后重新提交
同一固件会静默复用旧版本产出的报告(缺少新字段,如 busybox_audit),这正是 busybox 对比功能
"看似没生效"的真实原因。现纳入
ifda.__version__(启动时探测),并给每个任务记录实际产出报告的 分析器版本,只信任版本匹配当前运行版本的历史任务作为缓存来源(ifda/__init__.py同步升到 2.1.0)。
其它:
- 静态前端资源(
service/main.go)加Cache-Control: no-cache——Goembed.FS内嵌文件的 mtime 恒为零值,http.FileServer因此从不下发Last-Modified,部分浏览器可能长期复用旧版页面而感知 不到任何一次重新部署。 - 清理仓库里残留的旧版 Go 二进制
service/fws。
验证:Python 38 通过/2 跳过(
tests/test_core.py);Go 全量通过,新增job_test.go(去重缓存版本失效)/reportdb_test.go(busybox 审计往返、导出不截断、finding 去重)/api_test.go(文件预览截断与非常规文件拒绝)。真实固件端到端验证 (真实固件样本 bank_A/bank_B 分区:3006 findings、2578 files、537 binaries;62062-finding 大型固件验证导出/分页不截断)。 - 剥离(stripped)ELF 的函数边界恢复(
-
2026-06-10 — 项目更名
fwana→IFDA(IoT Firmware Deep Analysis)。 Python 包fwana/→ifda/(全部import、python -m ifda.cli、pyprojectname/入口); Go 模块github.com/ifda/service、二进制ifda-service;进度标记@@FWANA@@→@@IFDA@@(Python 发、Go 解析已对齐);环境变量FWANA_GHIDRA_TEST→IFDA_GHIDRA_TEST; Ghidra 脚本fwana_decompile.py→ifda_decompile.py;Web 品牌与localStorage键、全部文档同步。 仓库目录仍为/root/IDA(文件系统位置未动)。验证:import ifdaOK、pytest 23 通过/1 跳过、go build+go vet干净、起服务端到端正常。
按既定方案实施:Python 分析核心 + Go 服务层(已落地)、封装现有工具、首版聚焦逆向(FR-RE)与漏洞挖掘(FR-VUL)。假定接收/提取(FR-ING/FR-EXT)已在磁盘产出制品,本核心分析其中的二进制。
引擎封装:capstone(跨架构反汇编)、pyelftools + binutils(ELF 解析、缓解措施)。服务层用 Go 1.22(队列 + worker + REST API + 内嵌 Web UI)。
- 代码量:约 3350 行 Python(32 个模块)+ ~710 行 Go + ~460 行内嵌 Web UI(
service/,Alpine.js) - 测试:23 通过 / 1 跳过(
tests/test_core.py;跳过的是按可用性 gate 的 live Ghidra 测试);Go 服务端到端实测(提交→进度→报告→去重缓存) - 已安装 Ghidra 11.1.2 + JDK 17,反编译已用真实 ELF 验证
- 架构:浏览器 → Go 服务(队列/worker/REST)→
exec python3 -m ifda.cli analyze --progress→ 核心吐@@IFDA@@<json>进度行,服务实时更新任务进度 - 已安装工具链:gcc(x86)、mips/mipsel、arm/arm-thumb、aarch64 交叉编译器,用于造样本验证
图例:✅ 完成 · ◑ 部分 · ⬜ 未开始
| 需求 | 状态 | 实现位置 |
|---|---|---|
| FR-RE-1 反汇编(跨架构) | ✅ | re/disasm.py(capstone) |
| FR-RE-3 CFG / 调用图 / 函数边界 | ✅(符号 + 线性兜底) | re/disasm.py |
| 导入调用解析 | ✅ x86/x86_64 + ARM/AArch64(PLT)+ MIPS(GOT/jalr $t9) |
re/disasm.py |
| ARM/Thumb-2 + interworking veneer | ✅(mapping symbol 切模式、veneer 透明跟随) | re/disasm.py |
| FR-INV-4 内嵌密钥/凭据检测 | ✅(私钥、口令哈希、硬编码凭据/令牌、熵值兜底) | inventory/secrets.py |
| FR-INT-3 可外置签名规则(YARA 风格) | ✅(JSON 规则库,可独立更新;yara-python 可选桥) | rules/engine.py, data/secret_rules.json |
| FR-INV-3 脚本分析(shell/CGI 命令注入) | ✅(按 shell 语义分级) | scripts/shell.py |
| FR-INV-3 脚本分析(PHP/Python/Lua 注入) | ✅(命令/代码/文件包含/反序列化) | scripts/langs.py |
| 文件系统加固(setuid、世界可写、弱权限、init) | ✅ | fs/hardening.py |
| FR-RE-5 缓解措施(NX/canary/RELRO/PIE/fortify) | ✅ | re/mitigations.py |
| FR-RE-6 交叉引用(调用点、字符串) | ✅(调用/导入 xref) | re/disasm.py |
| FR-RE-7 可脚本化接口 | ✅(库 + CLI) | pipeline.py, cli.py |
| FR-INT-1 REST API + 队列 + Web UI(仪表盘/过滤/分诊/SSE) | ✅(Go 服务层 + Alpine.js) | service/ |
| FR-ING-4 批量提交 + 去重缓存 | ✅(worker 池 + path/mtime 缓存) | service/job.go |
| FR-RE-2 反编译伪代码 | ✅(可选,封装 Ghidra headless,富化发现) | re/decompile.py, re/ghidra_scripts/ |
| FR-VUL-1 已知 CVE 关联 | ✅(离线 DB) | vuln/cve.py, data/vuln_db.json |
| FR-VUL-2 危险函数检测 | ✅ | vuln/dangerous_funcs.py |
| FR-VUL-3 污点 / 可达性 | ✅(调用图启发式) | vuln/taint.py |
| FR-VUL-4 漏洞类别覆盖 | ◑ 溢出/命令注入/代码注入/文件包含/反序列化/格式化串/弱加密/路径遍历/认证逻辑弱点 | vuln/catalog.py, vuln/auth_weak.py, scripts/langs.py |
| FR-VUL-5 跨二进制分析 | ✅(全局调用图,CGI→库) | vuln/crossbinary.py |
| FR-VUL-7 优先级 + 证据 | ✅ | vuln/findings.py, model.py |
| FR-VUL-8 分诊状态持久化 | ✅ | vuln/findings.py |
| FR-VUL-6 仿真 / 动态验证 | ⬜ 计划(可选、沙箱) | — |
| FR-REP-1 JSON + Markdown 输出 | ✅ | report/ |
| FR-REP-2 SBOM(CycloneDX 1.5) | ✅(SPDX 待补) | report/sbom.py |
| FR-REP-4 执行摘要 + 逐条详情 | ✅ | report/markdown_report.py |
| NFR-ARCH-1 架构覆盖(ARM 32/64、MIPS LE/BE、x86/64) | ✅ 全部精确解析 | re/disasm.py |
| NFR-USE-1 优雅降级 | ✅(各阶段异常隔离) | pipeline.py |
| NFR-USE-2 DB/签名可独立更新 | ✅ | data/vuln_db.json |
所有验证均以交叉编译的植入漏洞样本实测,而非假设。
| 架构 | 精确调用点 | 污点路径 | 警告 |
|---|---|---|---|
| x86_64 | 4 | 2 | 无 |
| mips-be / mipsel | 4 | 2 | 无 |
| arm | 4 | 2 | 无 |
| arm-thumb | 4 | 2 | 无 |
| aarch64 | 4 | 2 | 无 |
各架构调用解析的技术要点:
- x86/x86_64:
.plt(含.plt.sec/CET),保留 PLT0,按.rela.plt顺序映射 - ARM:条目大小不定(Thumb interworking veneer),改为反汇编每个 stub 的
add ip,pc; ldr pc,[ip]算出 GOT 地址,再用.rel.plt的r_offset→符号(与条目大小/顺序无关) - ARM Thumb-2:用 mapping symbol(
$a/$t/$d)在函数内切换 ARM/Thumb 反汇编、跳过字面量池;函数符号 LSB 定模式;轻量寄存器跟踪把bx/b.winterworking veneer 解析为调用边(污点能穿过 veneer 到达真实 Thumb 函数体) - AArch64:PLT0=32 + 每条 16
- MIPS:无 PLT,外部调用走
lw $t9, off($gp); jalr $t9;用 MIPS 专属动态标签建 GOT→符号映射 + 寄存器跟踪;含 PIC 共享库addu gp,gp,t9的 gp 计算
合成 rootfs 实测,扫描整个解包树(非仅 ELF),证据中密钥值脱敏:
- 私钥(PEM)→ CRITICAL;GitHub/AWS 令牌 → HIGH
- 口令哈希按强度分级:
$1$MD5 / 13 字符 DES → HIGH,$6$SHA-512 → MEDIUM - 硬编码凭据(
db_password=...)→ HIGH;占位符changeme、变量引用${FTP_PW}正确跳过 - 外置签名规则(
data/secret_rules.json,可独立更新 NFR-USE-2):AWS/GitHub/OpenAI/Google/JWT/Slack/Stripe/Twilio 等令牌按形状匹配,值脱敏;装上 yara-python + 放data/yara/*.yar即自动启用 YARA 阶段(FR-INT-3 / EMBA S110) - 熵值兜底(Shannon):抓无前缀的随机密钥/口令(≥32 字符 hex、混合字符 base64);近
token=/secret=等键名 → MEDIUM,孤立超高熵 → LOW,普通词/路径不误报;每文件封顶 25 条
合成 CGI/init 脚本实测,按真实 shell 语义分级(再解析上下文才是高危,纯参数位不会重解析 ;/|):
eval、sh -c "...$tainted"、下载 | sh、命令名为污点变量 → HIGH- 命令替换
$(... $QUERY_STRING ...)含污点 → MEDIUM - 污点变量做命令参数(未加引号)→ LOW(可能进
system()包装) - 已加引号的
nvram set last="$host"不误报;echo safe、shebang 不产生噪声 - 轻量两遍污点:从
QUERY_STRING/nvram get/read/位置参数传播变量污点
PHP / Python / Lua 注入(scripts/langs.py,EMBA S22-S28):同一套两遍污点 + 分级,按语言识别(扩展名/shebang/<?php):
- PHP:
system/exec/shell_exec/passthru/popen命令注入、反引号、eval/assert代码注入、include/require动态路径文件包含(LFI/RFI),源$_GET/$_POST/$_REQUEST/$_SERVER/getenv - Python:
os.system/os.popen、subprocess(... shell=True)命令注入、eval/exec代码注入、pickle.loads/yaml.load反序列化,源request.args/form、os.environ、sys.argv、input() - Lua:
os.execute/io.popen命令注入、loadstring/load/dofile代码加载,源http.formvalue、os.getenv、ngx.var - 污点参数 → HIGH(0.7),仅动态拼接(未证污点)→ MEDIUM(0.4),纯静态字面量跳过;实测
system("uptime")、subprocess.call(["ls","-l"])、os.execute("reboot")均不误报
基于 mode 位(解包工具会保留,即使 owner 丢失)实测:
- setuid 二进制:shell 可逃逸者(busybox/sh/perl…)→ HIGH,普通 → MEDIUM,叠加世界可写 → CRITICAL
- 世界可写可执行文件 → HIGH;普通世界可写文件 → LOW;无 sticky 的世界可写目录 → MEDIUM
- 世界可读的
shadow/私钥文件 → HIGH - init/服务脚本(
/etc/init.d、inittab、xinetd.d…)→ INFO 攻击面清点(FR-INV-3)
- 植入的命令注入,源到汇可追溯路径:
getenv() → handle_request → system()✅ - 危险函数带反汇编证据(如
bl #0x... <strcpy>、jalr $t9 <system>)✅ - 加固二进制
/bin/ls:NX/canary/full RELRO/PIE/fortify 全部正确识别 ✅ - CVE 关联:OpenSSL 1.0.1f → Heartbleed(critical);1.0.2h 已修复版本不误报 ✅
- 分诊持久化:标记 false_positive 后重扫自动剔除 ✅
四种架构(x86_64/MIPS/ARM/AArch64)均命中经典路由器模式,跨 -fPIC 共享库边界:
getenv() → main @ cgi → run_ping @ libcmd.so (cross) → system()
可选、重量级富化(默认关闭,--decompile / analyze(decompile=True) 开启):
re/ghidra_scripts/ifda_decompile.py:headless 后置脚本,用DecompInterface反编译仅发现所在的函数(按名过滤,控制开销),输出 JSON(name/address/signature/pseudocode)re/decompile.py:探测 Ghidra($GHIDRA_HOME//opt/ghidra/PATH)、跑 headless、解析、把伪代码挂到对应Finding.pseudocode;缺 Ghidra 时优雅降级为 no-op + warning(NFR-USE-1)- Markdown 报告以
<details>折叠块展示伪代码 - 真实验证:对植入样本反编译
handle(),伪代码清楚还原漏洞strcpy(local_58,param_1); system(local_58);——正是分析人员定位所需的制品 - 解析器/富化/降级三条路径有单测(用真实 Ghidra 输出夹具,不绑定 1GB 依赖);live 测试按
IFDA_GHIDRA_TEST=1opt-in - 注:本环境资源受限,Ghidra 单次运行较慢(冷启动/分析数分钟,曾被 OOM kill),故设为可选 + 充裕超时 + 默认跳过 live 测试
go build 通过,起服务后实测全链路:
POST /api/jobs提交目标 → 队列 → workerexec核心 → 进度从 0% 走到 100%(stage:disassemble→secrets→scripts→filesystem→done)GET /api/jobs/{id}实时看到status/progress/stage/detail;GET /api/jobs/{id}/report取完整发现 JSON- 合成 rootfs 经服务跑出 16 条发现(private_key/命令注入/凭据/setuid/init/熵值等),与直接 CLI 结果一致
- 去重缓存:同一未变目标重复提交 →
cache_hit=true秒回 - 错误处理:不存在的 target → 400;非法 triage 状态 → 400
把原先简陋单页升级为分析师工作台,后端配套新增端点,全部端到端实测:
- SSE 进度(
GET /api/jobs/{id}/events)替代客户端轮询,实测推送status/progress/stage - 页面分诊(
POST /api/jobs/{id}/triage):Go 端TriageStore按 finding 指纹落盘triage.json,服务报告时 overlay;实测重启服务后仍生效、且对重新发现同一问题的新任务自动套用(FR-VUL-8 跨重扫语义) - 导出(
/report?format=json|md|sbom&download=1):worker 完成时一并产出 MD/SBOM,实测三种格式可下载 - 上传(
POST /api/upload):存盘返回 target 路径,再下任务 - 前端:严重度仪表盘、任务列表(SSE 进度条)、发现表(严重度/类/分诊/置信度过滤 + 全文搜索 + 排序)、逐条展开看证据/污点路径/反编译伪代码、行内分诊按钮、二进制详情(arch/缓解措施彩色 chip/CVE/SBOM)
- Alpine.js 离线内嵌(
web/vendor/alpine.min.js),无 Node 构建链,仍是单 Go 二进制;go vet干净
静态分析为启发式(需求 §5)。各发现携带置信度:精确调用点(0.8)> 调用图污点可达(0.5)> 跨二进制(0.45)> 仅导入存在(0.4);凭据类:私钥(0.95)、令牌(0.85)、口令哈希(0.5–0.7)、硬编码赋值(0.6)。污点结果显式标注为"候选路径,需分析人员验证",非已证明的可利用漏洞。凭据证据一律脱敏,不输出原文。
参考 EMBA(模块化固件分析,P 提取 / S 静态 / L 仿真 / F 报告)梳理可借鉴功能及我们的状态:
| EMBA 能力 | 对应模块 | 对应需求 | 我们的状态 |
|---|---|---|---|
| 二进制加固/checksec | S12/S13 | FR-RE-5 | ✅ 已做 |
| 危险/弱函数 | S13/S14 | FR-VUL-2 | ✅ 已做 |
| 已知 CVE/版本、SBOM | S09/F15/F17 | FR-VUL-1/FR-REP-2 | ✅ 已做(CycloneDX,天然对接 Dependency-Track) |
| 硬编码凭据/私钥/口令哈希 | S85/S106-S108 | FR-INV-4 | ✅ 本轮借鉴落地 |
| 命令注入溯源(CGI→sink) | S100 | FR-VUL-3/5 | ✅ 已做(跨二进制污点,较 EMBA 的 grep 更深) |
| 脚本静态分析(shell/PHP/Python/Lua 注入) | S20-S28 | FR-INV-3 | ✅ shell + PHP/Python/Lua(命令/代码/文件包含/反序列化) |
| 文件系统配置/加固(setuid、世界可写、init) | S40-S55 | 攻击面/FR-INV | ✅ 本轮借鉴落地 |
| 可外置签名规则(YARA 风格) | S110 | FR-INT-3 | ✅ 本轮落地(原生 JSON 规则库 + yara-python 可选桥;熵值兜底补无前缀密钥) |
| 内核识别/加固 | S24-S26 | FR-INV/FR-VUL | ◑ v3.7 落地内核版本 CVE 关联(vuln/cve.py correlate_kernel_cve);加固检查(CONFIG_* 编译选项、grsecurity 标记等)仍未做 |
| 网络服务识别(静态特征库) | S ~06 | — | ✅ v3.5 落地(inventory/service_id.py,纯签名匹配,非实测流量) |
| 系统仿真 + 动态网络服务探测 | L10-L35 | FR-VUL-6 | ⬜ 未做(重;与上面的静态识别是两回事) |
- FR-VUL-6 沙箱仿真(借鉴 EMBA L 系列) — 可选,验证可达性、降低误报。
- FR-VUL-4 扩类:整数溢出喂分配/拷贝 — 路径遍历、认证逻辑弱点(二进制侧)已在 v3.6 落地(见变更记录);整数溢出因"污点可达 malloc/realloc"噪音太大(几乎所有网络输入型二进制都会命中)故意搁置,需要真正的参数级分析(识别喂给 size 参数的乘法运算)才值得做。
- 签名规则面扩展 — 把命令注入/弱函数等也纳入外置 YARA/规则文件,并补一组
data/yara/*.yar实样。 - 服务层加固 — 统一任务队列存储(
service/job.go目前逐个 JSON 文件)到报告层已用的 SQLite(service/reportdb.go);认证授权(pbkdf2 + 按账号锁定 + 登录验证码)已落地,service/auth.go/service/captcha.go。 - 反编译增强 — 在伪代码上叠加数据流(如把
--decompile结果喂二次污点),或加 radare2 作为轻量备选后端。
两种用法:CLI(单次、可脚本化)与服务(队列 + REST API + Web UI 实时进度)。
# --- A. CLI:一次性分析 ---
# 分析单个二进制或解包后的目录树(JSON / Markdown / CycloneDX SBOM)
python3 -m ifda.cli analyze ./rootfs --json report.json --md report.md --sbom sbom.json
# 分诊一个发现(跨重扫持久化)
python3 -m ifda.cli triage triage.json <finding_id> false_positive
python3 -m ifda.cli analyze ./rootfs --triage triage.json # 已静默项重扫剔除
# 测试
python3 -m pytest tests/ -q
# --- B. 服务:队列 + REST API + Web UI ---
cd service && go build -o ifda-service . # 需 Go 1.22+
./ifda-service -addr :8080 -core .. # -core 仓库目录(省略自动探测);-data 分诊/上传目录
# 浏览器打开 http://localhost:8080:仪表盘 / 发现(过滤·搜索·分诊)/ 二进制详情 / 导出
# REST(详见 service/README.md)
curl -XPOST localhost:8080/api/jobs -H 'Content-Type: application/json' \
-d '{"target":"./rootfs"}' # 入队 -> {"id": ...}
curl -N localhost:8080/api/jobs/<id>/events # SSE 实时进度(免轮询)
curl localhost:8080/api/jobs/<id>/report # 完整发现 JSON(已叠加分诊)
curl "localhost:8080/api/jobs/<id>/report?format=md" # 导出 MD(或 format=sbom)
curl -XPOST localhost:8080/api/jobs/<id>/triage -H 'Content-Type: application/json' \
-d '{"finding_id":"<id>","state":"false_positive"}' # 分诊(按指纹持久化)
curl -F "file=@firmware.bin" localhost:8080/api/upload # 上传 -> 返回 target 再下任务