gaoyu06/c2j-native-deobfuscator

GitHub: gaoyu06/c2j-native-deobfuscator

针对 native-obfuscator 及其衍生工具的 Java 原生混淆还原器,通过动态 JVMTI 追踪或静态 Ghidra 反编译将 JNI 调用重建为可读的 JVM 字节码。

Stars: 1 | Forks: 0

**英语** | [中文](README.zh-CN.md) # c2j-native-deobfuscator 将 **经过 JNI-native 混淆的 JAR** 逆向工程还原为可读的 Java 字节码。目标是针对 [`native-obfuscator`](https://github.com/radioegor146/native-obfuscator) 及其衍生工具(例如 j2cc)—— 即任何将 JVM 字节码 转译为 C++,然后从打包的 `.dll` / `.so` 中通过 JNI 重新调用 Java 的工具。 两种互补的还原路径: | 路径 | 输入 | 方法 | |---|---|---| | **动态** | 混淆的 jar + 可运行命令 | 附加 JVMTI agent,观察 JNI 调用流,将其提升回 JVM 字节码 | | **静态** | 混淆的 jar + Ghidra | 在 native 块中定位 JNI 方法表,反编译每个函数,将伪 C 代码提升为 JVM 字节码 | 任何一种路径都会生成一个干净的 `out.jar`,其原生方法现在拥有 真实的字节码主体,并且 loader / native 块的条目已被剥离。 许可证:**GPLv3**。 ## 工作原理 ### 动态路径 - **JVMTI agent** (`native/`, C++)。通过 `-agentpath:` 加载。订阅 `NativeMethodBind`、`MethodEntry`、`MethodExit`、`Exception`、 `ExceptionCatch` JVMTI 事件。 - **JNI 函数表替换**。在 `VMInit` 和每次 `ThreadStart` 时, agent 使用一个副本覆盖 `JNIEnv->functions` 指针,该副本中大约 80 个条目被重定向到日志包装器。每个包装器 委托给原始函数,并将调用记录为 `trace.jsonl` 中的一行 JSON。可变参数 `Call*Method` 系列 根据每个类的 jmethodID 描述符缓存解码它们的 `va_list`。 - **符号表传播** (`jvm/trace-to-bytecode/`)。提升器 遍历跟踪记录,根据产生对象的内容(`FindClass` → jclass、`GetMethodID` → jmethodID 等)对每个 jobject 引用进行分类,并 发出具有完全解析的 owner / name / desc 的相应 JVM 操作码。 - **SSA 风格的合成局部变量**。方法在 语句间重复使用的每个 jobject 都会获得一个合成局部变量槽。提升器在 生成 JNI 调用之后发出 `DUP + ASTORE `,并在每个 重用站点发出 `ALOAD `,因此还原的字节码无需重新派生值即可保持真实的引用 一致性。 - **操作数栈平衡器**。跟踪活动栈并插入 `POP` / `CHECKCAST` / `ACONST_NULL` 修正,以便发出的 序列能在 ASM `COMPUTE_FRAMES` 下通过验证。 ### 静态路径 - **反汇编级别的表发现** (`py/binary_introspect/`, `capstone`)。遍历 native 块的可执行节,定位 每个 `call qword ptr [reg + 0x6B8]`(`RegisterNatives` JNI vtable 槽),对前面的指令进行反向扫描,查找 PC 相对的 `lea`,其目标位于 `.text` 中(即在 栈上构建的 `JNINativeMethod[]` 中的函数指针)加上最近的 `mov , imm`(即表大小)。 - **Ghidra 反编译器** (`ghidra/scripts/DumpFromManifest.java`, Ghidra Headless)。从 `manifest.json` 读取 `(class, method, fnAddr)` 三元组,并在每个地址上运行 Ghidra 的 p-code 反编译器, 生成一个单一的 `ghidra-dump.json`,其中每个方法包含一个伪 C 主体。 - **tree-sitter-c 解析** (`py/ast_matcher/`, `tree-sitter-c`)。解析 伪 C 代码,然后使用特性标志驱动器遍历 AST,该 驱动器能识别 `env->FnName(args)` 调用(从 Ghidra 的 `(**(code **)(*reg + 0xN))(...)` 形式重写而来)、JNI 辅助模式和异常检查保护。 - **抛出原因推断**。原生混淆器系列的混淆器在每个将要进行 Java 调用之前发出 `"Cannot invoke X.Y.Z(args)"` 字符串, 用于运行时异常消息。提升器将它们提取为 调用提示,并在符号跟踪无法通过混淆器辅助函数解析 jmethodID 时将其用作后备(owner、name、args-desc)。 - **Profile 自动检测**。活动的混淆器收集策略 (按类 vs 共享调度)、抛出原因正则表达式以及 if-guard 跳过规则均来自通过根据内置检测器扫描 二进制文件来选择的 :class:`Profile`。 ### 共享部分 - **JSON 管道**。每个阶段的输入 + 输出都是 `schemas/` 下的带版本控制的 JSON 产物。 - **ASM** (`org.objectweb.asm`) 驱动所有类文件生成。 `ClassWriter.COMPUTE_FRAMES` 是验证关卡;触发栈失衡的 方法会获得一个哨兵桩主体,jar 仍然可以正常发布。 ## 何时使用哪种路径 这两种路径针对相同的输入,但在覆盖率和准确性之间进行了权衡: | | 动态 | 静态 | |---|---|---| | **最适用** | 二进制文件被打包 / VM 保护 / 具有反调试功能 —— JVMTI agent 位于 Java 侧,因此原生保护层无关紧要。 | 二进制文件未受保护(例如,直接的 native-obfuscator + zig c++ 输出)。Ghidra 可以直接反编译每个 `fnAddr`。 | | **要求** | 能运行混淆类且可执行的命令行 (`java -jar ...`)。 | 已安装 Ghidra 11.x。 | | **覆盖率** | 仅限于你实际运行的分支。从未被调用的方法将被完全遗漏。 | 通过 `RegisterNatives` 注册的每个方法,无论是否曾调用过。 | | **准确性** | 高 —— 每个发出的操作码都对应于 JVM 观察到的真实 JNI 调用。 | 尽力而为 —— 提升器对反编译的 C 代码进行模式匹配,并在无法维持栈平衡时回退到桩代码。 | | **速度** | 受限于目标程序执行其代码路径所需的时间 + agent 的开销。 | 受限于 Ghidra 的自动分析(对于约 1 MB 的块需要几分钟)。 | ## 局限性 - **静态路径的正确性是尽力而为的。** 提升器对 Ghidra 的伪 C 进行模式匹配;具有 Ghidra 未干净地结构化的控制流的方法会产生栈失衡的字节码,`class-rebuilder` 会 静默地将其降级为桩代码。该类的其余部分仍然正常发布。 - **动态路径只能看到目标实际执行的分支。** 如果只运行了 `if` 分支的 if/else 语句,将被还原为仿佛 `else` 不存在。循环体显示一次迭代跟踪; 具体值会被烘焙为 LDC,除非被 agent 标记为动态。 - **纯原生控制流 / 算术对两种路径均不可见。** 当混淆器将不需要 JVM 往返的计算(字符数组操作、整数数学运算)完全翻译为 C++ 时, 没有 JNI 调用发生,因此动态跟踪和 JNI 调用模式 匹配都什么也看不到。这些区域被还原的方法主体最终是空的或被桩代码代替。 - **AOT 转换的逻辑无法恢复。** 高级混淆器 检测到不需要 JVM 合作的 Java 代码(例如 对称加密、字节数组转换、通过 POSIX/Win32 的文件 I/O),并将其作为直接的原生代码而不是 JNI 回调 C++ 发出。该输出根本没有 JNI 签名 —— 这两种 路径都会为这些方法生成桩代码。唯一的恢复方法是手工阅读 反汇编代码。 - **`` 中解密表里的字符串字面量仍保持** 未解析状态。每个混淆类将其字符串常量包装在 在类加载时解码的 XOR/旋转表中。提升器逐字保留了 索引访问(`Foo.a(0, 17)`)而不是替换值;运行一次清理后 jar 的 `` 并快照该 表已在路线图中(请参阅 `docs/ROADMAP.md`)。 ## 截图 图片位于 [`screenshots/showcase/`](screenshots/showcase/)。完整 目录请参阅 [`screenshots/README.md`](screenshots/README.md)。 **反编译器视图 (IntelliJ / CFR)** | 之前 | 之后 | |---|---| | ![](https://raw.githubusercontent.com/gaoyu06/c2j-native-deobfuscator/main/screenshots/showcase/decompiler-before.png) | ![](https://raw.githubusercontent.com/gaoyu06/c2j-native-deobfuscator/main/screenshots/showcase/decompiler-after.png) | | 原生桩代码 + loader 类仍然存在 | 主体已重构,loader 已剥离 | **`javap -c -p` — 单个方法** | 之前 | 之后 | |---|---| | ![](https://raw.githubusercontent.com/gaoyu06/c2j-native-deobfuscator/main/screenshots/showcase/javap-before.png) | ![](https://raw.githubusercontent.com/gaoyu06/c2j-native-deobfuscator/main/screenshots/showcase/javap-after.png) | | 仅包含 `native` 标志,无 Code 属性 | 真实的 Code 属性 + 操作码流 | **端到端管道与 Ghidra 伪 C** | 还原管道 | Ghidra 伪 C | |---|---| | ![](https://raw.githubusercontent.com/gaoyu06/c2j-native-deobfuscator/main/screenshots/showcase/pipeline.png) | ![](https://raw.githubusercontent.com/gaoyu06/c2j-native-deobfuscator/main/screenshots/showcase/ghidra-pseudoc.png) | | 动态路径每阶段的输出 | 静态路径上提升器的实际输入 | ## 快速开始 ### 一次性构建 ``` # JVM 模块 cd jvm && ./gradlew installDist # Python 工作区 cd py && uv sync --all-packages # Native agent (仅 dynamic path 需要) cd native && JDK_HOME="$JAVA_HOME" bash build.sh ``` ### 动态还原(当 jar 可以在你的环境中运行时优先使用) ``` python -m j2c_dumper_cli.main recover \ path/to/obfuscated.jar \ -o path/to/clean.jar \ --run-cmd "java -jar path/to/obfuscated.jar" ``` 此过程链包含: 1. `parse-jar` → `classes.json` 2. `inspect-binary` (自动从 jar 中提取原生块) 3. `merge-manifest` → `manifest.json` 4. `dynamic-trace` 使用 JVMTI agent 运行目标 → `trace.jsonl` 5. `trace-to-bc` 提升为 `recovered/*.json` 6. `rebuild` 生成剥离了 loader 的输出 jar ### 静态还原(当你无法运行 jar 时 —— 需要 Ghidra) ``` # 1. 如上所述解析 jar + 内省二进制文件 (无需 --run-cmd) python -m j2c_dumper_cli.main parse-jar in.jar -o classes.json python -m j2c_dumper_cli.main inspect-binary natives.bin -o binary.json python -m j2c_dumper_cli.main merge-manifest classes.json binary.json -o manifest.json # 2. 针对 native blob 运行 Ghidra headless /support/analyzeHeadless.bat proj \ -import natives.bin \ -scriptPath /ghidra/scripts \ -postScript DumpFromManifest.java manifest.json ghidra-dump.json # 3. 将 pseudo-C 提升为 bytecode + 重建 python -m ast_matcher.cli ghidra-dump.json --manifest manifest.json -o recovered/ python -m j2c_dumper_cli.main rebuild --input in.jar --recovered recovered/ \ --manifest manifest.json -o out.jar ``` ### 按阶段执行 每个阶段都在 `j2c-dumper` 下有自己的子命令;请参阅 `python -m j2c_dumper_cli.main --help` 获取完整列表。 ## 通用性 该项目附带了两个可自动检测的混淆器 **Profile**: - `native_obfuscator` — radioegor146/native-obfuscator + 兼容的衍生工具 - `j2cc` — me.x150.j2cc (单一共享的 `initClass` 调度) - `generic` — 当没有 profile 匹配时的回退方案;仅使用纯 JNI 规范知识 自定义变体可以在不触及主流程的情况下插入新的 profile。 请参阅 [`docs/adding-obfuscator-profile.md`](docs/adding-obfuscator-profile.md)。 静态路径的提升器将每个推理/匹配步骤公开为 特性标志(抛出原因提示解析、ExceptionCheck 保护跳过、 符号表跟踪、查找表解析等)。当某个标志在特定二进制文件上 行为异常时,请禁用该标志: ``` python -m ast_matcher.cli ghidra-dump.json -o recovered/ \ --disable use_throw_reason_invoke_hints \ --disable skip_native_exception_guards python -m ast_matcher.cli --list-flags ``` ## 仓库布局 ``` ├── jvm/ Kotlin/ASM modules (Gradle multi-project) │ ├── jar-parser/ input.jar → classes.json │ ├── trace-to-bytecode/ manifest + trace.jsonl → recovered/*.json │ ├── class-rebuilder/ input.jar + recovered/ → output.jar │ └── common/ shared schema types ├── native/ C++ JVMTI agent (zig c++ build) ├── ghidra/scripts/ Ghidra headless scripts (Java) ├── py/ Python modules (uv workspace) │ ├── jar_parser/ — │ ├── binary_introspect/ .dll / .so / natives.bin → binary.json │ │ ├── arch/ per-arch / ABI implementations │ │ ├── jni_tables.py RegisterNatives table discovery │ │ ├── profile.py obfuscator-variant profiles │ │ └── stub_recovery.py synthesize stub bodies for unrecovered methods │ ├── manifest_merge/ classes.json + binary.json → manifest.json │ ├── ast_matcher/ pseudo-C → JVM bytecode │ │ └── lifter/ driver + per-feature submodules │ ├── j2c_dumper_cli/ top-level CLI orchestrator │ └── snippet_importer/ (optional) native-obfuscator cppsnippets ingestor ├── docs/ ARCHITECTURE.md, ROADMAP.md, profile guide, … ├── schemas/ JSON Schema for every artifact └── tests/ e2e fixtures and pipeline tests ``` ## 文档 - [ARCHITECTURE.md](docs/ARCHITECTURE.md) — 模块边界,管道, 产物模式,扩展点 - [ROADMAP.md](docs/ROADMAP.md) — 已知局限和计划工作 - [adding-obfuscator-profile.md](docs/adding-obfuscator-profile.md) — 如何 注册新的混淆器变体 - [static-reverse-approach.md](docs/static-reverse-approach.md) — 基于 Ghidra 路径的 设计说明 ## 许可证 在 **GPL v3** 下发布。请参阅 [LICENSE](LICENSE)。
标签:Android安全, C++, DAST, DNS 反向解析, DNS 解析, GHAS, Ghidra, jar反编译, Java安全, Java逆向, JNI分析, JS文件枚举, JVMTI, native-obfuscator, 二进制分析, 云安全监控, 云安全运维, 云资产清单, 代码混淆与还原, 动态二进制插桩, 反混淆, 字节码恢复, 安全工具开发, 恶意软件分析, 数据擦除, 桌面安全, 逆向工具, 逆向工程, 静态分析