Skip to content

🏛️ 架构总览

ZjDroid 本质是一个 Xposed 模块:它把自己的代码注入到目标 App 进程里,然后在进程内部完成脱壳、内存 dump、API 监控等工作。理解它,先理解它"如何进入目标进程"、"如何被驱动"、"如何执行"这三件事。

🧩 一句话架构

ZjDroid 借 Xposed 之力寄生进目标进程,靠一条 adb 广播被外部驱动,在进程内部用 JNI 直读 Dalvik 内存完成脱壳与分析,结果通过 logcat 回传。

🔌 第一件事:如何进入目标进程

ZjDroid 依赖 Xposed 框架。Xposed 会替换 Android 的 app_process,让每个新启动的 App 进程在执行自身代码前,先加载所有已启用的 Xposed 模块。ZjDroid 的入口是 ReverseXposedModule,它实现 IXposedHookLoadPackage 接口:

java
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
    // 跳过系统应用;只处理"首个 Application"且非 ZjDroid 自身的包
    if (lpparam.isFirstApplication && !ZJDROID_PACKAGENAME.equals(lpparam.packageName)) {
        Logger.PACKAGENAME = lpparam.packageName;
        PackageMetaInfo pminfo = PackageMetaInfo.fromXposed(lpparam);
        ModuleContext.getInstance().initModuleContext(pminfo);   // ① 初始化上下文
        DexFileInfoCollecter.getInstance().start();              // ② 开始收集 DEX 信息
        LuaScriptInvoker.getInstance().start();                  // ③ 准备 Lua 注入能力
        ApiMonitorHookManager.getInstance().startMonitor();      // ④ 启动 API 监控
    }
}

为什么只处理 isFirstApplication

一个 App 可能有多进程。ZjDroid 只在"首个 Application"注入,避免重复初始化。同时排除系统应用(FLAG_SYSTEM)和 ZjDroid 自身,避免污染。

注入与初始化时序

关键细节:ModuleContext 并不立即注册广播接收器,而是先 hook 目标 Application.onCreate(),等目标进程的 Application 真正实例化后,才用它的 Context 注册 CommandBroadcastReceiver。因为注册广播需要一个有效的 Context,而模块加载那一刻还没有。

📡 第二件事:如何被外部驱动

ZjDroid 没有 UI,所有主动操作都靠一条广播触发:

bash
adb shell am broadcast -a com.zjdroid.invoke \
  --ei target <目标PID> \
  --es cmd '{"action":"backsmali","dexpath":"..."}'

广播是设备全局的——所有被注入的进程都会收到。ZjDroid 用 target PID 路由,只让匹配的进程执行:

为什么切到新线程执行

onReceive 跑在主线程,超过 10 秒会 ANR。而 backsmali 脱壳耗时远超 10 秒,所以 CommandBroadcastReceivernew Thread(...).start() 异步执行。这也意味着广播命令立即返回,真正结果要靠 logcat 异步查看。

指令到 Handler 的映射由 CommandHandlerParser 完成——一个典型的命令模式分发器,详见 指令处理层

⚙️ 第三件事:如何执行核心工作

ZjDroid 的杀手锏是绕过文件层加固,直接从内存拿 DEX。它通过 NativeFunction(JNI 桥)调用 libdvmnative.so,直读 Dalvik 虚拟机内部的数据结构:

java
public class NativeFunction implements MemoryReader {
    static { System.loadLibrary("dvmnative"); }

    // 依据 DexFile 的 mCookie(指向 Dalvik 内部结构的指针)导出 DEX
    public static native ByteBuffer dumpDexFileByCookie(int cookie, int version);
    public static native ByteBuffer dumpMemory(int start, int length);
    private static native DexFileHeadersPointer getHeaderItemPtr(int cookie, int version);
}

脱壳全链路(详见 脱壳原理):

🗺️ 分层与代码映射

代表类作用
入口层modReverseXposedModuleXposed 挂载点、广播接收
指令层requestCommandHandlerParser解析 JSON、分发命令
采集层collecterDexFileInfoCollecterDEX 信息、内存/堆 dump、Lua
反汇编层smaliMemoryBackSmali内存 DEX → smali → 重组
Hook 层hookXposeHookHelperImpl对 Xposed hook API 的封装
监控层apimonitorApiMonitorHookManager20 类敏感 API 监控
工具层utilNativeFunctionJNI 桥、反射、日志

📚 延伸阅读

基于 MIT 协议发布 · 本文档仅用于安全研究与教学目的