r0re自动逆向分析工具实战记录"

admin 2026-10-16 07:30:39 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

r0re 自动逆向分析工具实战记录

fyrlove
fyrlove

看雪学苑

2026年9月28日 17:59
上海

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

做 Android CTF 时经常碰到同一个问题:前面用 JADX、strings 很快能摸到入口,后面一碰到壳、动态注册 JNI、native 自解密或反调试,就开始在好几个终端和工具之间来回切。最后即便拿到一串“像答案”的内容,过几天再看,往往说不清它从哪一层来的,也不知道有没有真正走到校验点。

所以我把 Cairn 用 Java 重写,再把 Android 逆向常用的工具、提示词和验证环节接进去,做成了 r0re。它干的事情很简单:把任务拆小,把每一步看到的东西留下来;候选错了,就从错误处继续,而不是重新开一个聊天窗口。

这次用它跑完了看雪论坛公开 Android CTF 题集。AliCrackme\_2、AliCrackme\_4 都出现过首个候选不对、补 Hint 后才重新收敛的情况,我把这些过程保留下来了。对我来说,逆向 Agent 能不能第一次完成不是最关键的;更重要的是错误后能不能留下原因,并继续往下走。

说明:文中的样本仅用于公开 CTF/CrackMe 学习和授权逆向场景。源码、配置模板和测试记录随帖附件提供;发布时请将自己的模型 Key、设备地址和本机绝对路径从配置文件中移除。

一、测试结果:9 题通过

本次测试覆盖题集中的 9 道题,下图是最终记录。后面只展开 5 道:我想把篇幅留给出错后的处理、Hint 怎么生效,以及最终结果怎么验,而不是逐题堆截图。

二、我为什么从%20Cairn%20开始改

r0re%20不是凭空起的项目。它基于 Cairn 的架构思想进行%20Java%20重实现,再针对%20Android%20逆向补齐工具、提示词、运行态观测和验证环节。

Cairn%20的开发者是 淚笑,并在开源组织「起零衍迹%20/%20Oritera」发布;Bytex 是他参加腾讯云黑客松时使用的战队名。该系统在那次比赛中完成%2054/54,是该届唯一%20AK%20队伍。我参考它,主要不是因为这个成绩,而是因为它把复杂任务压缩成了一个很清楚的模型:已知 origin,设定 goal,中间路径未知,就持续补事实、拆下一步。Cairn%20源码与作者复盘在这里。

Cairn%20的核心很克制:黑板上只有%20Fact、Intent%20和%20Hint%20三种对象。Fact%20是已确认的发现;Intent%20是下一步准备探索的方向;Hint%20是人随时可以写入的判断。Worker%20围绕同一张黑板读取信息、探索、再写回事实,不靠“信息收集%20Agent%20/%20利用%20Agent%20/%20报告%20Agent”这样的固定角色切分工作。

这个思路刚好适合%20Android%20逆向。一个%20APK%20的校验路径可能横跨%20Java、资源、JNI%20和运行时内存,起手时谁也画不准流程图。先把确定的东西记下来,再盯着当前缺口往下挖,比硬规定一套“第一步、第二步、第三步”更实际。

不过,原版%20Cairn%20面向通用问题求解与渗透测试,Android%20逆向还有自己的硬约束:JADX%20和%20JNI%20映射要可回查,动态分析要能连设备,候选%20Flag%20不能只凭一段自然语言结论完成。因此%20r0re%20保留了%20Cairn%20的黑板、serve%20+%20dispatch 双进程和任务图思想,同时增加了%20Android%20CTF%20专用%20prompt、产物索引、runtime%20event、Frida/ADB%20工作路径,以及最终候选的验证门控。

所以%20r0re%20不是要另起一套理论。Cairn%20给了问题推进的骨架;我做的部分,是把它接到%20APK、so、设备和验证脚本上。

三、做%20r0re%20时,我盯着的几个问题

脚本适合把已经知道的算法批量化;大模型比较擅长在陌生二进制里找方向。两者之间还差一个能长期保存上下文、允许人中途纠偏的工作台。r0re%20就是补这个空档。

它把一个项目拆为三类可追踪对象:

  • Fact

已经观察到的事实,例如%20Manifest%20入口、某个%20JNI%20映射、一次%20Frida%20hook%20的参数或已导出的%20dex;
*%20Intent

下一步可独立完成的工作,例如“定位 RegisterNatives 的注册表”或“复现%20AES%20解密链”;
*%20Hint

人为补充的约束和方向,例如“先检查 libmobisec.so”“候选必须在真机输入通过后才可结束”。

Worker%20不能只写一段长篇结论就宣布完成。它需要说明依据哪些%20Fact,产出了什么文件,哪些结论已经由真实工具输出验证。候选答案随后进入单独的%20verification/finalize%20流程;验证失败,候选会保留失败痕迹而不是悄悄覆盖。

这套设计直接针对%20Android%20CTF%20里最常见的三个坑:

  • 可见字符串、Base64、Morse%20表只是线索,不等于%20Flag;
  • 静态分析卡在壳或自修改%20native%20时,需要切到设备动态分析;
  • 一个“看起来正确”的答案必须能通过原%20App、重建算法或独立验证器中的至少一种验证。

四、源码拆解:从创建项目到验证收口

源码仓库:github.com/fyrlove/r0re。r0re%20使用%20Java%2017、Spring%20Boot%20和%20SQLite;服务进程负责保存黑板、项目和完成状态,dispatcher%20是独立进程,按项目状态调用容器中的%20Worker。

这个拆分不是为了把架构画得复杂,而是为了把“模型调用”与“分析现场”分离:模型超时、容器退出、机器休眠,都不应该让已经得到的证据消失。

WebUI%20/%20HTTP%20API
 %20 %20 %20 │
 %20 %20 %20 ▼
SQLite%20黑板:projects%20/%20facts%20/%20intents%20/%20hints%20/%20runtime_events
 %20 %20 %20 │
 %20 %20 %20 ▼
Dispatcher:bootstrap%20→%20reason%20→%20explore%20→%20verify%20→%20finalize
 %20 %20 %20 │
 %20 %20 %20 ▼
Docker%20Worker:jadx%20/%20apktool%20/%20readelf%20/%20Frida%20/%20ADB%20/%20自定义%20solver
 %20 %20 %20 │
 %20 %20 %20 └──%20analysis%20产物、trace、报告回写到项目目录
4.1%20先把状态落下来

最核心的%20schema%20在 src/main/resources/schema.sql。projects 保存项目生命周期;facts、intents、intent_sources 和 hints 组成可回放的事实图;runtime_events 记录每个%20Worker%20在什么阶段、用了什么工具、生成了什么产物;completion_candidates 与 verification_intents 则把“模型声称完成”拆成待验证的候选和独立验证任务。

这个数据结构解决了一个很现实的问题:同样一句“已找到%20Flag”,在逆向里含金量完全不同。它可能是%20strings%20里看见的值,也可能是对 memcmp 参数的%20hook%20结果,还可能是本地脚本正向/逆向都跑通的输入。r0re%20用 verified、kind 和%20artifact%20路径把这些状态分开保存。dead_end 也会作为事实保留——例如“静态%20dump%20没有解密代码页”,后续任务就不会再沿同一条路无休止搜索。

从源码看,所有黑板写入集中在 src/main/java/com/fyr/r0re/server/R0reService.java,而 DispatcherRunner 只通过%20HTTP%20API%20获取项目、认领任务和回传结果,不直接碰%20SQLite。

这样%20WebUI、dispatcher%20和%20Worker%20看到的是同一个事实来源,不会出现“页面里已经加了%20Hint,容器还在拿旧上下文”的两套状态。

4.2%20调度循环怎么转起来

src/main/java/com/fyr/r0re/dispatcher/DispatcherRunner.java 是整个系统的心脏。每次%20tick%20会回收结束任务、读取活跃项目、刷新运行态、处理容器清理,然后为可执行项目补位。任务只有三种:

  • bootstrap

项目刚创建时做首轮侦察,建立%20APK%20的基本认识;
*%20reason

读取当前事实图,判断目标是否达成,或生成下一批更小的%20Intent;
*%20explore

认领一个%20Intent,真正调用工具完成分析,并把结果写回为新的%20Fact。

把它写成伪代码,大致就是:

项目创建%20→%20bootstrap
新%20Fact%20/%20新%20Hint%20/%20有空闲任务槽位%20→%20reason
reason%20产生%20Intent%20→%20Worker%20认领%20→%20explore
explore%20产出%20Fact%20或%20dead_end%20→%20再次%20reason
候选答案%20→%20verifier%20→%20finalize

这里最重要的是 reason 不负责“替人写一份计划书”,而是只提出当前证据支持、且可以验收的下一步。例如已经发现 System.loadLibrary,下一条%20Intent%20应该是“枚举%20JNI%20声明并定位%20RegisterNatives”,而不是“继续深入分析”。一条%20Intent%20只要能产生一个明确事实,就值得被调度;无法完成的宽泛描述则不应进入队列。

在 dispatch.kimi.swarm.yaml 中可以设置同一项目的%20Worker%20并发。它们不是固定分工的角色,而是围绕不同%20Intent%20并行探索:一个追%20Java%20入口,一个追%20native%20注册,一个准备动态%20hook。谁先把可验证证据写回黑板,其他路径就能利用它或被及时否定。这也是%20r0re%20从%20Cairn%20继承得最完整的一点。

4.3%20Worker%20如何拿到上下文,又如何留下现场

ExternalCliWorkerAdapter.java 负责将一次任务变成容器内的%20CLI%20执行。它创建/复用项目容器,渲染%20prompt,启动外部%20Agent%20CLI,持续上报心跳,解析结构化结果,并将%20trace、workspace%20snapshot、脚本和报告同步回 output/live-analysis/<project_id>/。因此网页上看到的不是“模型最后一段回答”,而是可以回到本地查看的%20JADX%20输出、Frida%20脚本、内存%20dump%20或%20solver。

runtime_events 是这层的旁路记录。每条事件可以带 phase、step、工具名、当前进度和%20artifact。遇到长时间没有更新的%20native%20任务时,使用者能判断它是在跑 jadx、卡在%20CLI,还是已经开始调设备,而不用盯着一段没有意义的“分析中”。这也是文章里%20AliCrackme\_4%20能从静态分析转向%20Frida%20的基础:方向变化会留下事件、Hint%20和产物,不靠口头交接。

4.4%20Android%20题的%20Prompt%20为什么要拆开

PromptFactory.java 并非简单地把整张黑板塞给模型。它会为 reason 和 explore 导出更紧凑的事实图,附带最近产物、可检索目录和%20JNI%20摘要;再根据%20Intent%20选择模板。Android%20CTF%20的模板位于 src/main/resources/prompts/android-ctf/,native%20相关任务进一步分为:

  • explore_native_jni.md

Java/Smali%20native%20声明、System.loadLibrary 与 RegisterNatives 对应关系;
*%20explore_native_constants.md

密钥、查表、比较缓冲区等常量恢复;
*%20explore_native_smc.md

mprotect、解密、按需执行等自修改代码路线;
*%20explore_native_packed.md

壳、动态%20dex、Godot%20或资源包的恢复;
*%20explore_native_verify.md

把候选输入回放到已恢复的校验链中。

这类拆分看似只是%20prompt%20工程,实际是在给分析过程设“可交付物”。比如%20JNI%20任务要交出方法映射,常量恢复要交出偏移/字节和提取方法,验证任务要交出可以运行的命令或脚本。只说“发现了%20AES”不能让项目结束;只有证明密钥、密文与最终比较之间的调用关系,才是能复用的事实。

4.5%20Flag%20为什么不能直接宣布完成

CTF%20题最危险的假阳性,是模型用流畅的文字把猜测说成答案。r0re%20因而没有接受原始 complete 后立即结束项目的做法。CompletionPolicyService.java 会对逆向项目检查四类东西:是否存在%20verifier、是否写入验证时间、分析目录是否有真实产物、最近运行事件中是否有%20verifier%20或%20solver%20的通过证据。任一项缺失,候选只能停留在%20pending,而不能%20finalize。

这个约束对应前面的测试记录:AliCrackme\_2、AliCrackme\_4%20的首个候选被%20App%20否决后,错误没有被“覆盖”;它们留在事件和事实图里,新的%20Hint%20再驱动下一轮%20reason。对使用者来说,多出来的不是一个漂亮状态,而是一张可以解释“为什么这次答案可信”的收据。

4.6%20跑一题样本

本地准备好%20Java%2017、Docker%20和%20Android%20分析镜像后,可以这样启动:

./scripts/r0re-preflight.sh
./start-r0re.sh%20--no-bridge%20--config=dispatch.kimi.swarm.yaml