|
|
发表于
2025-9-20 09:38:17
|
显示全部楼层
来自 福建
剩下内容放这里
完整代码:
- java
- package com.example.ndkdemo;
- import com.github.unidbg.*;
- import com.github.unidbg.arm.backend.Backend;
- import com.github.unidbg.arm.backend.CodeHook;
- import com.github.unidbg.arm.backend.UnHook;
- import com.github.unidbg.arm.backend.Unicorn2Factory;
- import com.github.unidbg.arm.context.Arm64RegisterContext;
- import com.github.unidbg.debugger.BreakPointCallback;
- import com.github.unidbg.debugger.Debugger;
- import com.github.unidbg.debugger.FunctionCallListener;
- import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
- import com.github.unidbg.linux.android.AndroidResolver;
- import com.github.unidbg.linux.android.dvm.DalvikModule;
- import com.github.unidbg.linux.android.dvm.DvmClass;
- import com.github.unidbg.linux.android.dvm.VM;
- import com.github.unidbg.memory.Memory;
- import com.github.unidbg.pointer.UnidbgPointer;
- import unicorn.Arm64Const;
- import unicorn.ArmConst;
- import java.io.*;
- public class MainActivity {
- private final AndroidEmulator emulator;
- private final VM vm;
- private final Module module;
- private final DvmClass security;
- private final boolean logging;
- private long instructionCount = 0; // 用于指令计数
- MainActivity(boolean logging) {
- this.logging = logging;
- // 1. 创建模拟器实例
- emulator = AndroidEmulatorBuilder.for64Bit()
- .setProcessName("com.example.ndkdemo")
- .addBackendFactory(new Unicorn2Factory(true))
- .build();
- // 2. 设置内存和系统库解析
- final Memory memory = emulator.getMemory();
- memory.setLibraryResolver(new AndroidResolver(23));
- // 3. 创建Dalvik虚拟机并加载SO文件
- vm = emulator.createDalvikVM();
- vm.setVerbose(logging);
- DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/zj/wuaipojie/util/libndkdemo.so"), false);
- dm.callJNI_OnLoad(emulator);
- module = dm.getModule();
- // 4. 获取DVM类
- security = vm.resolveClass("com/example/ndkdemo/MainActivity");
- // 5. 设置指令计数钩子 (可选)
- emulator.getBackend().hook_add_new(new CodeHook() {
- @Override
- public void hook(Backend backend, long address, int size, Object user) {
- instructionCount++;
- }
- @Override
- public void onAttach(UnHook unHook) {}
- @Override
- public void detach() {}
- }, module.base, module.base + module.size, null);
- // 6.设置函数追踪与x0寄存器检查
- attachTraceAndInspectX0(module.base + 0x15F8);
- }
- /**
- * 【模块化功能】: 附加一个函数调用追踪器 (traceFunctionCall)。
- * 此功能会监听所有的函数调用,并以树状结构打印出调用关系。
- * @param traceStream 日志输出流
- */
- public void attachFunctionCallTracer(final PrintStream traceStream) {
- Debugger debugger = emulator.attach();
- System.out.println("函数调用关系追踪器已附加,结果将输出到日志文件。");
- debugger.traceFunctionCall(null, new FunctionCallListener() {
- private int depth = 0;
- private String getPrefix(int currentDepth) {
- if (currentDepth <= 0) {
- return "";
- }
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < currentDepth - 1; i++) {
- sb.append("│ ");
- }
- sb.append("├─ ");
- return sb.toString();
- }
- @Override
- public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) {
- String prefix = getPrefix(depth + 1);
- String details = emulator.getUnwinder().formatAddressDetails(functionAddress);
- traceStream.printf("%sCALL -> %s%n", prefix, details);
- depth++;
- }
- @Override
- public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) {
- depth--;
- String prefix = getPrefix(depth + 1);
- String details = emulator.getUnwinder().formatAddressDetails(functionAddress);
- Backend backend = emulator.getBackend();
- Number retVal = emulator.is64Bit() ? backend.reg_read(Arm64Const.UC_ARM64_REG_X0) : backend.reg_read(ArmConst.UC_ARM_REG_R0);
- long retValLong = retVal.longValue();
- // 尝试将返回值作为指针解析
- String retValFormatted = String.format("0x%x", retValLong);
- UnidbgPointer pointer = UnidbgPointer.pointer(emulator, retValLong);
- if (pointer != null) {
- String cstring = safeReadCString(pointer);
- // 如果是一个可打印的字符串,则附加到日志中
- if (isPrintable(cstring)) {
- retValFormatted += String.format(" -> "%s"", cstring);
- }
- }
- traceStream.printf("%sRET <- %s, ret=%s%n", prefix, details, retValFormatted);
- }
- });
- }
- /**
- * 在指定地址设置断点,实现对一个函数调用的追踪,并在函数返回后检查 x0 寄存器的内容。
- *
- * @param callAddress 函数调用指令(例如 BL, B)的绝对地址
- */
- private void attachTraceAndInspectX0(long callAddress) {
- final TraceHook[] traceHook = new TraceHook[1];
- emulator.attach().addBreakPoint(callAddress, (emu, address) -> {
- traceHook[0] = emu.traceCode(module.base, module.base + module.size);
- return true;
- });
- long returnAddress = callAddress + 4;
- emulator.attach().addBreakPoint(returnAddress, new BreakPointCallback() {
- @Override
- public boolean onHit(Emulator<?> emu, long address) {
- if (traceHook[0] != null) {
- traceHook[0].stopTrace();
- System.out.println();
- }
- Arm64RegisterContext ctx = emu.getContext();
- long x0 = ctx.getXLong(0);
- System.out.printf("[+] 检查地址: 0x%x, x0寄存器值: 0x%x\n", address, x0);
- UnidbgPointer pointer = UnidbgPointer.pointer(emu, x0);
- if (pointer == null) {
- System.out.println("[-] x0的值不是一个有效的指针或指向未映射的内存");
- return true;
- }
- String cstring = safeReadCString(pointer);
- if (cstring != null && isPrintable(cstring)) {
- System.out.println("[+] x0指向的字符串: " + cstring);
- } else {
- System.out.println("[-] x0指向的内容不是一个可打印的字符串");
- }
- int dumpSize = 256;
- byte[] data = pointer.getByteArray(0, dumpSize);
- System.out.println("[+] x0指向内存的HexDump (前" + dumpSize + "字节):");
- System.out.println(prettyHexDump(data, x0));
- System.out.println("--- x0寄存器检查结束 ---\n");
- return true;
- }
- });
- }
- private static String safeReadCString(UnidbgPointer p) {
- try {
- return p.getString(0);
- } catch (Exception e) {
- return null;
- }
- }
- private static boolean isPrintable(String s) {
- if (s == null || s.isEmpty()) {
- return false;
- }
- int printableChars = 0;
- for (int i = 0; i < s.length(); i++) {
- char c = s.charAt(i);
- if ((c >= 32 && c <= 126) || Character.isWhitespace(c)) {
- printableChars++;
- }
- }
- return s.length() >= 2 && (double) printableChars / s.length() > 0.8;
- }
- private static String prettyHexDump(byte[] data, long baseAddr) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < data.length; i += 16) {
- sb.append(String.format("%016x: ", baseAddr + i));
- StringBuilder hexPart = new StringBuilder();
- StringBuilder asciiPart = new StringBuilder();
- for (int j = 0; j < 16; j++) {
- if (i + j < data.length) {
- byte b = data[i + j];
- hexPart.append(String.format("%02x ", b));
- char c = (b >= 32 && b <= 126) ? (char) b : '.';
- asciiPart.append(c);
- } else {
- hexPart.append(" ");
- }
- if (j == 7) {
- hexPart.append(" ");
- }
- }
- sb.append(hexPart).append(" |").append(asciiPart).append("|\n");
- }
- return sb.toString();
- }
- private void crack() {
- emulator.traceCode();
- boolean result = security.callStaticJniMethodBoolean(emulator, "check", "1234567");
- System.out.println("函数调用结束,返回结果: " + result);
- }
- void destroy() {
- try {
- emulator.close();
- if (logging) {
- System.out.println("模拟器已成功关闭");
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- public static void main(String[] args) {
- MainActivity test = new MainActivity(false);
- String traceFile = "unidbg-android/src/test/resources/traceFunctions.txt";
- try (PrintStream traceStream = new PrintStream(new FileOutputStream(traceFile), true)) {
- // 在调用 crack 之前附加追踪器,持久化日志
- test.attachFunctionCallTracer(traceStream);
- // 执行会触发函数调用的模拟代码
- test.crack();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- // 确保模拟器资源被释放
- test.destroy();
- System.out.println("总共执行ARM指令数: " + test.instructionCount);
- }
- }
- }
复制代码
二. Mcp 配置
`MCP概念:`
MCP(Model Context Protocol,模型上下文协议)是什么?
**MCP是一种标准化协议,旨在为人工智能模型(如大语言模型)与外部工具、数据源之间的交互提供统一接口。(为 AI 装上了手脚)**
MCP是AI智能体与外部工具的"USB接口",定义了AI模型与外部工具(如API、数据库、文档编辑器等)的交互标准,开发者无需为每个工具单独开发适配代码。
MCP协议旨在实现大型语言模型(LLM)与外部数据源和工具之间的无缝集成,通过提供标准化的接口,使AI应用程序能够安全、可控地与本地或远程资源进行交互。

项目地址
要求:
1.IDA Pro 8.3 以上,最好是 9
2.python3.11 或更高版本,使用`idapyswitch`切换到Python 版本
3.支持 Mcp 的客户端,这里以 Cursor 为例
`环境配置:`
- pip install git+https://github.com/mrexodia/ida-pro-mcp
复制代码
Ps: 如果你现有的 python 版本低于十一,可以单独下载一个版本然后安装到 ida 的目录下

接着打开 `idapyswitch` 切换 3.11 的版本

然后 cmd 窗口打开新安装的 python.exe 再运行 pip 命令
- D:\IDA\IDA9.0\python311\python.exe -m pip install --upgrade git+https://github.com/mrexodia/ida-pro-mcp
复制代码
接下来,再运行命令:
这步如果报错,可以打开 11 版本 python 目录下的 Scripts 文件夹,找到 ida-pro-mcp.exe
Cmd 窗口运行:
- 这里需要你自己拖动exe文件到cmd窗口
- D:\IDA\IDA9.0\python311\Scripts\ida-pro-mcp.exe --install
复制代码
最后运行:
- ida-pro-mcp --config
- 报错则运行下面这个:
- D:\IDA\IDA9.0\python311\Scripts\ida-pro-mcp.exe --config
复制代码

接下来打开 cursor,发现 ida_pro_mcp 已经亮绿灯了,说明 mcp 服务正常了。如果没有你就复制 ida-pro-mcp --config 输出的那串 json 到 mcp.json


最后在 IDA 中将 server 端开启即可。

三.Unidbg补环境
`定义:`
“Unidbg 补环境”是指在使用 Unidbg 模拟执行 Android 原生 (Native) 代码时,为了使程序能够正常运行并获得与真实设备一致的结果,对 Unidbg 模拟器**环境中的缺失或不完善部分进行补充和调整**的过程
一. 补 Jni 环境
#一、 JNI 补环境的核心概念
JNI (Java Native Interface) 补环境是 Unidbg 应用中的核心环节。当 Unidbg 模拟执行的原生库(. So 文件)尝试通过 JNI 调用 Java 层的代码时,Unidbg 必须能够提供这些 Java 方法的模拟实现。如果 Unidbg 缺少某个方法的实现,或者默认实现不符合预期,程序就会抛出 `UnsupportedOperationException` 异常,导致模拟中断。因此,JNI 补环境的目的就是拦截这些 JNI 调用,并提供一个符合目标 SO 文件逻辑预期的返回值或行为,从而“欺骗”SO 文件,使其认为自己运行在真实的 Android 环境中。(本质就是缺啥补啥)
#二、 Java 层函数补全:继承 `AbstractJni`
这是最主要的 Java 层补环境方式。通过创建一个继承自`com.github.unidbg.linux.android.dvm.AbstractJni` 的自定义类,并重写其关键方法,可以模拟任意 JNI 调用。
**具体步骤:**
1. **extends AbstractJni**:
- java
- public class ChallengeTenUnidbg extends AbstractJni {
- }
-
复制代码
2. **关联 Jni 类与虚拟机 (VM)** :
- 在创建了 `DalvikVM` 实例后,将 AbstractJni的实例设置给它。
- **强烈建议开启详细日志** (`vm.setVerbose(true)`),这会在控制台打印出所有 JNI 调用的详细信息(包括方法签名),是定位哪个方法需要补全的关键。
- java
- VM vm = emulator.createDalvikVM();
- vm.setJni(this);
- vm.setVerbose(true); // 开启详细日志
-
复制代码
3. **重写关键方法以拦截 JNI 调用**:
- 根据 SO 文件调用的 Java 方法类型(静态/实例、返回类型),重写 `AbstractJni` 中对应的方法。
- **方法命名规律**:`call[Static]XXXMethodV`,其中 `Static` 表示静态方法,`XXX` 表示返回类型(如 `Object`, `Boolean`, `Int`, `Void` 等)。
- **核心参数**:
- `signature`: 完整的方法签名字符串,如 `"com/example/MyClass->getAppContext()Landroid/content/Context;"`,用于 `switch` 或 `if` 判断具体是哪个方法被调用。
- `vaList`: 包含了调用时传递的所有参数。
| JNI 原型 | `AbstractJni` 覆写方法 | 典型用途 |
| --------------------------------- | ---------------------------------- | ----------------------------- |
| `jobject CallStaticObjectMethodV` | `callStaticObjectMethodV` | 返回 `Context`, `Application` 等 |
| `jint CallIntMethodV` | `callIntMethodV` | 返回版本号、随机数 |
| `jobject NewObjectV` | `newObjectV` | 构造 `ZipFile`, `Cipher` |
| `void Set<Object>Field` | `setObjectField` 等 | SO 写回 Java 字段 |
| `jint RegisterNatives` | `invokeRegisterNatives` | 动态注册补环境 |
| `jint NewGlobalRef` | `newGlobalRef` / `deleteGlobalRef` | 线程间对象共享 |
4. **方法实现示例**:
- **模拟静态方法 (`callStaticObjectMethodV`)** :
- java
- @Override
- public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
- switch (signature) {
- case "com/example/MyClass->getAppContext()Landroid/content/Context;":
- // 返回一个模拟的 Context 对象
- return vm.resolveClass("android/content/Context").newObject(null);
- // 可以添加更多 case 来处理其他需要补全的 Java 方法
- default:
- // 对于未处理的方法,务必调用父类的默认实现
- return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
- }
- }
-
复制代码
- **模拟构造方法 (`newObjectV`)** :
- java
- @Override
- public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
- switch (signature) {
- case "java/util/HashMap-><init>()V": {
- return ProxyDvmObject.createObject(vm, new HashMap<>());
- }
- case "com/zj/wuaipojie/ui/ChallengeTen$UserInfo-><init>(Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Lcom/zj/wuaipojie/ui/ChallengeTen$AccountStatus;Ljava/util/Map;)V": {
- System.out.println("【补环境 Level 3】拦截到 UserInfo 构造方法");
- Map<String, DvmObject<?>> userInfoData = new HashMap<>();
- userInfoData.put("status", vaList.getObjectArg(4));
- userInfoData.put("properties", vaList.getObjectArg(5));
- return dvmClass.newObject(userInfoData);
- }
- }
- return super.newObjectV(vm, dvmClass, signature, vaList);
- }
-
复制代码
- **模拟字段访问 (`Get/Set<Type>Field`)** :
- java
- @Override
- public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
- // 匹配枚举类的 PREMIUM 静态字段
- if ("com/zj/wuaipojie/ui/ChallengeTen$AccountStatus->PREMIUM:Lcom/zj/wuaipojie/ui/ChallengeTen$AccountStatus;".equals(signature)) {
- System.out.println("【补环境】拦截到获取 AccountStatus.PREMIUM 静态字段");
- // 创建一个枚举实例(用字符串"PREMIUM"作为其值,方便后续name()方法返回正确结果)
- DvmObject<?> premium = dvmClass.newObject("PREMIUM");
- return premium;
- }
- return super.getStaticObjectField(vm, dvmClass, signature);
- }
- @Override
- public void setIntField(BaseVM vm, DvmObject<?> dvmObject, String signature, int value) {
- // signature的格式是:com/example/User->age:I
- if ("com/example/User->age:I".equals(signature)) {
- System.out.println("SO 正在设置 User 对象的 age 字段,值为: " + value);
- // 你可以在这里记录值,或者什么都不做
- return; // 注意set方法是void返回
- }
- super.setIntField(vm, dvmObject, signature, value);
- }
复制代码 |
|