unidbg入门案例-hookdemo.add
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.DalvikModule;import com.github.unidbg.linux.android.dvm.DvmClass;import com.github.unidbg.linux.android.dvm.VM;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 {private final AndroidEmulator emulator;private final VM vm;private final Module module;private final DvmClass NativeHelper;private final boolean logging;NativeHelper(boolean logging) {this.logging = logging;emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.dudu.app").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析vm = emulator.createDalvikVM(); // 创建Android虚拟机vm.setVerbose(logging); // 设置是否打印Jni调用细节DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/dudu/ndk/libdudu.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数// dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数 //我们调用的是静态注册的函数,不需要callJNI_OnLoad,这里我们就屏蔽了module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块NativeHelper = vm.resolveClass("com/dudu/ndk/NativeHelper");}void destroy() throws IOException {emulator.close();if (logging) {System.out.println("destroy");}}public static void main(String[] args) throws Exception {NativeHelper test = new NativeHelper(true);int data = test.callFunc();System.out.println("retval: 0x" + Integer.toHexString(data));test.destroy();}int callFunc() {int retval = NativeHelper.callStaticJniMethodInt(emulator, "add(III)I", 0x100, 0x200, 0x300); // 执行Jni方法return retval;}}
- 注意事项
- NativeHelper构造方法中,emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(“com.dudu.app”).build(); “for32Bit/for64Bit”根据我们调用的so是32位还是64位决定的;“setProcessName”设置进程名,这里我们一般设置为我们apk的包名即可;
- vm.loadLibrary(new File(“unidbg-android/src/test/java/com/dudu/ndk/libdudu.so”), false); // 加载libdudu.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数,这里我们loadLibrary加载so,new File里面传入的是我们在工程里面的路径
- dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数 //我们调用的是静态注册的函数,不需要callJNI_OnLoad;
- NativeHelper = vm.resolveClass(“com/dudu/ndk/NativeHelper”); 这里我们是加载的目标所在类。得到的类是DvmClass
- 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 {
private final AndroidEmulator emulator;private final VM vm;private final Module module;private final DvmClass NativeHelper;private final boolean logging;NativeHelper(boolean logging) {this.logging = logging;emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.dudu.app").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析vm = emulator.createDalvikVM(); // 创建Android虚拟机vm.setJni(new AbstractJni() {});vm.setVerbose(logging); // 设置是否打印Jni调用细节DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xiaojianbang/ndk/libdudu.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数// dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数 //我们调用的是静态注册的函数,不需要callJNI_OnLoad,这里我们就屏蔽了module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块NativeHelper = vm.resolveClass("com/dudu/ndk/NativeHelper");}void destroy() throws IOException {emulator.close();if (logging) {System.out.println("destroy");}}public static void main(String[] args) throws Exception {NativeHelper test = new NativeHelper(true);String data = test.callFunc();System.out.println("retval: " + data);test.destroy();}String callFunc() {StringObject retval = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm,"dudu666")); // 执行Jni方法return retval.getValue();}
}
使用刚刚一样的方法,调用静态注册的md5,结果报错了。我们可以看见,提示叫我们“vm.setJni(jni)”接管jni的,并且给出了偏移地址“0x1103”,不妨我们去so里面看看。<br />结果发现是CallObjectMethodV这个方法出现了问题;<br />根据提示,我们接管jni,自己去实现CallObjectMethodV方法<br />vm.setJni(new AbstractJni() {});<br />这里,我们根据传入的是jni对象,AbstractJni这个类是对jni接口实现,里面包括了CallObjectMethodV方法的实现;```javaString callFunc() {StringObject retval = NativeHelper.callStaticJniMethodObject(emulator, "md5(Ljava/lang/String;)Ljava/lang/String;", new StringObject(vm,"dudu666")); // 执行Jni方法return retval.getValue();}
虽然说,我们callStaticJniMethodObject第三个参数传入可以直接是string,但是,其本质是因为unidbg给我们封装了一遍,针对string的封装,正如图片所示,new StringObject(vm,”dudu666”)
处理so调用系统Java类
在调用md5方法时,我们发现,在调用CallObjectMethodV的调用出现了错误,我们采取了自己接管了jni,传入了一个继承了jni接口的实现类AbstractJni来解决的;在此,我们给出了另外一些解决方法;
针对性的实现某一方法-该案例中需要针对性复写str2getBytes方法
vm.setJni(new AbstractJni() {@Overridepublic DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {System.out.println("callObjectMethod signature- > "+signature);return super.callObjectMethod(vm, dvmObject, signature, varArg);//callObjectMethod的本质就是调用callObjectMethodV}@Overridepublic DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {if(signature.equals("java/lang/String->getBytes(Ljava/lang/String;)[B")){System.out.println("callObjectMethod 被调用...");String data = (String) dvmObject.getValue();byte[] strbyte = data.getBytes();return new ByteArray(vm,strbyte);}return super.callObjectMethodV(vm, dvmObject, signature, vaList);}
- 这里我们需要知道,callObjectMethod的本质就是调用callObjectMethodV,因此应直接复写callObjectMethodV方法;
- dvmObject就是我们传入的string对象;
- 我们返回给unidbg需要返回符合他的变量类型,因此我们需要对java的byte数组包装成能被unnidbg使用的ByteArray类型;
通过vm.setjni(this)覆写父类AbstractJni方法

@Overridepublic DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {System.out.println("方法复写被调用...");return super.callObjectMethodV(vm, dvmObject, signature, vaList);}
unidbg入门案例-hookdemo.encode

- 老样子,我们这里调用一下我们的目标函数,然后我们的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,旧版的需要我们手动添加一下;

- 旧版的_Z7bssFuncv方法缺失的话,我们可以用Frida去查看导入符号表查看;
unidbg入门案例-hookdemo.通过符号调用函数1
根据我们上面的学习指导StringObject callStaticJniMethodObject(Emulator<?> emulator, String method, Object… args)方法传入对应的参数便可以调用so里面的静态注册或者动态注册的函数了;其本质是findNativeFunction的作用
UnidbgPointer findNativeFunction(Emulator<?> emulator, String method) {UnidbgPointer fnPtr = nativesMap.get(method);//获取动态注册的函数指针int index = method.indexOf('(');if (fnPtr == null && index == -1) {index = method.length();}String symbolName = "Java_" + getClassName().replace("_", "_1").replace('/', '_').replace("$", "_00024") + "_" + method.substring(0, index).replace("_", "_1");if (fnPtr == null) {for (Module module : emulator.getMemory().getLoadedModules()) {Symbol symbol = module.findSymbolByName(symbolName, false);if (symbol != null) {fnPtr = (UnidbgPointer) symbol.createPointer(emulator);break;}}}
通过代码我们可以发现第一步是取得在so中的symbolName,然后通过module.findSymbolByName获得函数指针Module.emulateFunction(emulator, fnPtr.peer, list.toArray())进行调用
对此,我们在Symbol类下的call也可以实现对函数的调用;
void callFunc() {//StringObject retval = NativeHelper.callStaticJniMethodObject(emulator, "encode()Ljava/lang/String;"); // 执行Jni方法//return retval.getValue();Symbol symbol = module.findSymbolByName("Java_dudu_ndk_NativeHelper_add");Number call = symbol.call(emulator, vm.getJNIEnv(), vm.addLocalObject(NativeHelper), 8800, 200, 300);System.out.println(call.intValue());Symbol symbol1 = module.findSymbolByName("_Z7_strcatP7_JNIEnvP7_jclass");Number call1 = symbol1.call(emulator, vm.getJNIEnv(), vm.addLocalObject(NativeHelper));System.out.println(vm.getObject(call1.intValue()).getValue());}//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);
- 获取到Symbol以后,最好先判断下是否为null<a name="Yo6y2"></a>#### unidbg入门案例-hookdemo.通过符号调用函数2```javavoid callFunc() {Number number = module.callFunction(emulator, "Java_com_dudu_ndk_NativeHelper_add", vm.getJNIEnv(), vm.addLocalObject(NativeHelper), 500, 600, 700);Number number1 = module.callFunction(emulator, "_Z7_strcatP7_JNIEnvP7_jclass", vm.getJNIEnv(), vm.addLocalObject(NativeHelper));System.out.println(number);System.out.println(vm.getObject( number1.intValue()).getValue());}
模拟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为例
void MD5Init(MD5_CTX *context);void MD5Update(MD5_CTX *context,unsigned char *input,unsigned int inputlen);void MD5Final(MD5_CTX *context,unsigned char digest[16]);

上面是c语言md5加密的三部曲,以及在ida中所对应的偏移;我们使用unidbg构建一个md5也应当遵循其规则;void callFunc() {//MD5InitUnidbgPointer MD5Ctx = emulator.getMemory().malloc(200, false).getPointer();module.callFunction(emulator,0x2230,MD5Ctx);//MD5UpdateUnidbgPointer plainText = emulator.getMemory().malloc(200, false).getPointer();byte[] buffer = "a123456".getBytes();plainText.write(buffer);module.callFunction(emulator,0x22A0,MD5Ctx,buffer,buffer.length);//MD5FinalUnidbgPointer cipherTxt = emulator.getMemory().malloc(200, false).getPointer();module.callFunction(emulator,0x3A78,MD5Ctx,cipherTxt);byte[] byteArray = cipherTxt.getByteArray(0, 16);Inspector.inspect(byteArray, "MD5Result");}
emulator.getMemory().malloc(200, false)模拟器申请一个200字节(大小可随意,只要能装下我们的目标参数)的内存大小,getPointer方法获取该内存地址的起始地址;
- module.callFunction 是一个重载函数,针对偏移方法的时候,第一个参数传入模拟器,第二个传入偏移地址,至于及地址,unidbg自己会帮我计算的,第三个就是传入一个可变长度的参数;
- Inspector.inspect格式化打印结果;
unidbg入门案例-总结

