args = new ArrayList<>(10); // 各种基本参数格式兼容 // 参数1:JNIEnv *env args.add(vm.getJNIEnv()); // 参数2:jobject或jclass 用不到直接填0即可 // 创建 jobject, 如果没用到的话可以不写 // cNative = vm.resolveClass("com/xxx/xxx"); // DvmObject<?> cnative = cNative.newObject(null); // args.add(cnative.hashCode()); args.add(0); // 参数3 字符串对象 String input = "abcdef"; args.add(vm.addLocalObject(new StringObject(vm, input))); // 参数4 bytes 数组 String input = "abcdef"; byte[] input_bytes = input.getBytes(StandardCharsets.UTF_8); ByteArray input_byte_array = new ByteArray(vm,input_bytes); args.add(vm.addLocalObject(input_byte_array)); // 参数5 bool // false 填 0,true 填 1 args.add(1); // unidbg 主动调用函数 // 第二个参数是函数偏移量(thumb 记得+1) // 第三个参数是参数列表 Number number = module.callFunction(emulator,0x10618, args.toArray());
// unicorn trace(贼好用!!!堪比 ida trace!!!) String traceFile = "trace.txt"; PrintStream traceStream = null; try{ traceStream = new PrintStream(new FileOutputStream(traceFile), true); } catch (FileNotFoundException e) { e.printStackTrace(); } // 核心 trace 开启代码,也可以自己指定函数地址和偏移量 emulator.traceCode(module.base,module.base+module.size).setRedirect(traceStream); // 获取最终返回值,同时运行过程中的汇编代码和寄存器值会写入到文件中 return vm.getObject(number.intValue()).getValue().toString();
看看 trace log,建议搭配 010 editor 使用, 更香
示例:代码分析
入口点
第一步: 补环境 跟踪 TTEncrypt 函数,注释写的很清楚了,不做过多分析。基本套路都是这个样子。
第二步: HOOK 相关的函数 跟踪 ttEncrypt,可知代码 hook 了 ssencrypt 和 ssencrypted_size 两个函数。
第三步: 添加调试及主动调用
第四步: 销毁环境。跟踪 destroy
运行结果。如下图所示,运行成功就代表成功
这个时候按 c 继续,可以看到 hook 的结果以及JNI调用细节。
单步调试,ida_server 的 Debug方式相对简单,对于 unidbg 的强大之一在于它的单步调试-- Console Debugger,例子是以抖音作为例子的,还是很不错的。注释都写的比较清楚了。unidbg单步调试做的很棒,这个弥补了 frida 调试能力比较弱的缺点。
此示例就是对如下目录中的 so 进行操作。
unidbg-android/src/test/resources/example_binaries/libttEncrypt.so
查找 so 中的 sbox0、sbox1 导出符号,并打印其内存数据。
Hook一些函数,使用多个框架。
调用 so 中的 ttEncrypt 静态注册函数。
运行结果如下图所示:
打包成 jar 文件:https://www.jianshu.com/p/59e08e48ac20
打包成 jar 包
调用so 的有一个可执行程序呀,总不能让别人机器上也装个IDEA来陪你玩吧?
File ---> Project Structure … 然后选择 Artifacts,点加号 Add ---> jar ---> From modules with dependencies… 然后如下配置, 别忘了勾上 Include tests
OK了,Build → Build Artifacts 编译成功之后就在 unidbg-0.9.0/out/artifacts/unidbg_parent_jar 目录下生成了一堆依赖jar包和unidbg-parent.jar 我们把要载入的 fenfei/libnative-lib.so 放到和 unidbg-parent.jar同级目录
跑一下,成功输出,手工。 (努力的字都不会打了)
fenfeideMacBook-Pro:unidbg_parent_jar fenfei$ java -jar unidbg-parent.jar call stringFromJNI rc = Hello from C++
unidbg 调用so层函数 ( 普通的so方法、jni_onload调用、jni函数调用 )
1、选择执行引擎:如果明确使用了以下代码,那么unidbg使用dynarmic引擎,否则默认使用unicorn引擎!
static { DynarmicLoader.useDynarmic(); }
2、创建虚拟机/模拟器,并执行虚拟机的类型是art还是dailvik:
AndroidARMEmulator emulator= new AndroidARMEmulator("com.sun.jna",null,null); final Memory memory = emulator.getMemory(); VM vm = emulator.createDalvikVM(); vm.setVerbose(true);//这里如果是true,后续调用jni_onload的时候就能打印日志
3、指定SDK的版本,这里用23版本:
LibraryResolver resolver = new AndroidResolver(23); memory.setLibraryResolver(resolver);
4、开始加载so库了:
Module unicorn08module=emulator.loadLibrary(new File("D:\\xxxx\\unidbg-master\\unidbg-android\\src\\test\\java\\com\\unicorncourse08\\unicorn08.so"));
5、调用so层的导出函数:这两个都是导出函数,直接用符号名就行了;
Number result=unicorn08module.callFunction(emulator,"_Z3addii",1,2)[0];//导出函数直接用符号名就行了 System.out.println("_Z3addii result:"+result.intValue()); //_Z7add_sixiiiiii result=unicorn08module.callFunction(emulator,"_Z7add_sixiiiiii",1,2,3,4,5,6)[0]; System.out.println("_Z7add_sixiiiiii result:"+result.intValue());
这个是打印的结果:
_Z3addii result:3 _Z7add_sixiiiiii result:21
6、这两个都是对字符串做操作的,so层仅仅求了传入字符串的长度:
MemoryBlock block1=memory.malloc(10,true); UnidbgPointer str1_ptr=block1.getPointer(); str1_ptr.write("hello".getBytes()); String content=ARM.readCString(emulator.getBackend(),str1_ptr.peer); System.out.println("_Z15getstringlengthPKc:"+str1_ptr.toString()+"---"+content); result=unicorn08module.callFunction(emulator,"_Z15getstringlengthPKc",new PointerNumber(str1_ptr))[0]; Number r0value=emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R0); System.out.println("_Z15getstringlengthPKc result:"+result.intValue()+"----"+r0value);
MemoryBlock block2=memory.malloc(10,true); UnidbgPointer str2_ptr=block2.getPointer(); str2_ptr.write("666".getBytes()); String content2=ARM.readCString(emulator.getBackend(),str2_ptr.peer); System.out.println("_Z16getstringlength2PKcS0_:"+str2_ptr.toString()+"---"+content2); result=unicorn08module.callFunction(emulator,"_Z16getstringlength2PKcS0_",new PointerNumber(str1_ptr),new PointerNumber(str2_ptr))[0]; r0value=emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R0); System.out.println("_Z16getstringlength2PKcS0_ result:"+result.intValue()+"----"+r0value);
打印结果:
_Z15getstringlengthPKc:RW@0x4016b000---hello _Z15getstringlengthPKc result:5----5 _Z16getstringlength2PKcS0_:RW@0x4016c000---666 _Z16getstringlength2PKcS0_ result:8----8
7、这核心的核心:直接调用jni_onload
vm.callJNI_OnLoad(emulator,unicorn08module);
打印结果:这里可以看到分别在so的0x8c7、0xccb调用了FindClass和RegisterNative方法,然后注册MainActivity这个类的stringFromJNI2方法,该方法和so层中0xb35的方法是映射的!
JNIEnv->FindClass(com/example/unicorncourse08/MainActivity) was called from RX@0x40000c87[libnative-lib.so]0xc87 JNIEnv->RegisterNatives(com/example/unicorncourse08/MainActivity, unidbg@0xbffff778, 1) was called from RX@0x40000ccb[libnative-lib.so]0xccb RegisterNative(com/example/unicorncourse08/MainActivity, stringFromJNI2(Ljava/lang/String;)Ljava/lang/String;, RX@0x40000b35[libnative-lib.so]0xb35)
去so的0xc87和0xccb查看,果然是FindClass和RegisterNative方法,unidbg 诚不我欺! 作为逆向,其实最重要的还是最后那个打印结果:java层的stringFromJNI2方法就是和so层的这个方法是映射的:
进入里面调用的各个函数仔细分析,发现这个函数还是比较简单:先接受传入的string,再打印出来!由于这个函数并未导出,但是和java层的函数做了映射,所以这里也可以直接通过java层的名字来直接调用,代码如下:
//调用jni函数,对于动态注册的jni函数必须在完成地址的绑定才能调用 System.out.println("stringFromJNI1-------------------------"); DvmClass MainActivity_dvmclass=vm.resolveClass("com/example/unicorncourse08/MainActivity");//先把类找到,这里的原理很像反射 DvmObject resultobj=MainActivity_dvmclass.callStaticJniMethodObject(emulator,"stringFromJNI1(Ljava/lang/String;)Ljava/lang/String;","helloworld");//再通过类去调用里面的函数 System.out.println("resultobj:"+resultobj); resultobj=MainActivity_dvmclass.callStaticJniMethodObject(emulator,"stringFromJNI1(Ljava/lang/String;)Ljava/lang/String;",new StringObject(vm, "hellokanxue")); System.out.println("resultobj:"+resultobj); System.out.println("stringFromJNI1-------------------------");
//动态注册的jni函数stringFromJNI2 resultobj=MainActivity_dvmclass.callStaticJniMethodObject(emulator,"stringFromJNI2(Ljava/lang/String;)Ljava/lang/String;",new StringObject(vm, "hellostringFromJNI2")); System.out.println("resultobj:"+resultobj); System.out.println("stringFromJNI2-------------------------");
DvmObject mainactivity=MainActivity_dvmclass.newObject(null); mainactivity.callJniMethodObject(emulator,"stringFromJNI2(Ljava/lang/String;)Ljava/lang/String;",new StringObject(vm, "hellostringFromJNI2")); System.out.println("resultobj:"+resultobj); System.out.println("stringFromJNI2-------------------------");
打印的结果如下:这里面除了我们显式调用println打印的日志,还有unidbg这个框架自身打印的日志(主要是JNIenv这个类的函数调用):
stringFromJNI1------------------------- Find native function Java_com_example_unicorncourse08_MainActivity_stringFromJNI1(Ljava/lang/String;)Ljava/lang/String; => RX@0x40000a71[libnative-lib.so]0xa71 JNIEnv->GetStringUtfChars("helloworld") was called from RX@0x40000b03[libnative-lib.so]0xb03 JNIEnv->NewStringUTF("helloworld") was called from RX@0x40000b2f[libnative-lib.so]0xb2f resultobj:"helloworld" Find native function Java_com_example_unicorncourse08_MainActivity_stringFromJNI1(Ljava/lang/String;)Ljava/lang/String; => RX@0x40000a71[libnative-lib.so]0xa71 JNIEnv->GetStringUtfChars("hellokanxue") was called from RX@0x40000b03[libnative-lib.so]0xb03 [main]I/stringFromJNI1: content:helloworld,length:10 [main]I/stringFromJNI1: content:hellokanxue,length:11 [main]I/stringFromJNI2: content:hellostringFromJNI2,length:19 [main]I/stringFromJNI2: content:hellostringFromJNI2,length:19 JNIEnv->NewStringUTF("hellokanxue") was called from RX@0x40000b2f[libnative-lib.so]0xb2f resultobj:"hellokanxue" stringFromJNI1------------------------- Find native function Java_com_example_unicorncourse08_MainActivity_stringFromJNI2(Ljava/lang/String;)Ljava/lang/String; => RX@0x40000b35[libnative-lib.so]0xb35 JNIEnv->GetStringUtfChars("hellostringFromJNI2") was called from RX@0x40000b03[libnative-lib.so]0xb03 JNIEnv->NewStringUTF("hellostringFromJNI2") was called from RX@0x40000b2f[libnative-lib.so]0xb2f resultobj:"hellostringFromJNI2" stringFromJNI2------------------------- Find native function Java_com_example_unicorncourse08_MainActivity_stringFromJNI2(Ljava/lang/String;)Ljava/lang/String; => RX@0x40000b35[libnative-lib.so]0xb35 JNIEnv->GetStringUtfChars("hellostringFromJNI2") was called from RX@0x40000b03[libnative-lib.so]0xb03 JNIEnv->NewStringUTF("hellostringFromJNI2") was called from RX@0x40000b2f[libnative-lib.so]0xb2f resultobj:"hellostringFromJNI2" stringFromJNI2-------------------------
完整的代码:
package com.unicorncourse08;
import com.github.unidbg.LibraryResolver; import com.github.unidbg.Module; import com.github.unidbg.PointerNumber; import com.github.unidbg.arm.ARM; import com.github.unidbg.arm.backend.dynarmic.DynarmicLoader; import com.github.unidbg.linux.android.AndroidARMEmulator; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.dvm.DvmClass; import com.github.unidbg.linux.android.dvm.DvmObject; import com.github.unidbg.linux.android.dvm.StringObject; import com.github.unidbg.linux.android.dvm.VM; import com.github.unidbg.memory.Memory; import com.github.unidbg.memory.MemoryBlock; import com.github.unidbg.pointer.UnidbgPointer; import unicorn.ArmConst; import java.io.File;
public class MainActivity { static { DynarmicLoader.useDynarmic(); } public static void main(String[] args) { AndroidARMEmulator emulator= new AndroidARMEmulator("com.sun.jna",null,null); final Memory memory = emulator.getMemory();
VM vm = emulator.createDalvikVM(); vm.setVerbose(true);
LibraryResolver resolver = new AndroidResolver(23); memory.setLibraryResolver(resolver); Module unicorn08module=emulator.loadLibrary(new File("D:\\xxxx\\unidbg-master\\unidbg-android\\src\\test\\java\\com\\unicorncourse08\\unicorn08.so")); Number result=unicorn08module.callFunction(emulator,"_Z3addii",1,2)[0];//导出函数直接用符号名就行了 System.out.println("_Z3addii result:"+result.intValue());
//_Z7add_sixiiiiii result=unicorn08module.callFunction(emulator,"_Z7add_sixiiiiii",1,2,3,4,5,6)[0]; System.out.println("_Z7add_sixiiiiii result:"+result.intValue());
//_Z15getstringlengthPKc MemoryBlock block1=memory.malloc(10,true); UnidbgPointer str1_ptr=block1.getPointer(); str1_ptr.write("hello".getBytes()); String content=ARM.readCString(emulator.getBackend(),str1_ptr.peer); System.out.println("_Z15getstringlengthPKc:"+str1_ptr.toString()+"---"+content); result=unicorn08module.callFunction(emulator,"_Z15getstringlengthPKc",new PointerNumber(str1_ptr))[0]; Number r0value=emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R0); System.out.println("_Z15getstringlengthPKc result:"+result.intValue()+"----"+r0value);
MemoryBlock block2=memory.malloc(10,true); UnidbgPointer str2_ptr=block2.getPointer(); str2_ptr.write("666".getBytes()); String content2=ARM.readCString(emulator.getBackend(),str2_ptr.peer); System.out.println("_Z16getstringlength2PKcS0_:"+str2_ptr.toString()+"---"+content2); result=unicorn08module.callFunction(emulator,"_Z16getstringlength2PKcS0_",new PointerNumber(str1_ptr),new PointerNumber(str2_ptr))[0]; r0value=emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R0); System.out.println("_Z16getstringlength2PKcS0_ result:"+result.intValue()+"----"+r0value);
//调用jni_OnLoad函数 vm.callJNI_OnLoad(emulator,unicorn08module);
//调用jni函数,对于动态注册的jni函数必须在完成地址的绑定才能调用 System.out.println("stringFromJNI1-------------------------"); DvmClass MainActivity_dvmclass=vm.resolveClass("com/example/unicorncourse08/MainActivity");//先把类找到,这里的原理很像反射 DvmObject resultobj=MainActivity_dvmclass.callStaticJniMethodObject(emulator,"stringFromJNI1(Ljava/lang/String;)Ljava/lang/String;","helloworld");//再通过类去调用里面的函数 System.out.println("resultobj:"+resultobj); resultobj=MainActivity_dvmclass.callStaticJniMethodObject(emulator,"stringFromJNI1(Ljava/lang/String;)Ljava/lang/String;",new StringObject(vm, "hellokanxue")); System.out.println("resultobj:"+resultobj); System.out.println("stringFromJNI1-------------------------");
//动态注册的jni函数stringFromJNI2 resultobj=MainActivity_dvmclass.callStaticJniMethodObject(emulator,"stringFromJNI2(Ljava/lang/String;)Ljava/lang/String;",new StringObject(vm, "hellostringFromJNI2")); System.out.println("resultobj:"+resultobj); System.out.println("stringFromJNI2-------------------------");
DvmObject mainactivity=MainActivity_dvmclass.newObject(null); mainactivity.callJniMethodObject(emulator,"stringFromJNI2(Ljava/lang/String;)Ljava/lang/String;",new StringObject(vm, "hellostringFromJNI2")); System.out.println("resultobj:"+resultobj); System.out.println("stringFromJNI2-------------------------");
} }
总结一下,上述API包括了3种so函数的调用方法:
普通的so方法 jni_onload调用 jni 函数调用 上面举例的这些内容相对简单,并不涉及到so层调用java层的函数。如果遇到so层函数调用java层函数怎么办么?我们如果自己在apk写java代码调用so层函数,遇到so通过反射调用java层函数时,需要自己补上java层对应的类、方法和变量,因为这些需要执行的代码是绕不过去的! unidbg是这么样的么? 答案是肯定的!
示例:Unidbg模拟执行某段子so实操教程 ( 跑 native_init、执行sign )
:http://91fans.com.cn/post/unidbgzyone/
:http://91fans.com.cn/post/unidbgzytwo/
so 通过反射调用 java 层 ( 补环境 )
比如下面的这个so层的方法,会在jni_onload中被调用;这里需要获取java层普通变量、static变量后打印出来;也会获取java层的普通方法然后调用,这该怎么办了?
上面说了:so层调用java层的代码肯定是要补齐的(如果直接简单粗暴改so层代码,可能导致别人原来的逻辑错误)! 这里该怎么实操了? 大概的思路是:
自己补上缺失的方法(当然java层的方法可以用jadx、jeb等反编译得到,不用自己反编译smali),这里缺失了base64方法,需要补上! 自己补上缺失的变量,方法同上! 重写getStaticObjectField、getObjectField、callObjectMethodV等方法,然后检测传入的参数。一旦发现使用/调用的是java层变量、方法,用自己补上的哪些代码替换(原理像不像平时常用的hook?) 说了那么多,完整的demo代码如下:
package com.unicorncourse08;
import com.github.unidbg.Module; import com.github.unidbg.linux.android.AndroidARMEmulator; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.dvm.*; import com.github.unidbg.linux.android.dvm.jni.ProxyClassFactory; import com.github.unidbg.memory.Memory; import com.github.unidbg.virtualmodule.android.AndroidModule; import com.github.unidbg.virtualmodule.android.JniGraphics; import org.apache.commons.codec.binary.Base64; import sun.applet.Main;
import java.io.File; import java.lang.reflect.Field;
public class MainActivitymethod1 extends AbstractJni {
private static DvmClass MainActivityClass;
@Override /* * staticcontent是java层的静态变量;getStaticObjectField,一旦检测到so层引用这个变量,那么自己返回这个变量的值 * */ public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) { System.out.println("getStaticObjectField->"+signature); if(signature.equals("com/example/testjni/MainActivity->staticcontent:Ljava/lang/String;")){ return new StringObject(vm,"staticcontent");//源码 public static string staticcontent = "staticcontent" } return super.getStaticObjectField(vm, dvmClass, signature); }
@Override /* * objcontent是java层的变量;这里重写getObjectField方法,一旦检测到so层引用这个变量,那么自己返回这个变量的值 * */ public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) { System.out.println("getObjectField->"+signature); if(signature.equals("com/example/testjni/MainActivity->objcontent:Ljava/lang/String;")){ return new StringObject(vm,"objcontent");//public string objcontent } return super.getObjectField(vm, dvmObject, signature); } /* * java层的方法,这里需要复现,否则不知道怎么执行 * */ public String base64(String arg3) { String result=Base64.encodeBase64String(arg3.getBytes()); return result; }
@Override /* * base64是java层的方法,这里重写callObjectMethodV方法:一旦发现调用的是java层的base64方法,这里就用自己复现的base64方法替换 * */ public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) { System.out.println("callObjectMethodV->"+signature); if(signature.equals("com/example/testjni/MainActivity->base64(Ljava/lang/String;)Ljava/lang/String;")){ DvmObject dvmobj=vaList.getObjectArg(0); String arg= (String) dvmobj.getValue(); String result=base64(arg); return new StringObject(vm,result); } return super.callObjectMethodV(vm, dvmObject, signature, vaList); }
public static void main(String[] args) { MainActivitymethod1 mainActivitymethod1=new MainActivitymethod1(); AndroidARMEmulator emulator = new AndroidARMEmulator("org.telegram.messenger",null,null); final Memory memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23)); VM vm = emulator.createDalvikVM(); vm.setVerbose(true); vm.setJni(mainActivitymethod1); DalvikModule dm = vm.loadLibrary(new File("D:\\xxxx\\unidbg-master\\unidbg-android\\src\\test\\java\\com\\unicorncourse08\\calljava.so"), true); dm.callJNI_OnLoad(emulator); MainActivityClass=vm.resolveClass("com/example/testjni/MainActivity"); DvmObject obj=MainActivityClass.newObject(null); obj.callJniMethodObject(emulator,"base64byjni(Ljava/lang/String;)Ljava/lang/String;","callbase64byjni"); } }
整个逻辑其实并不复杂,从main函数开始:创建模拟器、创建虚拟机、加载so、调用so层函数!打印的结果如下:
JNIEnv->FindClass(com/example/testjni/MainActivity) was called from RX@0x400009df[libnative-lib.so]0x9df JNIEnv->RegisterNatives(com/example/testjni/MainActivity, unidbg@0xbffff768, 1) was called from RX@0x40000f19[libnative-lib.so]0xf19 RegisterNative(com/example/testjni/MainActivity, stringFromJNI(Ljava/lang/String;)Ljava/lang/String;, RX@0x40000cb1[libnative-lib.so]0xcb1) Find native function Java_com_example_testjni_MainActivity_base64byjni(Ljava/lang/String;)Ljava/lang/String; => RX@0x4000088d[libnative-lib.so]0x88d JNIEnv->FindClass(com/example/testjni/MainActivity) was called from RX@0x400009df[libnative-lib.so]0x9df getStaticObjectField->com/example/testjni/MainActivity->staticcontent:Ljava/lang/String; JNIEnv->GetStaticObjectField(class com/example/testjni/MainActivity, staticcontent Ljava/lang/String; => "staticcontent") was called from RX@0x40000aa5[libnative-lib.so]0xaa5 JNIEnv->GetStringUtfChars("staticcontent") was called from RX@0x40000adb[libnative-lib.so]0xadb [main]I/stringFromJNI: staticcontent:staticcontent [main]I/stringFromJNI: objcontent:objcontent getObjectField->com/example/testjni/MainActivity->objcontent:Ljava/lang/String; JNIEnv->GetObjectField(com.example.testjni.MainActivity@7b3300e5, objcontent Ljava/lang/String; => "objcontent") was called from RX@0x40000b11[libnative-lib.so]0xb11 JNIEnv->GetStringUtfChars("objcontent") was called from RX@0x40000adb[libnative-lib.so]0xadb JNIEnv->GetMethodID(com/example/testjni/MainActivity.base64(Ljava/lang/String;)Ljava/lang/String;) was called from RX@0x40000b55[libnative-lib.so]0xb55 callObjectMethodV->com/example/testjni/MainActivity->base64(Ljava/lang/String;)Ljava/lang/String; JNIEnv->CallObjectMethodV(com.example.testjni.MainActivity@7b3300e5, base64("callbase64byjni") => "Y2FsbGJhc2U2NGJ5am5p") was called from RX@0x40000bb1[libnative-lib.so]0xbb1 JNIEnv->GetStringUtfChars("Y2FsbGJhc2U2NGJ5am5p") was called from RX@0x40000adb[libnative-lib.so]0xadb JNIEnv->NewStringUTF("Y2FsbGJhc2U2NGJ5am5p") was called from RX@0x40000c05[libnative-lib.so]0xc05 [main]I/stringFromJNI: base64result:Y2FsbGJhc2U2NGJ5am5p
大杀器Unidbg真正的威力
:大杀器Unidbg真正的威力 - 知乎
大杀器内置的HOOK框架
当然Unidbg还内置了多种HOOK框架,今天讲一个分析So比较实用的一款HookZz
// 1. 获取HookZz对象 IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz // 2. enable hook hookZz.enable_arm_arm64_b_branch(); // 测试enable_arm_arm64_b_branch,可有可无 index = 0; hookZz.replace(module.findSymbolByName("lrand48"), new ReplaceCallback() { @Override public void postCall(Emulator<?> emulator, HookContext context) { ((EditableArm32RegisterContext)context).setR0(0x12345678); int ptrace_args0 = context.getIntArg(0); System.out.println("lrand48=" + ptrace_args0); } },true); //aesdecode hook hookZz.wrap((module.base)+0x39634+1, new WrapCallback() { // inline wrap导出函数 UnidbgPointer addr = null; @Override // 4. 方法执行前 public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) { addr= ctx.getPointerArg(0); UnidbgPointer pointerArg = ctx.getPointerArg(1); UnidbgPointer pointer = pointerArg.getPointer(12); int anInt = pointerArg.getInt(8); byte[] byteArray = pointer.getByteArray(0, anInt); String s =xuzi1(byteArray); System.out.println("aes aesdecode= " + s); } @Override // 5. 方法执行后 public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) { byte[] aaaa = addr.getPointer(0).getPointer(12).getByteArray(0,0x30); String s =xuzi1(aaaa); System.out.println("aes aesdecode1= " + s); } });
同理,此框架也支持导出函数HOOK以及InlineHOOK 有了这个方法,在你分析一些函数的时候,可以充当Log的效果或者强行改变一些函数的返回值让你更容易的分析,比如本例中笔者改变了Lrand48的返回值,让函数每次都强行返回0x12345678,这样在逆向分析的时候能让一些不确定性变成可控性。
拟执行某段子so实操教程
:Unidbg模拟执行某段子so实操教程(一) 先把框架搭起来_奋飞安全的博客-CSDN博客
运行自己的 so 文件
下面我们执行 libnative-lib.so 中的 stringFromJNI 函数。
在 unidbg-android/src/test/java/com 下新建 test 文件夹,然后新建个 java 类 MainActivity。
代码如下:
package com.test;
public class MainActivity {
public static void main(String[] args) {
MainActivity mainActivity = new MainActivity();
mainActivity.stringFromJNI();
}
private final AndroidEmulator emulator;
private final VM vm;
private DvmClass cNative;
private MainActivity() {
emulator = new AndroidARMEmulator();
Memory memory = emulator.getMemory();
// 设置 sdk版本 23
LibraryResolver resolver = new AndroidResolver(23);
memory.setLibraryResolver(resolver);
//创建DalvikVM,可以载入apk,也可以为null
vm = emulator.createDalvikVM(null);
// 是否打印日志
vm.setVerbose(false);
// System.out.println(getPath());
// 载入要执行的 so
DalvikModule dm = vm.loadLibrary(new File(getPath() + "/fenfei/libnative-lib.so"), false);
dm.callJNI_OnLoad(emulator);
}
private void stringFromJNI() {
// Jni调用的类
cNative = vm.resolveClass("com/fenfei/myndk/MainActivity");
DvmObject<?> strRc = cNative.callStaticJniMethodObject(emulator, "stringFromJNI()Ljava/lang/String;");
System.out.println("call stringFromJNI rc = " + strRc.getValue());
}
public String getPath() {
String path = this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
if (System.getProperty("os.name").contains("dows")) {
path = path.substring(1, path.length());
}
if (path.contains("jar")) {
// System.out.println("jar = " + path);
path = path.substring(0, path.lastIndexOf("."));
return path.substring(0, path.lastIndexOf("/"));
}
// System.out.println(path);
// path.replace("target/classes/", "");
return path.replace("/target/test-classes/", "");
}
}
输出如下:
call stringFromJNI rc = Hello from C++
示例 2
调用 libencryptLib.so 文件中的 getGameKey 函数。编写 EncryptUtilsJni 类
package cn.hestyle;
import com.github.unidbg.Module;
import com.github.unidbg.arm.ARMEmulator;
import com.github.unidbg.linux.android.AndroidARMEmulator;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import java.io.File;
import java.io.IOException;
/**
* description: EncryptUtils调用so
*
* @author hestyle
* @version 1.0
* @className unidbg->EncryptUtilsJni
* @date 2020-05-20 22:01
**/
public class EncryptUtilsJni extends AbstractJni {
// ARM模拟器
private final ARMEmulator emulator;
// vm
private final VM vm;
// 载入的模块
private final Module module;
private final DvmClass TTEncryptUtils;
/**
*
* @param soFilePath 需要执行的so文件路径
* @param classPath 需要执行的函数所在的Java类路径
* @throws IOException
*/
public EncryptUtilsJni(String soFilePath, String classPath) throws IOException {
// 创建app进程,包名可任意写
emulator = new AndroidARMEmulator("cn.hestyle");
Memory memory = emulator.getMemory();
// 作者支持19和23两个sdk
memory.setLibraryResolver(new AndroidResolver(23));
// 创建DalvikVM,利用apk本身,可以为null
vm = ((AndroidARMEmulator) emulator).createDalvikVM(null);
// (关键处1)加载so,填写so的文件路径
DalvikModule dm = vm.loadLibrary(new File(soFilePath), false);
// 调用jni
dm.callJNI_OnLoad(emulator);
module = dm.getModule();
// (关键处2)加载so文件中的哪个类,填写完整的类路径
TTEncryptUtils = vm.resolveClass(classPath);
}
/**
* 调用so文件中的指定函数
* @param methodSign 传入你要执行的函数信息,需要完整的smali语法格式的函数签名
* @param args 是即将调用的函数需要的参数
* @return 函数调用结果
*/
private String myJni(String methodSign, Object ...args) {
// 使用jni调用传入的函数签名对应的方法()
Number ret = TTEncryptUtils.callStaticJniMethod(emulator, methodSign, args);
// ret存放返回调用结果存放的地址,获得函数执行后返回值
StringObject str = vm.getObject(ret.intValue() & 0xffffffffL);
return str.getValue();
}
/**
* 关闭模拟器
* @throws IOException
*/
private void destroy() throws IOException {
emulator.close();
System.out.println("emulator destroy...");
}
public static void main(String[] args) throws IOException {
// 1、需要调用的so文件所在路径
String soFilePath = "src/test/resources/myso/libencryptLib.so";
// 2、需要调用函数所在的Java类完整路径,比如a/b/c/d等等,注意需要用/代替.
String classPath = "com/.../EncryptUtils";
// 3、需要调用函数的函数签名,我这里调用EncryptUtils中的getGameKey方法,由于此方法没有参数列表,所以不需要传入
String methodSign = "getGameKey()Ljava/lang/String;";
EncryptUtilsJni encryptUtilsJni = new EncryptUtilsJni(soFilePath, classPath);
// 输出getGameKey方法调用结果
System.err.println(encryptUtilsJni.myJni(methodSign));
encryptUtilsJni.destroy();
}
}
参数说明 EncryptUtilsJni类 中最重要的设置为 main 方法中的 soFilePath、classPath、methodSign 三个参数。main方法中已经注释过了,这里再次解释一下。
soFilePath,填写你需要调用的so文件路径 classPath,填写你需要调用的函数所在Java类的完整类路径。 methodSign,填写你要调用的函数签名,语法为smali。(在jadx中,直接可以看smali代码) 备注:如果你要调用的函数还需要传入参数,直接传入 myJni 方法中即可,myJni 方法中省略 args 参数就是供你传入参数。
3、Unidbg 一些基本使用
在 unidbg-android 的 src/test 包含了几个 Demo,可以尝试将 Demo 运行起来,即可看到 Unidbg 的效果:https://github.com/zhkl0228/unidbg/tree/master/unidbg-android/src/test/java
快速使用步骤
//1.创建Android模拟器实例 AndroidEmulator emulator = AndroidEmulatorBuilder .for32Bit() .addBackendFactory(new DynarmicFactory(true)) .build();
// 创建模拟器实例,建议使用实际进程名,可以规避进程名校验 // emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.xxx.xxx").build();
//2.创建模拟器内存接口 Memory memory = emulator.getMemory(); // final Memory memory = emulator.getMemory();
//3.设置Android SDK 版本,设置系统类库解析 memory.setLibraryResolver(new AndroidResolver(23));
//4.创建虚拟机 VM vm = emulator.createDalvikVM(); // 创建 Android 虚拟机,传入 APK,unidbg 可以协助做一部分签名工作 vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/xxx/xxx.apk"));
//5.加载ELF文件 // 创建 jobject, 如果没用到的话可以不写 cNative = vm.resolveClass("com/xxx/xxx"); // 加载 so 到虚拟内存,第二个参数的意思表示是否执行动态库的初始化代码 DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xxx/libxxx.so"),true); DalvikModule dm = vm.loadLibrary(new File("你的ELF文件路径名"), false); // 获取 so 模块的句柄 module = dm.getModule(); // 设置 JNI vm.setJni(this); // 打印日志 vm.setVerbose(true);
//6.调用 JNI_OnLoad dm.callJNI_OnLoad(emulator);
//此时ELF将加载到内存,可以对其做任意操作 //7.执行JNI方法 DvmObject<?> obj = ProxyDvmObject.createObject(vm, this); boolean result = obj.callJniMethodBoolean(emulator, "jnitest(Ljava/lang/String;)Z", str);
构建 函数参数 格式
// 构建函数参数格式 List args = new ArrayList<>(10);
// 各种基本参数格式兼容
// 参数1:JNIEnv *env args.add(vm.getJNIEnv());
// 参数2:jobject 或 jclass 用不到直接填0即可 // DvmObject<?> cnative = cNative.newObject(null); // args.add(cnative.hashCode()); args.add(0);
// 参数3 字符串对象 String input = "abcdef"; args.add(vm.addLocalObject(new StringObject(vm, input)));
// 参数4 bytes 数组 String input = "abcdef"; byte[] input_bytes = input.getBytes(StandardCharsets.UTF_8); ByteArray input_byte_array = new ByteArray(vm,input_bytes); args.add(vm.addLocalObject(input_byte_array));
// 参数5 bool // false 填 0,true 填 1 args.add(1);
unidbg 主动调用 函数
// 第二个参数是函数偏移量(thumb 记得+1),第三个参数是参数列表 Number number = module.callFunction(emulator,0x10618, args.toArray());
// unicorn trace(贼好用!!!堪比 ida trace!!!) String traceFile = "trace.txt"; PrintStream traceStream = null; try{ traceStream = new PrintStream(new FileOutputStream(traceFile), true); } catch (FileNotFoundException e) { e.printStackTrace(); } // 核心 trace 开启代码,也可以自己指定函数地址和偏移量 emulator.traceCode(module.base,module.base+module.size).setRedirect(traceStream); // 获取最终返回值,同时运行过程中的汇编代码和寄存器值会写入到文件中 return vm.getObject(number.intValue()).getValue().toString();
输出结果:
看看 trace log,建议搭配 010 editor 使用, 更好用
创建 AndroidEmulator 实例
使用 AndroidEmulatorBuilder 可以来帮助你快速创建一个 AndroidEmulator 的实例。
AndroidEmulator emulator = AndroidEmulatorBuilder //指定32位CPU .for32Bit() //添加后端,推荐使用Dynarmic,运行速度快,但并不支持某些新特性 .addBackendFactory(new DynarmicFactory(true)) //指定进程名,推荐以安卓包名做进程名 .setProcessName("com.github.unidbg") //设置根路径 .setRootDir(new File("target/rootfs/default")) //生成AndroidEmulator实例 .build();
使用 AndroidEmulator
当使用 AndroidEmulatorBuilder 构造了一个 AndroidEmulator 实例之后,就可以直接来操作这个实例
//获取内存操作接口 Memory memory = emulator.getMemory(); //获取进程pid int pid = emulator.getPid(); //创建虚拟机 VM dalvikVM = emulator.createDalvikVM(); //创建虚拟机并指定APK文件 VM dalvikVM = emulator.createDalvikVM(new File("apk file path")); //获取已创建的虚拟机 VM dalvikVM = emulator.getDalvikVM(); //显示当前寄存器状态 可指定寄存器 emulator.showRegs(); //获取后端CPU Backend backend = emulator.getBackend(); //获取进程名 String processName = emulator.getProcessName(); //获取寄存器 RegisterContext context = emulator.getContext(); //Trace读内存 emulator.traceRead(1,0); //Trace写内润 emulator.traceWrite(1,0); //Trace汇编 emulator.traceCode(1,0); //是否正在运行 boolean running = emulator.isRunning();
Memory 操作
Memory memory = emulator.getMemory(); //指定Android SDK 版本,目前支持19和23两个版本 memory.setLibraryResolver(new AndroidResolver(23));
//拿到一个指针,指向内存地址,通过该指针可操作内存 UnidbgPointer pointer = memory.pointer(0x4000000);
//获取当前内存映射情况 Collection memoryMap = memory.getMemoryMap();
//根据模块名来拿到某个模块 Module module = memory.findModule("module name");
//根据地址拿到某个模块 Module module = memory.findModuleByAddress(0x40000000);
VM 操作
//推荐指定APK文件,Unidbg会自动做许多固定的操作 VM vm = emulator.createDalvikVM(new File("apk file path"));
//是否输出JNI运行日志 vm.setVerbose(true);
//加载SO模块 参数二设置是否自动调用init函数 DalvikModule dalvikModule = vm.loadLibrary(new File("so file path"), true);
//设置JNI交互接口 参数需实现Jni接口,推荐使用this继承AbstractJni vm.setJni(this);
//获取JNIEnv指针,可作为参数传递 Pointer jniEnv = vm.getJNIEnv();
//获取JavaVM指针,可作为参数传递 Pointer javaVM = vm.getJavaVM();
//调用JNI_OnLoad函数 vm.callJNI_OnLoad(emulator,dalvikModule.getModule());
//向VM添加全局对象,返回该对象的hash值 int hash = vm.addGlobalObject(dvmObj);
//获取虚拟机中的对象,参数为该对象的hash值 DvmObject<?> object = vm.getObject(hash);
unidbg 之 hook
hook 代码是逆向最基本的功能之一,frida 的 hook 代码都不陌生,Unidbg 还内置了多种 HOOK 框架,unidbg 底层用的是分析So比较实用的 HookZz 框架 ,所以 hook 的代码长这样的:
public void hook(){ //unidbg集成了HookZz框架 HookZz hook = HookZz.getInstance(emulator); //直接hook add函数的地址,比通过符号hook更具有“普适性” hook.replace(module.base + 0x3DC + 1, new ReplaceCallback() { @Override public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) { //R2和R3才是参数,R0是env,R1是object System.out.println(String.format("R2: %d, R3: %d",context.getIntArg(2),context.getIntArg(3))); //把第二个参数R3改成5 emulator.getBackend().reg_write(Unicorn.UC_ARM_REG_R3,5); return super.onCall(emulator, context, originFunction); }
@Override public void postCall(Emulator<?> emulator, HookContext context) { emulator.getBackend().reg_write(Unicorn.UC_ARM_REG_R0,10); //返回值放R0,这里直接修改返回值 super.postCall(emulator, context); } }, true); }
代码整体的结构和 frinda 的 hook 类似,onCall就是刚进入函数时候的回调(本质就是在函数入口处hook),onPost就是在函数ret前的hook回调!
unidbg 之 打patch
打patch方法:hook本质也是patch,还有很多关键的跳转代码(android下的B、BL等)可能也要NOP掉才能按照我们自己的逻辑执行!最原始打patch的办法就是在IDA或010editor更改,为了更好地逆向so,unidbg也提供了打patch的方法,如下:
public void patch(){ UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0x3E8); byte[] code = new byte[]{(byte) 0xd0, 0x1a};//直接用硬编码改原so的代码:subs r0,r2,r3 pointer.write(code); } public void patch2(){ UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0x3E8); Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb); String s = "subs r0, r2, r3"; byte[] machineCode = keystone.assemble(s).getMachineCode(); //byte[] code = ; pointer.write(machineCode); }
代码很简单,可以直接在目标位置写硬编码,也可以借助 keystone 写汇编代码!
hook 的时候需要知道so的基址和代码偏移,unidbg 提供的方法如下:
// 加载so到虚拟内存 DalvikModule dm = vm.loadLibrary("libnative-lib.so", true); // 得到模块对象,然后根据导出的函数名找到函数入口偏移,比直接在代码写死地址灵活一些 module = dm.getModule(); int address = (int) module.findSymbolByName("funcNmae").getAddress();
unidbg 的单步调试
unidbg 的单步调试也叫 console debug,就是在 console 下输入各种命令调试!操作也简单:
(1)先下个断点:当然这里也能制定特定的偏移地址
emulator.attach().addBreakPoint(module.findSymbolByName("funName").getAddress());
(2)代码运行到断点后正常情况下会停下,然后逆向人员就可以在console下输入各种命令操作了,原理和 hyperpwn、gbd 等类似,如下:
比如 r 是删除断点,b 是增加断点,n 是步过等!其他写方面的操作命令如下:
wr0-wr7, wfp, wip, wsp : write specified register wb(address), ws(address), wi(address) : write (byte, short, integer) memory of specified address, address must start with 0x wx(address) : write bytes to memory at specified address, address must start with 0x
如果命中断点后想做一个个性化的操作,但是又觉得在 console 上挨个敲命令麻烦,也可以写代码固化下来,比如这样:
public void ReplaceArgByConsoleDebugger(){
emulator.attach().addBreakPoint(module.findSymbolByName("funName").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext context = emulator.getContext();
String fakeInput = "hello world";
int length = fakeInput.length();
// 修改r1值为新长度
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R1, length);
MemoryBlock fakeInputBlock = emulator.getMemory().malloc(length, true);
fakeInputBlock.getPointer().write(fakeInput.getBytes(StandardCharsets.UTF_8));
// 修改r0为指向新字符串的新指针
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, fakeInputBlock.getPointer().peer);
Pointer buffer = context.getPointerArg(2);
// OnLeave
emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
String result = buffer.getString(0);
System.out.println("base64 result:"+result);
return true;
}
});
return true;
}
});
}
条件断点
为了避免被过多信息干扰,很多时候的断点或hook是需要设置条件的,符合了条件才需要进一步打印出来查看结果,unidbg也不例外,也是这个思路。举个例子:比如strcat、strstr、strcmp这种函数,每时每刻都在被大量的模块调用,直接hook打印会产生大量无用日志,严重影响排查。同时大量日志得打印也会严重拖慢运行速度,所以需要自己写条件判断是否需要打印日志!比如这种:
(1)只打印某个特定so调用的strcat函数:
public void hookstrcmp(){
long address = module.findSymbolByName("strcat").getAddress();
emulator.attach().addBreakPoint(address, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext registerContext = emulator.getContext();
String arg1 = registerContext.getPointerArg(0).getString(0);
String moduleName = emulator.getMemory().findModuleByAddress(registerContext.getLRPointer().peer).name;
if(moduleName.equals("libxxx.so")){
System.out.println("strcat arg1:"+arg1);
}
return true;
}
});
}
(2)只打印某个特定函数中调用的strcat函数:
// 早先声明全局变量 public boolean show = false;
public void hookstrcat(){
emulator.attach().addBreakPoint(module.findSymbolByName("targetfunName").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext registerContext = emulator.getContext();
show = true;//进入目标函数就把show设置为true,下面才好打印日志
emulator.attach().addBreakPoint(registerContext.getLRPointer().peer, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
show = false;//离开目标函数就把show设置为false,下面才知道不打印日志
return true;
}
});
return true;
}
});
emulator.attach().addBreakPoint(module.findSymbolByName("strcat").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext registerContext = emulator.getContext();
String arg1 = registerContext.getPointerArg(0).getString(0);
if(show){
System.out.println("strcmp arg1:"+arg1);
}
return true;
}
});
}
内存检索
搜索某些 sign 字段、字符串的时候特别重要,如下:
private Collection searchMemory(long start, long end, byte[] data) {
List pointers = new ArrayList<>();
for (long i = start, m = end - data.length; i < m; i++) {
byte[] oneByte = emulator.getBackend().mem_read(i, 1);
if (data[0] != oneByte[0]) {
continue;
}
if (Arrays.equals(data, emulator.getBackend().mem_read(i, data.length))) {
pointers.add(UnidbgPointer.pointer(emulator, i));
i += (data.length - 1);
}
}
return pointers;
}
4、Unidbg 调用so文件
示例:Unidbg 调用so文件生成京东 sign 参数
案例学习,模拟调用so文件生成京东sign参数:https://blog.csdn.net/qq_44628911/article/details/127322805
抓包 "商品详情页 ",要模拟的是sign参数
搭建基础框架代码:
package com.kdd.test;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.*;
public class jd_main extends AbstractJni {
private static final Log log = LogFactory.getLog(AbstractJni.class);
public static void main (String[] args) throws IOException {
jd_main RunLDQ =new jd_main();
RunLDQ.runJni();
RunLDQ.destroy();
}
private void destroy() throws IOException{
emulator.close();
System.out.println("destroy");
}
private static LibraryResolver createLibraryResolver() {
return new AndroidResolver(23);
}
private static AndroidEmulator createARMEmulator() {
return AndroidEmulatorBuilder
.for32Bit()
.build();
}
private final AndroidEmulator emulator;
private final VM vm;
private Module module;
private DvmClass aBitmapkitUtils;
//初始化
public jd_main(){
emulator = createARMEmulator();
final Memory memory = emulator.getMemory();
// 设置 sdk版本 23
memory.setLibraryResolver(createLibraryResolver());
//使用apk文件加载so的话,会自动处理签名方面的jni,具体可看AbstractJni,利用apk加载的好处,
vm = emulator.createDalvikVM(new File("F:\\frida_learn_app\\jd\\jd-9.2.2.apk"));
vm.setJni(this);
// 是否打印日志
vm.setVerbose(true);
}
public String runJni(){
//加载apk的so
DalvikModule dm = vm.loadLibrary("jdbitmapkit", false);
//调用jni
dm.callJNI_OnLoad(emulator);
module = dm.getModule();
return null;
}
运行有报错补代码
@Override
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature) {
case "com/jingdong/common/utils/BitmapkitUtils->a:Landroid/app/Application;": {
return vm.resolveClass("android/app/Activity", vm.resolveClass("android/content/ContextWrapper", vm.resolveClass("android/content/Context"))).newObject(null);
}
}
return super.getStaticObjectField(vm, dvmClass, signature);
}
报错,补代码
@Override
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature) {
case "android/app/Application->getPackageName()Ljava/lang/String;": {
String packageName = vm.getPackageName();
if (packageName != null) {
return new StringObject(vm, packageName);
}
}
}
throw new UnsupportedOperationException(signature);
}
报错,补代码
@Override
public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "sun/security/pkcs/PKCS7->([B)V": {
ByteArray array = varArg.getObjectArg(0);
return new StringObject(vm, new String(array.getValue()));
}
}
return super.newObject(vm, dvmClass, signature, varArg);
}
基础环境没报错后,调用签名函数
//加载so的哪个类
aBitmapkitUtils = vm.resolveClass("com/jingdong/common/utils/BitmapkitUtils");
//调用方法
DvmObject<?> strRc = aBitmapkitUtils.callStaticJniMethodObject(emulator,"getSignFromJni()(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
vm.addLocalObject(null),
vm.addLocalObject(new StringObject(vm,"wareBusiness")),
vm.addLocalObject(new StringObject(vm,"{\"abTest800\":true,\"avoidLive\":false,\"brand\":\"360\",\"cityId\":2144,\"darkModelEnum\":3,\"districtId\":24463,\"eventId\":\"Searchlist_Productid\",\"fromType\":0,\"isDesCbc\":true,\"latitude\":\"26.618816\",\"lego\":true,\"longitude\":\"106.644705\",\"model\":\"1605-A01\",\"ocrFlag\":false,\"pluginVersion\":90220,\"plusClickCount\":0,\"plusLandedFatigue\":0,\"provinceId\":\"24\",\"skuId\":\"10024083045618\",\"source_type\":\"search\",\"source_value\":\"鼠标垫小号\",\"townId\":51707,\"uAddrId\":\"0\"}")),
vm.addLocalObject(new StringObject(vm,"uuid")),
vm.addLocalObject(new StringObject(vm,"android")),
vm.addLocalObject(new StringObject(vm,"9.2.2")));
System.out.println(strRc.getValue());
//获取返回值
return (String) strRc.getValue();
后面有报错也是跟着报错补环境。最后成功运行出结果:
全部代码如下:
package com.kdd.test;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.Enumeration;
import com.github.unidbg.linux.android.dvm.api.*;
import com.github.unidbg.linux.android.dvm.api.ClassLoader;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.*;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.*;
public class jd_main extends AbstractJni {
private static final Log log = LogFactory.getLog(AbstractJni.class);
public static void main (String[] args) throws IOException {
jd_main RunLDQ =new jd_main();
RunLDQ.runJni(args);
RunLDQ.destroy();
}
private void destroy() throws IOException{
emulator.close();
System.out.println("destroy");
}
private static LibraryResolver createLibraryResolver() {
return new AndroidResolver(23);
}
private static AndroidEmulator createARMEmulator() {
return AndroidEmulatorBuilder
.for32Bit()
.build();
}
private final AndroidEmulator emulator;
private final VM vm;
private Module module;
private DvmClass aBitmapkitUtils;
//初始化
public jd_main(){
emulator = createARMEmulator();
final Memory memory = emulator.getMemory();
// 设置 sdk版本 23
memory.setLibraryResolver(createLibraryResolver());
//使用apk文件加载so的话,会自动处理签名方面的jni,具体可看AbstractJni,利用apk加载的好处,
vm = emulator.createDalvikVM(new File("F:\\frida_learn_app\\jd\\jd-9.2.2.apk"));
vm.setJni(this);
// 是否打印日志
// vm.setVerbose(true);
}
public String runJni(String[] args){
//加载apk的so
DalvikModule dm = vm.loadLibrary("jdbitmapkit", false);
//调用jni
dm.callJNI_OnLoad(emulator);
module = dm.getModule();
//加载so的哪个类
aBitmapkitUtils = vm.resolveClass("com/jingdong/common/utils/BitmapkitUtils");
//调用方法
DvmObject<?> strRc = aBitmapkitUtils.callStaticJniMethodObject(emulator,"getSignFromJni()(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
vm.addLocalObject(null),
vm.addLocalObject(new StringObject(vm,"wareBusiness")),
vm.addLocalObject(new StringObject(vm,"{\"abTest800\":true,\"avoidLive\":false,\"brand\":\"360\",\"cityId\":2144,\"darkModelEnum\":3,\"districtId\":24463,\"eventId\":\"Searchlist_Productid\",\"fromType\":0,\"isDesCbc\":true,\"latitude\":\"26.618816\",\"lego\":true,\"longitude\":\"106.644705\",\"model\":\"1605-A01\",\"ocrFlag\":false,\"pluginVersion\":90220,\"plusClickCount\":0,\"plusLandedFatigue\":0,\"provinceId\":\"24\",\"skuId\":\"10024083045618\",\"source_type\":\"search\",\"source_value\":\"鼠标垫小号\",\"townId\":51707,\"uAddrId\":\"0\"}")),
vm.addLocalObject(new StringObject(vm,"uuid")),
vm.addLocalObject(new StringObject(vm,"android")),
vm.addLocalObject(new StringObject(vm,"9.2.2")));
System.out.println(strRc.getValue());
//获取返回值
return (String) strRc.getValue();
}
@Override
public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature) {
case "com/jingdong/common/utils/BitmapkitUtils->a:Landroid/app/Application;": {
return vm.resolveClass("android/app/Activity", vm.resolveClass("android/content/ContextWrapper", vm.resolveClass("android/content/Context"))).newObject(null);
}
}
return super.getStaticObjectField(vm, dvmClass, signature);
}
@Override
public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "sun/security/pkcs/PKCS7->([B)V": {
ByteArray array = varArg.getObjectArg(0);
return new StringObject(vm, new String(array.getValue()));
}
}
return super.newObject(vm, dvmClass, signature, varArg);
}
@Override
public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature) {
case "android/app/Application->getPackageName()Ljava/lang/String;": {
String packageName = vm.getPackageName();
if (packageName != null) {
return new StringObject(vm, packageName);
}
}
}
throw new UnsupportedOperationException(signature);
}
@Override
public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "java/lang/StringBuffer->()V":{
return vm.resolveClass("java/lang/StringBuffer").newObject(new StringBuffer());
}
case "java/lang/Integer->(I)V" :{
return vm.resolveClass("java/lang/Integer").newObject(new Integer(vaList.getIntArg(0)));
}
}
throw new UnsupportedOperationException(signature);
}
@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
switch (signature) {
case "android/app/Application->getAssets()Landroid/content/res/AssetManager;":
return new AssetManager(vm, signature);
case "android/app/Application->getClassLoader()Ljava/lang/ClassLoader;":
return new ClassLoader(vm, signature);
case "android/app/Application->getContentResolver()Landroid/content/ContentResolver;":
return vm.resolveClass("android/content/ContentResolver").newObject(signature);
case "java/util/ArrayList->get(I)Ljava/lang/Object;": {
int index = vaList.getIntArg(0);
ArrayListObject arrayList = (ArrayListObject) dvmObject;
return arrayList.getValue().get(index);
}
case "android/app/Application->getSystemService(Ljava/lang/String;)Ljava/lang/Object;": {
StringObject serviceName = vaList.getObjectArg(0);
assert serviceName != null;
return new SystemService(vm, serviceName.getValue());
}
case "java/lang/String->toString()Ljava/lang/String;":
return dvmObject;
case "java/lang/Class->getName()Ljava/lang/String;":
return new StringObject(vm, ((DvmClass) dvmObject).getName());
case "android/view/accessibility/AccessibilityManager->getEnabledAccessibilityServiceList(I)Ljava/util/List;":
return new ArrayListObject(vm, Collections.>emptyList());
case "java/util/Enumeration->nextElement()Ljava/lang/Object;":
return ((Enumeration) dvmObject).nextElement();
case "java/util/Locale->getLanguage()Ljava/lang/String;":
Locale locale = (Locale) dvmObject.getValue();
return new StringObject(vm, locale.getLanguage());
case "java/util/Locale->getCountry()Ljava/lang/String;":
locale = (Locale) dvmObject.getValue();
return new StringObject(vm, locale.getCountry());
case "android/os/IServiceManager->getService(Ljava/lang/String;)Landroid/os/IBinder;": {
ServiceManager serviceManager = (ServiceManager) dvmObject;
StringObject serviceName = vaList.getObjectArg(0);
assert serviceName != null;
return serviceManager.getService(vm, serviceName.getValue());
}
case "java/io/File->getAbsolutePath()Ljava/lang/String;":
File file = (File) dvmObject.getValue();
return new StringObject(vm, file.getAbsolutePath());
case "android/app/Application->getPackageManager()Landroid/content/pm/PackageManager;":
case "android/content/ContextWrapper->getPackageManager()Landroid/content/pm/PackageManager;":
case "android/content/Context->getPackageManager()Landroid/content/pm/PackageManager;":
DvmClass clazz = vm.resolveClass("android/content/pm/PackageManager");
return clazz.newObject(signature);
case "android/content/pm/PackageManager->getPackageInfo(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;": {
StringObject packageName = vaList.getObjectArg(0);
assert packageName != null;
int flags = vaList.getIntArg(1);
if (log.isDebugEnabled()) {
log.debug("getPackageInfo packageName=" + packageName.getValue() + ", flags=0x" + Integer.toHexString(flags));
}
return new PackageInfo(vm, packageName.getValue(), flags);
}
case "android/app/Application->getPackageName()Ljava/lang/String;":
case "android/content/ContextWrapper->getPackageName()Ljava/lang/String;":
case "android/content/Context->getPackageName()Ljava/lang/String;": {
String packageName = vm.getPackageName();
if (packageName != null) {
return new StringObject(vm, packageName);
}
break;
}
case "android/content/pm/Signature->toByteArray()[B":
if (dvmObject instanceof Signature) {
Signature sig = (Signature) dvmObject;
return new ByteArray(vm, sig.toByteArray());
}
break;
case "android/content/pm/Signature->toCharsString()Ljava/lang/String;":
if (dvmObject instanceof Signature) {
Signature sig = (Signature) dvmObject;
return new StringObject(vm, sig.toCharsString());
}
break;
case "java/lang/String->getBytes()[B": {
String str = (String) dvmObject.getValue();
return new ByteArray(vm, str.getBytes());
}
case "java/lang/String->getBytes(Ljava/lang/String;)[B":
String str = (String) dvmObject.getValue();
StringObject charsetName = vaList.getObjectArg(0);
assert charsetName != null;
try {
return new ByteArray(vm, str.getBytes(charsetName.getValue()));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
case "java/lang/Integer->toString()Ljava/lang/String;":{
Integer iUse = (Integer)dvmObject.getValue();
return new StringObject(vm, Integer.toString(iUse));
}
case "java/lang/StringBuffer->toString()Ljava/lang/String;":{
StringBuffer str1 = (StringBuffer) dvmObject.getValue();
return new StringObject(vm,str1.toString());
}
case "java/lang/StringBuffer->append(Ljava/lang/String;)Ljava/lang/StringBuffer;": {
StringBuffer str1 = (StringBuffer) dvmObject.getValue();
StringObject serviceName = vaList.getObjectArg(0);
assert serviceName != null;
return vm.resolveClass("java/lang/StringBuffer").newObject(str1.append(serviceName.getValue()));
}
case "java/security/cert/CertificateFactory->generateCertificate(Ljava/io/InputStream;)Ljava/security/cert/Certificate;":
CertificateFactory factory = (CertificateFactory) dvmObject.getValue();
DvmObject<?> stream = vaList.getObjectArg(0);
assert stream != null;
InputStream inputStream = (InputStream) stream.getValue();
try {
return vm.resolveClass("java/security/cert/Certificate").newObject(factory.generateCertificate(inputStream));
} catch (CertificateException e) {
throw new IllegalStateException(e);
}
case "java/security/cert/Certificate->getEncoded()[B": {
Certificate certificate = (Certificate) dvmObject.getValue();
try {
return new ByteArray(vm, certificate.getEncoded());
} catch (CertificateEncodingException e) {
throw new IllegalStateException(e);
}
}
case "java/security/MessageDigest->digest([B)[B": {
MessageDigest messageDigest = (MessageDigest) dvmObject.getValue();
ByteArray array = vaList.getObjectArg(0);
assert array != null;
return new ByteArray(vm, messageDigest.digest(array.getValue()));
}
case "java/util/ArrayList->remove(I)Ljava/lang/Object;": {
int index = vaList.getIntArg(0);
ArrayListObject list = (ArrayListObject) dvmObject;
return list.getValue().remove(index);
}
case "java/util/List->get(I)Ljava/lang/Object;":
List<?> list = (List<?>) dvmObject.getValue();
return (DvmObject<?>) list.get(vaList.getIntArg(0));
case "java/util/Map->entrySet()Ljava/util/Set;":
Map<?, ?> map = (Map<?, ?>) dvmObject.getValue();
return vm.resolveClass("java/util/Set").newObject(map.entrySet());
case "java/util/Set->iterator()Ljava/util/Iterator;":
Set<?> set = (Set<?>) dvmObject.getValue();
return vm.resolveClass("java/util/Iterator").newObject(set.iterator());
}
throw new UnsupportedOperationException(signature);
}
}
打包成 jar,方便其它程序调用
IDEA 找到 File ---> Project Structure … 然后选择 Artifacts, 点加号 Add 如图配置,勾上 Include tests
点击 ok 后,Build ---> Build Artifacts 进行编译,编译成功后会生成很多jar文件
在控制台测试运行下:java -jar unidbg-master.jar
运行出了结果,证明打包的没问题
python 调用 打包的 jar 包
# coding:utf-8
import requests, urllib, subprocess
import chardet, jpype,os
headers = {
"Host": "api.m.jd.com",
"charset": "UTF-8",
"cache-control": "no-cache",
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
"user-agent": "okhttp/3.12.1"
}
cookies = {
}
url = "https://api.m.jd.com/client.action"
params = {
"functionId": "wareBusiness",
"clientVersion": "9.2.2",
"build": "85371",
"client": "android",
"d_brand": "360",
"d_model": "1605-A01",
"osVersion": "6.0.1",
"screen": "1920*1080",
"partner": "ks012",
"aid": "xxx",
"oaid": "",
"eid": "xxx",
"sdkVersion": "23",
"lang": "zh_CN",
"uuid": "xxx",
"area": "24_2144_2149_21104",
"networkType": "wifi",
"wifiBssid": "xxx",
# "st": "1665562015795",
# "sign": "45a7dc3f547be113a6a4dfa942e190c6",
# "sv": "111"
}
body = '''{"abTest800":true,"avoidLive":false,"brand":"360","cityId":2144,"darkModelEnum":3,"districtId":24463,"eventId":"Searchlist_Productid","fromType":0,"isDesCbc":true,"latitude":"","lego":true,"longitude":"","model":"1605-A01","ocrFlag":false,"pluginVersion":90220,"plusClickCount":0,"plusLandedFatigue":0,"provinceId":"24","skuId":"10024083045618","source_type":"search","source_value":"鼠标垫小号","townId":51707,"uAddrId":"0"}'''
data = {
"lmt": "0",
"body": body,
"": ""
}
jvmPath=jpype.getDefaultJVMPath()
d='unidbg_master_jar2/unidbg-master.jar'#对应jar地址
jpype.startJVM(jvmPath,"-ea","-Djava.class.path="+d+"")
JDClass=jpype.JClass("com.kdd.test.runliudq") //类目
jd=JDClass()
signature=jd.runJni(["wareBusiness", body, "uuid", "android", "9.2.2"])
url = url + "?" + urllib.parse.urlencode(params) + "&" + str(signature)
print(url)
response = requests.post(url, headers=headers, cookies=cookies, data=data)
print(response.text)
print(response)
jpype.shutdownJVM()
成功跑出结果
示例:com.du.du
From:https://zhuanlan.zhihu.com/p/111793677
案例来自 JXU2QkQyYXBwJTIwdjQuMTYuMA== 对于该 app 而言,是非常适合入门的一个app,未加固、算法简单、很容易找到 so 的 jni。
"毒unidbg" 文件放在:https://github.com/zhaoboy9692/dailyanalysis
这里重要的是目前利用 unidbg + springboot 做成 web 服务。
先去 github 下载 https://github.com/zhkl0228/unidbg,下载完毕用 idea 打开,等待 maven 下载完毕。创建好du的文件。
代码解析
package com.du;
import cn.banny.unidbg.Module;
import cn.banny.unidbg.arm.ARMEmulator;
import cn.banny.unidbg.linux.android.AndroidARMEmulator;
import cn.banny.unidbg.linux.android.AndroidResolver;
import cn.banny.unidbg.linux.android.dvm.*;
import cn.banny.unidbg.memory.Memory;
import java.io.File;
import java.io.IOException;
//毒app 4.16.0
public class du extends AbstractJni {
//ARM模拟器
private final ARMEmulator emulator;
//vm
private final VM vm;
//载入的模块
private final Module module;
private final DvmClass TTEncryptUtils;
//初始化
public du() throws IOException {
//创建毒进程,这里其实可以不用写的,我这里是随便写的,使用app本身的进程就可以绕过进程检测
emulator = new AndroidARMEmulator("com.du.du");
Memory memory = emulator.getMemory();
//作者支持19和23两个sdk
memory.setLibraryResolver(new AndroidResolver(23));
memory.setCallInitFunction();
//创建DalvikVM,利用apk本身,可以为null
//如果用apk文件加载so的话,会自动处理签名方面的jni,具体可看AbstractJni,这就是利用apk加载的好处
// vm = emulator.createDalvikVM(new File("src/test/resources/du/du4160.apk"));
vm = emulator.createDalvikVM(null);
//加载so,使用armv8-64速度会快很多
DalvikModule dm = vm.loadLibrary(new File("src/test/resources/du/libJNIEncrypt.so"), false);
//调用jni
dm.callJNI_OnLoad(emulator);
module = dm.getModule();
//Jni调用的类,加载so
TTEncryptUtils = vm.resolveClass("com/duapp/aesjni/AESEncrypt");
}
//关闭模拟器
private void destroy() throws IOException {
emulator.close();
System.out.println("destroy");
}
public static void main(String[] args) throws IOException {
du t = new du();
t.encodeByte("lastIdloginTokenplatformandroidsellTime201912timestamp1577459413370uuid7337c8189240625v4.16.0");
t.destroy();
}
private String encodeByte(String strs) {
//调试
// 这里还支持gdb调试,
//emulator.attach(DebuggerType.GDB_SERVER);
//附加调试器
// emulator.attach(DebuggerType.SIMPLE);
// emulator.traceCode();
//这里是打断点,原地址0x00005028->新地址0x40005028 新地址需要改成0x4
// emulator.attach().addBreakPoint(null, 0x40001188);//encode地址
// emulator.attach().addBreakPoint(null, 0x40000D10);
Number ret = TTEncryptUtils.callStaticJniMethod(emulator, "getByteValues()Ljava/lang/String;");
long hash = ret.intValue() & 0xffffffffL;
StringObject st1 = vm.getObject(hash);
//毒这里要处理下字符串
String byteString = st1.getValue();
StringBuilder builder = new StringBuilder(byteString.length());
for (int i = 0; i < byteString.length(); i++) {
if (byteString.charAt(i) == '0') {
builder.append('1');
} else {
builder.append('0');
}
}
//获取encodeByte地址
ret = TTEncryptUtils.callStaticJniMethod(emulator, "encodeByte(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
//传参,这里需要两个字符串,所以就传入两个参数
vm.addLocalObject(new StringObject(vm, strs)),
vm.addLocalObject(new StringObject(vm, builder.toString())));
//ret 返回的是地址,
hash = ret.intValue() & 0xffffffffL;
//或得其值
StringObject str = vm.getObject(hash);
System.out.println(str.getValue());
return str.getValue();
}
}
上边代码有 jni 的类是哪一个需要知道,就是下面这个类,这个其实是和加载so有关系的。 TTEncryptUtils=vm.resolveClass("com/*/aesjni/AESEncrypt");
我们需要逆向app,这里不细说如何在app中寻找加载so的类。如下图,encodeByte是该app调用native层加密的入口,loadLibrary是java加载so的方法,这个类就是上述代码中填写的。
然后再看 "encodeByte(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"这里,这是smali写法,不补基础,后面跟上需要传的参数, getByteValues 这个方法是毒获取的一个01字符串,并且在java层进行了处理,然后再传进 encodeByte里面, encodeByte这个方法最后获取的其实并不是最终需要的,需要md5才是最后的newSign。可以验证一下下。
测试结果通过。
最后,启动 java 文件时候注意这个改成自己的平台!!! VM options:-Djava.library.path=prebuilt/os -Djna.library.path=prebuilt/os Where os may: linux64, win32, win64, osx64
示例:unidbg 使用姿势
:https://blog.csdn.net/chl191623691/article/details/118415796
演示unidbg项目的导入、封装自己的调用so文件的API,其实这只是入门了,unidbg还支持断点调试so文件,也能导入到IDA中进行动态调试,自己去研究
使用 unidbg 调用 libbaseEncryptLib.so、libencryptLib.so 中的方法,这样就不用去逆向so文件了。
示例:unidbg 过混淆过的 arm64 程序初体验
:https://bbs.pediy.com/thread-268376.htm
示例:调用 native 方法、传不同的参数
:https://blog.csdn.net/xubaoyong/article/details/121750645
代码如下
package com.example.jnitest4.jni;
import android.content.Context;
public class JniManager {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
public native String str2str(String org,String append);
/**
* 获取uuid字符串
* @param type
* @return
*/
public native String uuid(int type);
/**
* 加密
*
* @param msg 原始字符串
* @param type 加密的方式
* @return 加密后的数据
*/
public native String encode(String msg, int type);
/**
* 解密
*
* @param msg 解密前的 字符串
* @param type 解密的方式
* @return 解密后的数据
*/
public native String decode(String msg, int type);
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
/**
* SHA1签名 --失败
*
* @param src
* @return
*/
public native String encodeBySHA1(byte[] src);
/**
* SHA224签名
*
* @param src
* @return
*/
public native String encodeBySHA224(byte[] src);
/**
* SHA384签名
*
* @param src
* @return
*/
public native String encodeBySHA256(byte[] src);
/**
* SHA256签名
*
* @param src
* @return
*/
public native String encodeBySHA384(byte[] src);
/**
* SHA512签名
*
* @param src
* @return
*/
public native String encodeBySHA512(byte[] src);
/**
* AES加密
*
* @param keys
* @param src
* @return
*/
public native byte[] encodeByAES(byte[] keys, byte[] src);
/**
* AES解密
*
* @param keys
* @param src
* @return
*/
public native byte[] decodeByAES(byte[] keys, byte[] src);
/**
* RSA公钥加密
*
* @param keys
* @param src
* @return
*/
public native byte[] encodeByRSAPubKey(byte[] keys, byte[] src);
/**
* RSA私钥解密
*
* @param keys
* @param src
* @return
*/
public native byte[] decodeByRSAPrivateKey(byte[] keys, byte[] src);
/**
* RSA私钥加密
*
* @param keys
* @param src
* @return
*/
public native byte[] encodeByRSAPrivateKey(byte[] keys, byte[] src);
/**
* RSA公钥解密
*
* @param keys
* @param src
* @return
*/
public native byte[] decodeByRSAPubKey(byte[] keys, byte[] src);
/**
* RSA私钥签名
*
* @param keys
* @param src
* @return
*/
public native byte[] signByRSAPrivateKey(byte[] keys, byte[] src);
/**
* RSA公钥验证签名 (未测试)
*
* @param keys
* @param src
* @param sign
* @return 1:验证成功
*/
public native int verifyByRSAPubKey(byte[] keys, byte[] src, byte[] sign);
/**
* 异或加解密
*
* @param src
* @return
*/
public native byte[] xOr(byte[] src);
/**
* MD5编码
*
* @param src
* @return 默认小写
*/
public native String md5(byte[] src);
/**
* HmacSHA1签名
*
* @param context
* @param src
* @return
*/
public native byte[] encodeByHmacSHA1(Context context, byte[] src);
/**
* 获取apk-sha1
*
* @param context
* @return
*/
public native String sha1OfApk(Context context);
/**
* 校验apk签名是否有效(未验证)
*
* @param context
* @return
*/
public native boolean verifySha1OfApk(Context context);
/**
* 字节测试用例
* @param bytes
* @return
*/
public native byte[] byteTestFn(byte[] bytes) ;
}
unidbg 实现代码
package com.jni4;
import com.github.unidbg.AndroidEmulator;
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.DvmObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.jni.ProxyClassFactory;
import com.github.unidbg.memory.Memory;
import java.io.File;
import java.io.IOException;
public class JniManagerUtil {
private final AndroidEmulator emulator;
private final DvmClass jniManagerUtil;
private final VM vm;
public JniManagerUtil() {
emulator = AndroidEmulatorBuilder.for64Bit()
.setProcessName("com.example.jnitest4")
.build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM();
vm.setDvmClassFactory(new ProxyClassFactory());
vm.setVerbose(false);
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/jnitest4/libnative-lib.so"), false);
jniManagerUtil = vm.resolveClass("com/example/jnitest4/jni/JniManager");
dm.callJNI_OnLoad(emulator);
}
public void destroy() throws IOException {
emulator.close();
}
/**
* 这里有个问题是使用callStaticJniMethodObject还是callJniMethodObject
*/
public void stringFromJNI(){
String methodStringFromJNI = "stringFromJNI()Ljava/lang/String;";
DvmObject<?> strRc = jniManagerUtil.callStaticJniMethodObject(emulator, methodStringFromJNI);
System.out.println("stringFromJNI返回值:"+strRc.getValue());
}
/**
* 这个例子的重点是参数是int类型,对应的参数类型标识为I
*/
public void UUIDTest(){
String methodStringFromJNI = "uuid(I)Ljava/lang/String;";
int paramInt = 15;
DvmObject<?> strRc = jniManagerUtil.callStaticJniMethodObject(emulator, methodStringFromJNI,paramInt);
System.out.println("UUIDTest返回值:"+strRc.getValue());
}
// public native String str2str(String org,String append);
public void str2strTest(){
String methodStringFromJNI = "str2str(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
String paramString0 = "hello ";
String paramString1 = "mr wang";
DvmObject<?> strRc = jniManagerUtil.callStaticJniMethodObject(emulator, methodStringFromJNI,paramString0,paramString1);
System.out.println("str2strTest返回值:"+strRc.getValue());
}
public void encode(){
String methodStringFromJNI = "encode(Ljava/lang/String;I;)Ljava/lang/String;";
String paramString0 = "hello ";
int paramString1 = 20;
DvmObject<?> strRc = jniManagerUtil.callStaticJniMethodObject(emulator, methodStringFromJNI,paramString0,paramString1);
System.out.println("encode返回值:"+strRc.getValue());
}
/**
* 学习重点:
* 1:这里重点是参数是byte数组,学习byte数组如何传参。
* 2:调用方法采用先new一个对象,然后再调用非静态方法来调用。
*/
public void encodeBySHA1(){
String methodStringFromJNI = "encodeBySHA1(B[;)Ljava/lang/String;";
String paramString0 = "99999 ";
// 方法1
// DvmObject<?> strRc = jniManagerUtil.callStaticJniMethodObject(emulator, methodStringFromJNI,paramString0.getBytes());
// 方法2:
DvmObject<?> strRc = jniManagerUtil.newObject(null).callJniMethodObject(emulator, methodStringFromJNI,paramString0.getBytes());
System.out.println("encodeBySHA1返回值:"+strRc.getValue());
}
public static void main(String[] args) throws Exception {
JniManagerUtil jniManagerUtil = new JniManagerUtil();
jniManagerUtil.stringFromJNI();
jniManagerUtil.UUIDTest();
jniManagerUtil.str2strTest();
jniManagerUtil.encode();
jniManagerUtil.encodeBySHA1();
jniManagerUtil.destroy();
}
}
frida、unidbg 写 hook 方法时的基本数据类型
参考:
Dalvik 可执行文件格式:https://source.android.google.cn/docs/core/dalvik/dex-format frida-java-bridge:https://github.com/frida/frida-java-bridge/blob/main/lib/types.js com.github.unidbg.linux.android.dvm.Shorty.java 调用 native 方法
使用方法:callStaticJniMethodObject 先 new 出对象,再调用方法 callJniMethodObject 传不同的参数:
调用无参函数 int类型参数 btye数组类型参数 多个String类型的参数 TypeDescriptor 各个变体的含义
语法 含义 V void;仅对返回类型有效 Z boolean B byte S short C char I int J long F float D double Lfully/qualified/Name; 类 fully.qualified.Name [descriptor descriptor的数组,可递归地用于“数组的数组”,但维数不能超过 255。
示例:完整流程分析
:https://blog.csdn.net/qq_39736559/article/details/126205037
一、Jadx 分析
首先用jadx打开apk文件,查看MainActivity可以发现,页面判断了MyApp.m这个类变量的值,并调用类work()这个函数,且当类变量m的值为0时会跳转到RegActivity注册页面
RegActivity界面比较简单,就是把输入的sn传入MyApp.saveSN()函数,然后退出,可以看出关键都在MyApp这个类。
所以我们继续查看MyApp这个类,发现类有三个native函数,所以需要进一步分析so文件
二、IDA PRO分析
(1)找关键函数
将对应的so文件拖到ida pro后通过Export栏可以发现有JNi_OnLoad函数,说明函数为动态注册,所以进入JNi_OnLoad函数查看注册的函数。
Tips: ida pro直接反编译的格式可能会很乱,这个时候可以把变量右键设置set item type设置成为JNIEnv*,然后许多函数都能解析出来类,就好看了很多(如果不知道设置哪一个变量,就把能试试的都试一便,总有一个能行_)
通过分析Onload函数可以发现注册函数在off_5044这个位置上,点击跳转后发现注册的函数名字符串找到了,但是函数名却是n1,n2,n3,可以对这些函数重命名,这样好看一点儿。
由此三个关键函数,work(), initSN(), saveSN()就找到了。
(2)分析关键函数 - work()函数分析 进入work函数,将传入的变量类型右键设置set lvar type设置为JNIEnv*后,F5反编译如下,可以看到该函数用getValue的方法获取了MyApp.m的值(getValue 函数用同样的方法进行反编译),然后将unk_2EFB或unk_2F25处的值赋给了V3,其中unk_2F25处的值啥也看不到,unk_2EFB处的值能看到有flag字样,应该和flag有关。最后该函数调用类callWork函数(反编译了一下,暂时没看懂,不过不重要)
因此work函数的主要逻辑就是判断MyApp.m的值是否为1,如果为1则赋值对应地址的值给V3,然后调用callWork。
- initSN()函数分析 进入initSN()函数分析其逻辑为:读取reg.dat的内容,如果内容为"EoPAoY62@ElRD",则MyApp.m的值设置为1,否则为0
- saveSN()函数分析
进入saveSN()函数分析,首先修改变量类型,使得反编译更加人性化,一般变量第一个为JEIEnv*, 第二个参数jobject或者jclass, 后面的参数就是传入的native 函数中传入的参数,依次修改尝试就行。
通过分析代码,v10为数组的索引,从0-sn的长度,依次增长,然后将v10的值会在字符串的指定位置取一个值来与sn对应索引位置的字符串作异或运算。所以可以看出逻辑应该为,输入的sn的每一位和字符串"W3_arE_whO_we_ARE"的固定位置的字符进行了异或运算,然后输出到V8上,最后使用f_puts函数保存到文件中。(至于在"W3_arE_whO_we_ARE"取了那几位,不重要,反正异或运算可逆)
(3)整体流程思路 通过分析三个函数,可以看出该程序的整体调用思路为,work->initSN->saveSN,逻辑思路为:
(1) work函数:判断MyApp.m的值是否为1,然后赋值对应地址的值给v3 (2) initSN函数:判断reg.dat的内容是否为 “EoPAoY62@ElRD”,若是则MyApp.m赋值为1 (3) saveSN函数: 将输入的sn与"W3_arE_whO_we_ARE"做异或运算后保存到reg.dat中 通过jadx可以看出只有当MyApp.m的值为1时才算已注册,所以reg.dat的内容应该为"EoPAoY62@ElRD",而reg.dat的内容是根据输入sn与字符串"W3_arE_whO_we_ARE"通过异或的算法得出的,因此只要将"EoPAoY62@ElRD"与字符串"W3_arE_whO_we_ARE"做异或运算的算法,也能得出我们应该输入的sn,及输入"EoPAoY62@ElRD"进行注册就能得到应该输入的sn。
unidbg 脚本编写
通过编写unidbg脚本,需要实现的函数有
saveSN: 主动调用saveSN函数,传入sn参数 f_puts: saveSN函数中的子函数,hook该函数可以得到运算后的字符串 work: 打印work中的地址unk_2EFB查看是否有提示 整体代码如下
package com.hack;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.HookZz;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import com.sun.jna.JNIEnv;
import com.sun.jna.Pointer;
import unicorn.ArmConst;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class hack extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private DvmClass cNative;
private hack () {
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.test").build();
final Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/hack/hack.apk"));
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/hack/libmyjni.so"), true);
vm.setJni(this);
vm.setVerbose(true);
dm.callJNI_OnLoad(emulator);
module = dm.getModule();
}
@Override
public void setStaticIntField(BaseVM vm, DvmClass dvmClass, String signature, int value) {
switch (signature) {
case "com/gdufs/xman/MyApp->m:I":
System.out.println("> Patched: com/gdufs/xman/MyApp->m:I");
return;
}
super.setStaticIntField(vm, dvmClass, signature, value);
}
@Override
public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature) {
case "com/gdufs/xman/MyApp->m:I":
System.out.println("> Patched: com/gdufs/xman/MyApp->m:I");
return 0;
}
return super.getStaticIntField(vm, dvmClass, signature);
}
@Override
public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "com/gdufs/xman/MainActivity->()V":
System.out.println("> Patched: com/gdufs/xman/MainActivity->()V");
return vm.resolveClass("com/gdufs/xman/MainActivity").newObject(null);
}
return super.newObject(vm, dvmClass, signature, varArg);
}
@Override
public void callVoidMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
switch (signature) {
case "com/gdufs/xman/MainActivity->work(Ljava/lang/String;)V":
System.out.println("> Patched: com/gdufs/xman/MainActivity->work(Ljava/lang/String;)V");
return;
}
super.callVoidMethod(vm, dvmObject, signature, varArg);
}
public static void main(String[] args) {
hack test = new hack();
test.hookPuts();
test.hookWork();
test.saveSN();
test.work();
}
private void saveSN() {
List list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
list.add(vm.addLocalObject(new StringObject(vm, "201608Am!2333"))); // arg 3
Number number = module.callFunction(emulator, 0x000011F8+1, list.toArray());
}
private void work() {
DvmClass dvmClass = vm.resolveClass("com/gdufs/xman/MyApp");
String methodSign = "work()V";
DvmObject<?> dvmObject = dvmClass.newObject(null);
DvmObject ret = dvmObject.callJniMethodObject(emulator, methodSign);
Pointer pointer = emulator.getMemory().pointer(module.base + 0x00002EEB);
System.out.println("> Pointer:"+pointer.getString(0x10));
}
private void hookPuts() {
// hook saveSN中的f_puts函数
HookZz hook = HookZz.getInstance(emulator);
hook.replace(module.base + 0x00002C3C+1, new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
System.out.println("> onCall:f_puts()");
System.out.println("> arg0:"+context.getPointerArg(0).getString(0)); // 入参1 R0寄存器
return super.onCall(emulator, context,originFunction);
}
}, true);
}
private void hookWork() {
HookZz hook = HookZz.getInstance(emulator);
hook.replace(module.base + 0x000014AC, new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
System.out.println("onCall work");
System.out.println(context.getPointerArg(0).getString(0)); // 入参1 R0寄存器
return super.onCall(emulator, context,originFunction);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
System.out.println("postCall work");
System.out.println(context.getPointerArg(0).getString(0)); // 入参1 R0寄存器
super.postCall(emulator, context);
}
}, true);
}
}
运行结果如下
根据结果可以发现 work 中的函数是提示 flag 格式的,格式为 xman{……},而且输入的 sn 即是flag,然后我们本应该输入的sn由异或运算可以得出为。
所以最终 flag 为:xman{201608Am!2333}
Unidbg + Web = Unidbg-server
:http://91fans.com.cn/post/unidbgweb/
手把手教你搭个签名服务器
把so用unidbg跑起来后,就可以通过 web 服务对外提供 API 接口
集成 springboot 运行 unidbg 的方案::https://github.com/cxapython/unidbg-server
git clone 下来,然后导入到 idea,然后、编译、运行……。
跑一下作者提供的例子:python3 send.py
一套行云流水,顺利跑通。 简直开箱即用。
只下载了Unidbg-server的代码,并没有下载Unidbg的代码?为啥直接就能跑起来?
奥秘在pom.xml里面,加载了线上的unidbg模块,所以可以直接跑起来。
不加载线上的Unidbg的代码,而加载我们本机修改过的版本?
首先把我们定制版的unidbg编译成jar包,参考 http://91fans.com.cn/post/unidbgone/ 在 Unidbg-server工程的根目录下(和pom.xml同级目录)新建 libs 目录
把定制版的unidbg编译生成的一堆jar包拷进去
// 删除这两个段, 不使用线上的unidbg com.github.zhkl0228 unidbg-api 0.9.0 com.github.zhkl0228 unidbg-android 0.9.0
// 增加这一个段,使用本地的unidbg unidbg unidbg 0.9.5 system ${project.basedir}/libs/unidbg-parent.jar
然后在左侧工程窗口点右键 Maven → Reload project 重新编译下。 报错了,我就知道不会这么顺利。 有点慢?再优化一把
生产环境下的性能瓶颈可能在unidbg的模拟器初始化上,我们可以只初始化一个模拟器,然后每次做签名的时候只需要调用指定的函数就行。
在controller目录下创建一个 FenfeiController.java
public class FenfeiController { public static DouyinSign instance;
static { instance = new DouyinSign(); }
@RequestMapping(value="dySignEx",method = {RequestMethod.GET,RequestMethod.POST}) @ResponseBody public String dySign(@RequestParam("url") String url) { Map result= instance.crack(url); String jsonString = JSON.toJSONString(result);
return jsonString; } }
这样模拟器只初始化了一次,感觉能快一些了。
不过又引入了一个新问题,模拟器是被共享了,并发的时候是会出问题的,这也难不倒我们,加个锁就行了
public String dySign(@RequestParam("url") String url) { synchronized (this) { Map result = instance.crack(url); String jsonString = JSON.toJSONString(result);
return jsonString; } }
可以通过application.properties自行修改服务的地址和端口, 目前我使用的结果是只改端口就行,ip地址就保持 0.0.0.0就可以了。
开源程序的版本搭配也很重要,发现和最新代码不兼容的时候,可以研究下回退一两个版本。
github 上 unidbg 项目
:https://github.com/search?q=unidbg
unidbgweb
:https://github.com/zhaoboy9692/unidbgweb
unidbg的服务化,毒、酷安、快手、小红书、马蜂窝、抖音、今日头条、美团、拼多多、启信宝、天眼查、封面新闻的相关so调用
unidbg_api
脱离安卓手机调用第三方.so文件,集成了spring boot框架提供web服务 可打成jar包一键部署
unidbg-local-server
:https://github.com/gl953236368/uls
小红书 最右 拼多多 携程 bilibili 轻小说 美团 ...... 懂车帝
Unidbg 调试 浮点数 运算
Unidbg 代码同步到官方最新版,最新版已经支持浮点寄存器的显示了。
:http://91fans.com.cn/post/unidbgreturnone/
在做代码还原的时候,经常能看到一些奇怪的寄存器和奇怪的指令:
vldr s15, [r1]
vadd.f32 s15, s14, s15
很像某些流量明星,看上去很眼熟,仔细看看又不认识。
它们就是传说中的浮点数运算,今天我们来点亮一个很有用的技能树: Unidbg调试浮点数运算
先写个floatdemo
有这么一个祖传的算法函数。
extern "C" JNIEXPORT jstring JNICALL Java_com_fenfei_app_floatdemo_MainActivity_stringFromJNI( JNIEnv* env, jobject Obj, jdouble value) { std::string hello = "Hello from C++";
double p=3.14159; double s,v,rc;
v = 2*p*value; s = p*value*value;
rc = v+s;
hello = std::to_string(rc);
return env->NewStringUTF(hello.c_str()); }
算出圆的周长和面积,然后再把它们相加。
高级语言就是好,一目了然。
IDA 打开 so
可以看出两个区别, 一个是寄存器不一样,普通运算使用的寄存器是R0-Rx,浮点数运算使用的是D0-Dx (其实还有 S0-Sx),另一个是指令不一样,普通运算是MOV、MUL,而浮点数运算使用的是VMOV,VMUL,感觉就是普通运算的VIP版。
第一个知识点就出来了,V 开头的指令就是浮点数运算指令,Dx Sx Qx 就是浮点数寄存器。
使用 unidbg 把编译的 floatdemo.apk 跑起来,然后增加一个 stringFromJNI 函数的调用。
private String callfun(String methodSign, Object ...args) { DvmObject mainactivity = MainActivity_dvmclass.newObject(null); Object value = mainactivity.callJniMethodObject(emulator,methodSign,args).getValue(); return value.toString(); }
由于 stringFromJNI 不是静态(static)的类函数,所以我们需要先创建个一个 MainActivity 对象,才可以调用它的方法。
先跑一下看看结果
Find native function Java_com_fenfei_app_floatdemo_MainActivity_stringFromJNI(D)Ljava/lang/String; => RX@0x4000c6c9[libnative-lib.so]0xc6c9 JNIEnv->NewStringUTF("150.796320") was called from RX@0x4000c73d[libnative-lib.so]0xc73d ret:150.796320 emulator destroy...
我们传了个参数6,半径是6的圆, 周长是 37.699, 面积是113.097 ,它们之和是 150.796。 结果没毛病,那我们开始调试了。
Unidbg 调试
从刚才运行的结果里我们知道 stringFromJNI 函数的地址在 0xc6c9, 那么我们现在需要在这个地址下个断点,让调试器停在这个地址。
Unidbg的调试功能依然很强大,它支持三种调试模式 CONSOLE、GDB和IDA,目前我用的顺手的是 CONSOLE 模式,今天先介绍这个。
开启调试炒鸡简单,加上这两行代码就行
Debugger MyDbg = emulator.attach(DebuggerType.CONSOLE); MyDbg.addBreakPoint(module.base + 0xc6c9);
运行一下,就顺利的进入到调试器命令行了,直接回车,会显示目前支持的调试命令。
新手嘛,先掌握一个n和s两个命令就行了,n是单步步过,就是执行一条指令,步过函数调用;s是单步步入,就是执行一条指令,进入函数调用。
n命令跑几下来到我们要分析的浮点数运算的位置,发现尴尬了……
Unidbg调试器只显示了Rx寄存器,没有显示Dx系列的寄存器,这下怎么分析,不能盲摸呀?
打开 Unidbg 浮点数寄存器显示
Unidbg是支持浮点数运算模拟的,那么一定是有地方去读取浮点数寄存器的,只是没有显示出来而已。
我们先分析下Unidbg调试时寄存器显示部分的代码。
先搜索 r0= 在哪里处理的?
showRegs 就是显示寄存器, 当参数为null的时候,通过 ARM.getAllRegisters 来显示所有的寄存器。但是为啥没有显示浮点寄存器呢?奇怪。
我们再往下翻,发现在ARM64的模拟下显示了Q0-Q31寄存器,通过查阅资料,我们知道了原来它们都是一伙的。
那ARM32先放一下,我们把模拟环境切换到ARM64
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.fenfei.runfloatdemo").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
再跑一下,调试器没有激活?
这是为什么? 原来我们把模拟器从Arm32切换到了Arm64,那么载入的so就是64位的了,所以 stringFromJNI 函数的地址也变了,需要把断点下在新的地址 0x12738 上面。
这下不一样了,浮点寄存器都显示出来了。
优化 浮点寄存器的显示
这个0x400921f9f01b866e是啥意思呀,你是不是搞错了,浮点数寄存器显示的咋不是 3.14159 ,而是这个乱七八糟的数据?
程序员的母语就是16进制,没有一眼把 0x400921f9f01b866e 认出是 3.14159的,晚饭是不配加鸡腿的,也不配变秃的。
有理想的同学请自行搜索 IEEE754 二进制浮点数算术标准
其他的同学请和我一起优化下浮点寄存器的显示。
由于飞哥目前为止还没有变秃,确实也看不出来这玩意就是 3.14159, 只好另辟蹊径,给大家传授一个神奇的函数:
public static double bytes2Double(byte[] arr) { long value = 0; for (int i = 0; i < 8; i++) { value |= ((long) (arr[i] & 0xff)) << (8 * i); } return Double.longBitsToDouble(value); }
// 在showRegs64函数里面加个显示 case Arm64Const.UC_ARM64_REG_Q0: byte[] data = backend.reg_read_vector(reg);
double bOut = bytes2Double(data);
if (data != null) { builder.append("\n>>>"); builder.append(String.format(Locale.US, " q0=0x%s(%.3f)", newBigInteger(data).toString(16),bOut)); } break;
其实C/C++ 有个神奇的玩意叫指针,这种显示一把梭就行
BYTE dPiByte[8] = {0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40 }; double dPi = *(double*)dPiByte;
好了,后面的几步运算就是乘乘加加了,同学你自己n几下就ok了。
总结
为什么要去调试,直接F5大法不香吗?
现在Ollvm肆虐,掌握一些手撕汇编的良方可保你无忧。
为什么要用Unidbg去调试,IDA不香吗?
悟空,等你遇上内存防修改,无法下软件断点和一些BT的反调试的时候,你自会回来和为师唱这首歌的: Only You ……
条件断点
:http://91fans.com.cn/post/unidbgreturntwo/
进阶学习
某电商App sign算法升级验证
Mac 10.14.x 下Android 10源码(QP1A.190711.020)编译和刷机 (Pixel 2 XL)
AndroidNativeEmu中Hook gettimeofday和lrand48来验证签名值
算法还原的助手(一) 先让时间停下来
AndroidNativeEmu模拟执行计算出某电商App sign
某电商App反反调试
Unicorn反混淆:恢复被OLLVM保护的程序(一)
Unicorn用法示例(二)
Unicorn用法示例(一)
如何保护你的代码 - Ollvm(一)
小程序逆向分析 (二) 跑起来
小程序逆向分析 (一)
IDA F5 增强插件: I Have a Dream (二)
IDA F5 增强插件,还我源代码(一)
借你一双慧眼, Frida Native Trace
frida调试不了怎么办?着急,在线等!
Frida-syscall-interceptor
一通操作猛如虎 合并Unidbg的更新,继续跑sign
Sekiro + Xposed 签名解决方案
声明
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。
在线投稿:投稿 站长QQ:1888636