unidbg入门案例-hookdemo.add

  1. package com.dudu.ndk;
  2. import com.github.unidbg.AndroidEmulator;
  3. import com.github.unidbg.Emulator;
  4. import com.github.unidbg.Module;
  5. import com.github.unidbg.Symbol;
  6. import com.github.unidbg.arm.HookStatus;
  7. import com.github.unidbg.arm.context.Arm32RegisterContext;
  8. import com.github.unidbg.arm.context.RegisterContext;
  9. import com.github.unidbg.debugger.DebuggerType;
  10. import com.github.unidbg.hook.HookContext;
  11. import com.github.unidbg.hook.ReplaceCallback;
  12. import com.github.unidbg.hook.hookzz.Dobby;
  13. import com.github.unidbg.hook.hookzz.HookEntryInfo;
  14. import com.github.unidbg.hook.hookzz.HookZz;
  15. import com.github.unidbg.hook.hookzz.IHookZz;
  16. import com.github.unidbg.hook.hookzz.InstrumentCallback;
  17. import com.github.unidbg.hook.hookzz.WrapCallback;
  18. import com.github.unidbg.hook.xhook.IxHook;
  19. import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
  20. import com.github.unidbg.linux.android.AndroidResolver;
  21. import com.github.unidbg.linux.android.XHookImpl;
  22. import com.github.unidbg.linux.android.dvm.DalvikModule;
  23. import com.github.unidbg.linux.android.dvm.DvmClass;
  24. import com.github.unidbg.linux.android.dvm.VM;
  25. import com.github.unidbg.linux.android.dvm.array.ByteArray;
  26. import com.github.unidbg.memory.Memory;
  27. import com.github.unidbg.utils.Inspector;
  28. import com.sun.jna.Pointer;
  29. import java.io.File;
  30. import java.io.IOException;
  31. public class NativeHelper {
  32. private final AndroidEmulator emulator;
  33. private final VM vm;
  34. private final Module module;
  35. private final DvmClass NativeHelper;
  36. private final boolean logging;
  37. NativeHelper(boolean logging) {
  38. this.logging = logging;
  39. emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.dudu.app").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
  40. final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
  41. memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
  42. vm = emulator.createDalvikVM(); // 创建Android虚拟机
  43. vm.setVerbose(logging); // 设置是否打印Jni调用细节
  44. DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/dudu/ndk/libdudu.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
  45. // dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数 //我们调用的是静态注册的函数,不需要callJNI_OnLoad,这里我们就屏蔽了
  46. module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
  47. NativeHelper = vm.resolveClass("com/dudu/ndk/NativeHelper");
  48. }
  49. void destroy() throws IOException {
  50. emulator.close();
  51. if (logging) {
  52. System.out.println("destroy");
  53. }
  54. }
  55. public static void main(String[] args) throws Exception {
  56. NativeHelper test = new NativeHelper(true);
  57. int data = test.callFunc();
  58. System.out.println("retval: 0x" + Integer.toHexString(data));
  59. test.destroy();
  60. }
  61. int callFunc() {
  62. int retval = NativeHelper.callStaticJniMethodInt(emulator, "add(III)I", 0x100, 0x200, 0x300); // 执行Jni方法
  63. return retval;
  64. }}
  • 注意事项
  1. NativeHelper构造方法中,emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(“com.dudu.app”).build(); “for32Bit/for64Bit”根据我们调用的so是32位还是64位决定的;“setProcessName”设置进程名,这里我们一般设置为我们apk的包名即可;
  2. vm.loadLibrary(new File(“unidbg-android/src/test/java/com/dudu/ndk/libdudu.so”), false); // 加载libdudu.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数,这里我们loadLibrary加载so,new File里面传入的是我们在工程里面的路径
  3. dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数 //我们调用的是静态注册的函数,不需要callJNI_OnLoad;
  4. NativeHelper = vm.resolveClass(“com/dudu/ndk/NativeHelper”); 这里我们是加载的目标所在类。得到的类是DvmClass
  5. Integer是int的包装类,下面有一个到十六进制的方法toHexString

    unidbg入门案例-hookdemo.md5

    ```java package com.dudu.ndk;

import com.github.unidbg.AndroidEmulator; import com.github.unidbg.Emulator; import com.github.unidbg.Module; import com.github.unidbg.Symbol; import com.github.unidbg.arm.HookStatus; import com.github.unidbg.arm.context.Arm32RegisterContext; import com.github.unidbg.arm.context.RegisterContext; import com.github.unidbg.debugger.DebuggerType; import com.github.unidbg.hook.HookContext; import com.github.unidbg.hook.ReplaceCallback; import com.github.unidbg.hook.hookzz.Dobby; import com.github.unidbg.hook.hookzz.HookEntryInfo; import com.github.unidbg.hook.hookzz.HookZz; import com.github.unidbg.hook.hookzz.IHookZz; import com.github.unidbg.hook.hookzz.InstrumentCallback; import com.github.unidbg.hook.hookzz.WrapCallback; import com.github.unidbg.hook.xhook.IxHook; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.XHookImpl; import com.github.unidbg.linux.android.dvm.*; import com.github.unidbg.linux.android.dvm.array.ByteArray; import com.github.unidbg.memory.Memory; import com.github.unidbg.utils.Inspector; import com.sun.jna.Pointer;

import java.io.File; import java.io.IOException; public class NativeHelper {

  1. private final AndroidEmulator emulator;
  2. private final VM vm;
  3. private final Module module;
  4. private final DvmClass NativeHelper;
  5. private final boolean logging;
  6. NativeHelper(boolean logging) {
  7. this.logging = logging;
  8. emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.dudu.app").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
  9. final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
  10. memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析
  11. vm = emulator.createDalvikVM(); // 创建Android虚拟机
  12. vm.setJni(new AbstractJni() {});
  13. vm.setVerbose(logging); // 设置是否打印Jni调用细节
  14. DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libdudu.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
  15. // dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数 //我们调用的是静态注册的函数,不需要callJNI_OnLoad,这里我们就屏蔽了
  16. module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
  17. NativeHelper = vm.resolveClass("com/dudu/ndk/NativeHelper");
  18. }
  19. void destroy() throws IOException {
  20. emulator.close();
  21. if (logging) {
  22. System.out.println("destroy");
  23. }
  24. }
  25. public static void main(String[] args) throws Exception {
  26. NativeHelper test = new NativeHelper(true);
  27. String data = test.callFunc();
  28. System.out.println("retval: " + data);
  29. test.destroy();
  30. }
  31. String callFunc() {
  32. StringObject retval = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm,"dudu666")); // 执行Jni方法
  33. return retval.getValue();
  34. }

}

  1. 使用刚刚一样的方法,调用静态注册的md5,结果报错了。我们可以看见,提示叫我们“vm.setJni(jni)”接管jni的,并且给出了偏移地址“0x1103”,不妨我们去so里面看看。![image.png](https://cdn.nlark.com/yuque/0/2022/png/22752752/1644480178980-f643ee7b-f58f-4c64-949a-58c19780fb2b.png#clientId=u7add128e-49af-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=515&id=u593e5106&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1030&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=257583&status=done&style=none&taskId=u064e8b98-ad4f-4b9f-aa76-07f8634c4c6&title=&width=960)![image.png](https://cdn.nlark.com/yuque/0/2022/png/22752752/1644480418521-78929600-c969-40b2-8459-a8e411209252.png#clientId=u7add128e-49af-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=284&id=u98ae2c38&margin=%5Bobject%20Object%5D&name=image.png&originHeight=568&originWidth=1894&originalType=binary&ratio=1&rotation=0&showTitle=false&size=111807&status=done&style=none&taskId=ub86f69a7-b1ff-4655-83b9-1f4752fbfcb&title=&width=947)<br />结果发现是CallObjectMethodV这个方法出现了问题;<br />根据提示,我们接管jni,自己去实现CallObjectMethodV方法<br />vm.setJni(new AbstractJni() {});<br />这里,我们根据传入的是jni对象,AbstractJni这个类是对jni接口实现,里面包括了CallObjectMethodV方法的实现;
  2. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22752752/1644480790879-ee90fe41-ab62-4670-b7d6-1b2c0205fe3d.png#clientId=u7add128e-49af-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=515&id=udf28c76a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1029&originWidth=1919&originalType=binary&ratio=1&rotation=0&showTitle=false&size=381219&status=done&style=none&taskId=u4e82100f-e60c-4274-b903-5efeeb670be&title=&width=959.5)![image.png](https://cdn.nlark.com/yuque/0/2022/png/22752752/1644483888629-93aa6832-3c6a-4494-80f8-ace2af159d60.png#clientId=u7add128e-49af-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=515&id=u5b8a4983&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1030&originWidth=1920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=308553&status=done&style=none&taskId=ub42627d2-4cf3-4f2a-9501-44aa2393959&title=&width=960)
  3. ```java
  4. String callFunc() {
  5. StringObject retval = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm,"dudu666")); // 执行Jni方法
  6. return retval.getValue();
  7. }

虽然说,我们callStaticJniMethodObject第三个参数传入可以直接是string,但是,其本质是因为unidbg给我们封装了一遍,针对string的封装,正如图片所示,new StringObject(vm,”dudu666”)image.png

处理so调用系统Java类

在调用md5方法时,我们发现,在调用CallObjectMethodV的调用出现了错误,我们采取了自己接管了jni,传入了一个继承了jni接口的实现类AbstractJni来解决的;在此,我们给出了另外一些解决方法;

  1. 针对性的实现某一方法-该案例中需要针对性复写str2getBytes方法

    1. vm.setJni(new AbstractJni() {
    2. @Override
    3. public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
    4. System.out.println("callObjectMethod signature- > "+signature);
    5. return super.callObjectMethod(vm, dvmObject, signature, varArg);
    6. //callObjectMethod的本质就是调用callObjectMethodV
    7. }
    8. @Override
    9. public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    10. if(signature.equals("java/lang/String->getBytes(Ljava/lang/String;)[B")){
    11. System.out.println("callObjectMethod 被调用...");
    12. String data = (String) dvmObject.getValue();
    13. byte[] strbyte = data.getBytes();
    14. return new ByteArray(vm,strbyte);
    15. }
    16. return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    17. }
  • 这里我们需要知道,callObjectMethod的本质就是调用callObjectMethodV,因此应直接复写callObjectMethodV方法;
  • dvmObject就是我们传入的string对象;
  • 我们返回给unidbg需要返回符合他的变量类型,因此我们需要对java的byte数组包装成能被unnidbg使用的ByteArray类型;
  1. 通过vm.setjni(this)覆写父类AbstractJni方法image.png

    1. @Override
    2. public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    3. System.out.println("方法复写被调用...");
    4. return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    5. }

    unidbg入门案例-hookdemo.encode

    image.png

  • 老样子,我们这里调用一下我们的目标函数,然后我们的encode函数是一个动态注册的函数,因此我们需要dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
  • libxiaojianbang.so load dependency libxiaojianbangA.so failed 这里说明我们的这个 libxiaojianbang.so依赖于libxiaojianbangA.so;
  • [libxiaojianbang.so]symbol ElfSymbol[name=_Z7bssFuncv, type=function, size=0] is missing relocationAddr=RW@0x40005f78[libxiaojianbang.so]0x5f78, offset=0x0 提示说_Z7bssFuncv缺少了;
  • 新版的unidbg不用我们手动添加依赖的的so,旧版的需要我们手动添加一下;image.png
  • 旧版的_Z7bssFuncv方法缺失的话,我们可以用Frida去查看导入符号表查看;

    unidbg入门案例-hookdemo.通过符号调用函数1

    根据我们上面的学习指导StringObject callStaticJniMethodObject(Emulator<?> emulator, String method, Object… args)方法传入对应的参数便可以调用so里面的静态注册或者动态注册的函数了;其本质是findNativeFunction的作用
  1. UnidbgPointer findNativeFunction(Emulator<?> emulator, String method) {
  2. UnidbgPointer fnPtr = nativesMap.get(method);//获取动态注册的函数指针
  3. int index = method.indexOf('(');
  4. if (fnPtr == null && index == -1) {
  5. index = method.length();
  6. }
  7. String symbolName = "Java_" + getClassName().replace("_", "_1").replace('/', '_').replace("$", "_00024") + "_" + method.substring(0, index).replace("_", "_1");
  8. if (fnPtr == null) {
  9. for (Module module : emulator.getMemory().getLoadedModules()) {
  10. Symbol symbol = module.findSymbolByName(symbolName, false);
  11. if (symbol != null) {
  12. fnPtr = (UnidbgPointer) symbol.createPointer(emulator);
  13. break;
  14. }
  15. }
  16. }

通过代码我们可以发现第一步是取得在so中的symbolName,然后通过module.findSymbolByName获得函数指针Module.emulateFunction(emulator, fnPtr.peer, list.toArray())进行调用
对此,我们在Symbol类下的call也可以实现对函数的调用;

  1. void callFunc() {
  2. //StringObject retval = NativeHelper.callStaticJniMethodObject(emulator, "encode()Ljava/lang/String;"); // 执行Jni方法
  3. //return retval.getValue();
  4. Symbol symbol = module.findSymbolByName("Java_dudu_ndk_NativeHelper_add");
  5. Number call = symbol.call(emulator, vm.getJNIEnv(), vm.addLocalObject(NativeHelper), 8800, 200, 300);
  6. System.out.println(call.intValue());
  7. Symbol symbol1 = module.findSymbolByName("_Z7_strcatP7_JNIEnvP7_jclass");
  8. Number call1 = symbol1.call(emulator, vm.getJNIEnv(), vm.addLocalObject(NativeHelper));
  9. System.out.println(vm.getObject(call1.intValue()).getValue());
  10. }
  11. //NativeHelper = vm.resolveClass("com/dudu/ndk/NativeHelper");获取jclass

代码框的是新版的代码,老版本的unidbg代码有些许不同;

  • symbol.call传入的参数第一个是模拟器,后面全是参数,传入的第一个一定是JNIEnv,参数由于Java层是静态方法,第二个是jclass,需要以对象的形式传入vm.addLocalObject
  • 旧版本的unidbg调用symbol.call返回的是Number[]数组,我们取用Number[0];新版本的直接返回的Number,已经是第0各成员,无需自己取数组下标了
  • 得到Java的int数据后,根据这个数据去内存中捞对象或者数据 ```java 比如返回的是Java对象 vm.getObject(retval) //相当于vm.addLocalObject的反过程 比如返回的是地址 emulator.getMemory().pointer(retval).getByteArray(…, …); 比如返回的是长度 emulator.getMemory().getByteArray(…, retval);
  1. - 获取到Symbol以后,最好先判断下是否为null
  2. <a name="Yo6y2"></a>
  3. #### unidbg入门案例-hookdemo.通过符号调用函数2
  4. ```java
  5. void callFunc() {
  6. Number number = module.callFunction(emulator, "Java_com_dudu_ndk_NativeHelper_add", vm.getJNIEnv(), vm.addLocalObject(NativeHelper), 500, 600, 700);
  7. Number number1 = module.callFunction(emulator, "_Z7_strcatP7_JNIEnvP7_jclass", vm.getJNIEnv(), vm.addLocalObject(NativeHelper));
  8. System.out.println(number);
  9. System.out.println(vm.getObject( number1.intValue()).getValue());
  10. }
  1. 模拟callStaticJniMethodObject的功能
    1.1 找到符号对应地址
    1.2 包装传入的参数
    1.3 Java类型的传递
    1.3.1 JNIEnv*的获取
    vm.getJNIEnv()
    1.3.2 jclass/jobject的构建
    DvmClass xxx = vm.resolveClass(…)
    DvmObject xxxx = xxx.newObject(null)
    vm.addLocalObject(xxxx) //Java类或者对象以引用的方式传入
    传入native的Java参数,除了八个基本类型外,都必须vm.addLocalObject添加到局部引用中去
    1.3.3 其他Java类型的传递 StringObject、ByteArray等

    unidbg入门案例-hookdemo.通过偏移调用函数

    MD5为例

    1. void MD5Init(MD5_CTX *context);
    2. void MD5Update(MD5_CTX *context,unsigned char *input,unsigned int inputlen);
    3. void MD5Final(MD5_CTX *context,unsigned char digest[16]);

    image.png
    上面是c语言md5加密的三部曲,以及在ida中所对应的偏移;我们使用unidbg构建一个md5也应当遵循其规则;

    1. void callFunc() {
    2. //MD5Init
    3. UnidbgPointer MD5Ctx = emulator.getMemory().malloc(200, false).getPointer();
    4. module.callFunction(emulator,0x2230,MD5Ctx);
    5. //MD5Update
    6. UnidbgPointer plainText = emulator.getMemory().malloc(200, false).getPointer();
    7. byte[] buffer = "a123456".getBytes();
    8. plainText.write(buffer);
    9. module.callFunction(emulator,0x22A0,MD5Ctx,buffer,buffer.length);
    10. //MD5Final
    11. UnidbgPointer cipherTxt = emulator.getMemory().malloc(200, false).getPointer();
    12. module.callFunction(emulator,0x3A78,MD5Ctx,cipherTxt);
    13. byte[] byteArray = cipherTxt.getByteArray(0, 16);
    14. Inspector.inspect(byteArray, "MD5Result");
    15. }
  2. emulator.getMemory().malloc(200, false)模拟器申请一个200字节(大小可随意,只要能装下我们的目标参数)的内存大小,getPointer方法获取该内存地址的起始地址;

  3. module.callFunction 是一个重载函数,针对偏移方法的时候,第一个参数传入模拟器,第二个传入偏移地址,至于及地址,unidbg自己会帮我计算的,第三个就是传入一个可变长度的参数;
  4. Inspector.inspect格式化打印结果;

unidbg入门案例-总结

image.png