unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用

马肤

温馨提示:这篇文章已超过410天没有更新,请注意相关的内容是否还可用!

摘要:,,本文介绍了unidbg,一个开源的动态二进制分析工具。文章简要概述了它的简介和基本使用方式。还详细描述了如何使用unidbg调用共享库(so)中的方法。本文还提到了unidbg-web,一个基于Web的图形界面工具,用于更方便地使用unidbg进行二进制分析。

爬虫工程师的unidbg入门教程:https://www.cnblogs.com/xbjss/p/12110083.html

日常逆向分析的app:https://github.com/zhaoboy9692/dailyanalysis

分析unidbg(unidbgMutil)多线程机制:https://bbs.kanxue.com/thread-266999.htm

逆向调试时还是 IDA 的图形化界面更方便,一般首选 IDA 调试分析,后期要在生产线上生成 sign 字段,这时再用 unidbg 就更合适了!

1、调用 so 库中函数的一些方式

  • frida 的 rpc
  • xposed+andserver
  • unicorn+web 框架
  • unidbg ( 基于 Unicorn 引擎 )
  • qiling ( 基于 Unicorn 引擎 )
  • AndroidNativeEmu
  • 等等。

    2、Unidbg 简介

    Unidbg 是什么

    unidbg 是一个标准的 java 项目,是一款基于 unicorn 和 dynarmic 的逆向工具。可以直接黑盒调用 Android 和 IOS 的 so 文件,无论是黑盒调用 so 层算法,还是白盒 trace 输出 so 层寄存器值变化都是一把利器~ 尤其是动态 trace 方面堪比 ida trace。

    unidbg github 地址:https://github.com/zhkl0228/unidbg

    做脱机协议,首先要找到关键的加密代码,然而这些代码一般都在so里面,因为逆向c/c++的难度远比java大多了!找到关键代码后,一般情况下是逐行分析,然后自己写代码复现整个加密过程。但是,有些非标准的加密算法是由一个团队实现的,整个过程非常复杂。逆向人员再去逐行分析和复现,有点“不划算”!怎么才能直接调用so里面的这些关键代码了?可以通过前面的介绍的frida hook,也可以通过今天介绍的这个so的模拟框架--unidbg!

    Unidbg 特色

    • 模拟 JNI 调用 API,以便可以调用 JNI_OnLoad。
    • 支持 JavaVM,JNIEnv。
    • 模拟 syscalls 调用。
    • 支持 ARM32 和 ARM64。
    • 基于 HookZz 实现的 inline hook。
    • 基于 xHook 实现的 import hook。
    • 支持 iOS fishhook 、 substrate 、 whale hook。
    • 支持简单的控制台调试器,gdb,IDA android 调试器服务器,指令跟踪,内存读/写跟踪。
    • 支持 iOS objc 和 Swift 运行时。

      unidbg 基于以下项目

      • unicorn
      • dynarmic
      • HookZz
      • xHook
      • AndroidNativeEmu
      • usercorn
      • keystone
      • capstone
      • idaemu
      • jelf
      • whale
      • kaitai_struct
      • fishhook
      • runtime_class-dump
      • mman-win32

        看着很多,实际并不复杂。开发android app,在Android studio配置好各种环境和参数后是能直接在java层调用so层函数的。那么在unidbg,也能实现同样的功能:即调用so层的函数!这也是unidbg最核心的功能之一了!

        keystone-engine 汇编框架

        :https://github.com/keystone-engine/keystone

        keystone-engine 是⼀个开源的轻量级多平台、多架构汇编框架,⽀持 Arm, Arm64 (AArch64/Armv8), Hexagon, Mips, PowerPC, Sparc, SystemZ、 X86 (16/32/64bit).  ⾮常强⼤!!

        pip 安装:pip install keystone-engine

        源码可以在Linux、Windows下顺利编译,自带一个kstool用于演示。

        利用Keystone,很简单,大致这么几步:

        ks_open()   指定CPU

        ks_asm()    指定基址、汇编指令

        printf()    输出机器码

        ks_free()   释放动态分配的机器码空间

        ks_close()  关闭

        unidgb 有3 种调试模式,源码写得很清楚,分别是

        • CONSOLE
        • GDB_SERVER
        • ANDROID_SERVER_V7

          Unidbg 有什么用

          现在许多 app 把签名算法已经放到了 so 文件中,所以要想破解签名算法,必须能够破解 so 文件。但是 C++ 的逆向远比 Java 的逆向要难得多了,所以好多时候是没法破解的,那么这个时候还可以采用 hook 的方法,直接读取程序中算出来的签名,但是这样的话,需要实际运行这个应用,需要模拟器或者真机,效率又不是很高。unidbg 就是一个很巧妙地解决方案,他不需要直接运行 app,也无需逆向 so 文件,而是通过在 app 中找到对应的 JNI 接口,然后用 unicorn 引擎直接执行这个 so 文件,所以效率也比较高。

          现在很多的 app 使用了so加密,可能你会直接破解 so 进行算法破解,但是也可以不用破解so,利用很多大佬写好的轮子即可 直接调用 so 中的函数方法。

          Frida + IDA 动静态 分析流程

          使用 Frida + IDA 的动静态分析中,流程是这样的

          • 找线索 ---> Frida Hook验证 ---> 验证成功继续下一步 / 验证失败继续找线索

            在以 Frida 为中心的逆向分析中,写 hook 代码是工作重心

            Unidbg 模拟执行、算法还原 流程

            以 Unidbg 为中心去做模拟执行/算法还原时,流程是这样的

            • Frida 主动调用获取一份正确结果
            • Unidbg写代码尝试运行→Unidbg给出报错→补环境
            • 循环往复,最后得到和Frida主动调用一致的结果

              以 Unidbg 为中心的逆向分析里,补环境 是工作重心。

              Unidbg 补环境

              Unidbg 中的补环境,大体上可以分成两类,运行环境缺失和上下文缺失,

              • 上下文缺失则是由于样本在运行目标函数前对SO或目标函数做了一些初始化工作,Unidbg中对目标函数单独运行,自然就导致了上下文缺失。上下文缺失相较于运行环境缺失,是一种更加隐蔽的环境缺失。
              • 运行环境缺失。最简单的例子就是目标函数中通过 JNI 调用到了某个自己的 JAVA 方法,Unidbg 会及时报错,给出堆栈以及这个 JAVA 方法的签名,需要我们补上对应的 JAVA 方法,

                补 JAVA 环境是补运行环境中主要的一部分,但是,补 JAVA 环境并不是工作的全部。还有哪些环境需要补呢?

                • 文件读写——对linux虚拟文件的读写,对ASSETS资源文件的读写、对app目录下文件的读取,对 Sharedpreference 的读取等等
                • 系统调用具体实现——比如popen函数所涉及的系统调用等
                • 系统库SO,Unidbg并没有,实际上也不可能模拟完整的 Android 系统 SO 环境,有的 SO 所依赖的 SO 比较多,很难调起来,所以 Unidbg 设计了 VirtualModule(虚拟SO模块),有时候我们需要和它打交道。
                • 补不了,打 patch

                  Unidbg 在大多数情况下是好的方案

                  在补环境的过程中,对算法细节有了一定的理解,而且用 Unidbg 跑出结果后,分析和还原算法会更快,因为 Unidbg 的 code trace / hook / console debugger 相比较 IDA trace,Frida hook 更快、更稳定、更方便复现。而且环境完全是我们的,有绝对的掌控。

                  Unidbg 并不是过往某个工具的替代品,而是 IDA、Frida 以及其工具套件的互补品。它和过去的工具结合在一起 1+1>2

                  Unidbg 环境配置、使用

                  Unidbg 环境配置

                  unidbg 项目用 Java 编写,并且官网下载的下来的代码使用的是标准的 maven 构建的,所以在使用 unidbg 之前需要修改先安装好 JDK 环境和 Maven 环境。将下载的 unidbg-master.zip 进行解压,然后使用 IDEA 导入项目。【File】 –> 【New】–> 【Project from Existing Sources】 

                  ​1、IntelliJ IDEA。官网:https://www.jetbrains.com.cn/idea/

                  2、Maven 环境。 官网:https://maven.apache.org/download.cgi

                          解压文件:tar -zxvf apache-maven-3.8.7-bin.tar.gz

                          配置环境变量:

                          export MAVEN_HOME=/usr/local/apache-maven-3.8.7

                          export PATH=$MAVEN_HOME/bin:$PATH

                          测试是否配置成功:mvn -version

                  MAVEN_HOME : D:\Software\apache-maven-3.8.7

                  PATH : %MAVEN_HOME%\bin;

                  MAVEN_OPTS : -Xms128m -Xmx512m -Duser.language=zh -Dfile.encoding=UTF-8

                  unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第1张

                  idea 配置 maven

                  unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第2张

                  导入代码:git clone https://github.com/zhkl0228/unidbg.git

                  基本使用步骤

                  下载 unidbg 框架,​框架的目录 unidbg-android/src/test/java 放置了很多示例,足以支撑入门

                  简单罗列了一下基本的使用, 十分好上手~!

                  // 创建模拟器实例,建议使用实际进程名,可以规避进程名校验

                  emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.xxx.xxx").build();

                  // 创建模拟器内存接口

                  final Memory memory = emulator.getMemory();

                  // 设置系统类库解析

                  memory.setLibraryResolver(new AndroidResolver(23));

                  // 创建 Android 虚拟机,传入 APK,unidbg 可以协助做一部分签名工作

                  vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/xxx/xxx.apk"));

                  // 加载 so 到虚拟内存,第二个参数的意思表示是否执行动态库的初始化代码

                  DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xxx/libxxx.so"),true);

                  // 获取 so 模块的句柄

                  module = dm.getModule();

                  // 设置 JNI 

                  vm.setJni(this);

                  // 打印日志

                  vm.setVerbose(true);

                  // 调用 JNI_Onload

                  dm.callJNI_OnLoad(emulator);

                  // 构建函数参数格式

                  List 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 使用, 更香

                  示例:代码分析

                  入口点

                  unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第3张

                  第一步: 补环境 跟踪 TTEncrypt 函数,注释写的很清楚了,不做过多分析。基本套路都是这个样子。

                  unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第4张

                  第二步: HOOK 相关的函数 跟踪 ttEncrypt,可知代码 hook 了 ssencrypt 和 ssencrypted_size 两个函数。

                  第三步: 添加调试及主动调用

                  第四步: 销毁环境。跟踪 destroy 

                  运行结果。如下图所示,运行成功就代表成功

                  unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第5张

                  这个时候按 c 继续,可以看到 hook 的结果以及JNI调用细节。

                  unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第6张

                  单步调试,ida_server 的 Debug方式相对简单,对于 unidbg 的强大之一在于它的单步调试-- Console Debugger,例子是以抖音作为例子的,还是很不错的。注释都写的比较清楚了。unidbg单步调试做的很棒,这个弥补了 frida 调试能力比较弱的缺点。

                   此示例就是对如下目录中的 so 进行操作。

                  unidbg-android/src/test/resources/example_binaries/libttEncrypt.so

                  查找 so 中的 sbox0、sbox1 导出符号,并打印其内存数据。

                  Hook一些函数,使用多个框架。

                  调用 so 中的 ttEncrypt 静态注册函数。

                  运行结果如下图所示:

                  unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第7张

                  打包成 jar 文件:https://www.jianshu.com/p/59e08e48ac20

                  打包成 jar 包

                  调用so 的有一个可执行程序呀,总不能让别人机器上也装个IDEA来陪你玩吧?

                  File ---> Project Structure …​ 然后选择 Artifacts,点加号 Add ---> jar ---> From modules with dependencies…​ 然后如下配置, 别忘了勾上 Include tests

                  unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第8张

                  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层的这个方法是映射的:

                  unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第9张

                  进入里面调用的各个函数仔细分析,发现这个函数还是比较简单:先接受传入的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层的普通方法然后调用,这该怎么办了?

                    unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第10张

                    上面说了: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。

                      unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第11张

                      代码如下:

                      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();

                        输出结果:

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第12张

                        看看 trace log,建议搭配 010 editor 使用, 更好用

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第13张

                        创建 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 等类似,如下:   

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第14张

                        比如 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  
                         
                         
                        

                        4、Unidbg 调用so文件

                        示例:Unidbg 调用so文件生成京东 sign 参数

                        ​案例学习,模拟调用so文件生成京东sign参数:https://blog.csdn.net/qq_44628911/article/details/127322805

                        抓包 "商品详情页",要模拟的是sign参数

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第15张

                        搭建基础框架代码:

                        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;
                            }

                        运行有报错补代码

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第16张

                            @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);
                            }

                        报错,补代码

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第17张

                            @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);
                            }

                        报错,补代码

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第18张

                            @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();

                        后面有报错也是跟着报错补环境。最后成功运行出结果:

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第19张

                        全部代码如下:

                        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.) 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

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第20张

                        点击 ok 后,Build ---> Build Artifacts 进行编译,编译成功后会生成很多jar文件

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第21张

                        在控制台测试运行下:java -jar unidbg-master.jar

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第22张

                        运行出了结果,证明打包的没问题

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第23张

                        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()

                        成功跑出结果

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第24张

                        示例: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的文件。

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第25张

                        代码解析

                        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  
                        

                        上边代码有 jni 的类是哪一个需要知道,就是下面这个类,这个其实是和加载so有关系的。

                        TTEncryptUtils=vm.resolveClass("com/*/aesjni/AESEncrypt");

                        我们需要逆向app,这里不细说如何在app中寻找加载so的类。如下图,encodeByte是该app调用native层加密的入口,loadLibrary是java加载so的方法,这个类就是上述代码中填写的。

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第26张

                        然后再看 "encodeByte(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"这里,这是smali写法,不补基础,后面跟上需要传的参数, getByteValues 这个方法是毒获取的一个01字符串,并且在java层进行了处理,然后再传进 encodeByte里面, encodeByte这个方法最后获取的其实并不是最终需要的,需要md5才是最后的newSign。可以验证一下下。

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第27张

                        测试结果通过。

                        最后,启动 java 文件时候注意这个改成自己的平台!!!

                        VM options:-Djava.library.path=prebuilt/os -Djna.library.path=prebuilt/os

                        Where os may: linux64, win32, win64, osx64

                        unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第28张

                        示例: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注册页面

                              unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第29张

                              RegActivity界面比较简单,就是把输入的sn传入MyApp.saveSN()函数,然后退出,可以看出关键都在MyApp这个类。

                              unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第30张

                              所以我们继续查看MyApp这个类,发现类有三个native函数,所以需要进一步分析so文件

                              unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第31张

                              二、IDA PRO分析

                              (1)找关键函数

                              将对应的so文件拖到ida pro后通过Export栏可以发现有JNi_OnLoad函数,说明函数为动态注册,所以进入JNi_OnLoad函数查看注册的函数。

                              unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第32张

                              Tips: ida pro直接反编译的格式可能会很乱,这个时候可以把变量右键设置set item type设置成为JNIEnv*,然后许多函数都能解析出来类,就好看了很多(如果不知道设置哪一个变量,就把能试试的都试一便,总有一个能行_)

                              通过分析Onload函数可以发现注册函数在off_5044这个位置上,点击跳转后发现注册的函数名字符串找到了,但是函数名却是n1,n2,n3,可以对这些函数重命名,这样好看一点儿。

                              unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第33张

                              由此三个关键函数,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函数(反编译了一下,暂时没看懂,不过不重要)

                              unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第34张

                              因此work函数的主要逻辑就是判断MyApp.m的值是否为1,如果为1则赋值对应地址的值给V3,然后调用callWork。

                              - initSN()函数分析

                              进入initSN()函数分析其逻辑为:读取reg.dat的内容,如果内容为"EoPAoY62@ElRD",则MyApp.m的值设置为1,否则为0

                              unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第35张

                              - saveSN()函数分析

                              进入saveSN()函数分析,首先修改变量类型,使得反编译更加人性化,一般变量第一个为JEIEnv*, 第二个参数jobject或者jclass, 后面的参数就是传入的native 函数中传入的参数,依次修改尝试就行。

                              unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第36张

                              通过分析代码,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);
                                      }
                                  }
                                  

                                  运行结果如下

                                  unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第37张

                                  根据结果可以发现 work 中的函数是提示 flag 格式的,格式为 xman{……},而且输入的 sn 即是flag,然后我们本应该输入的sn由异或运算可以得出为。

                                  所以最终 flag 为:xman{201608Am!2333}

                                  Unidbg + Web = Unidbg-server

                                  :http://91fans.com.cn/post/unidbgweb/

                                  手把手教你搭个签名服务器

                                  把so用unidbg跑起来后,就可以通过 web 服务对外提供 API 接口

                                  • unidbg
                                  • Unidbg-server

                                    集成 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 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第38张

                                      把定制版的unidbg编译生成的一堆jar包拷进去

                                      • 修改 pom.xml

                                        // 删除这两个段, 不使用线上的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

                                            unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第39张

                                            可以看出两个区别, 一个是寄存器不一样,普通运算使用的寄存器是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);

                                            运行一下,就顺利的进入到调试器命令行了,直接回车,会显示目前支持的调试命令。

                                            unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第40张

                                            新手嘛,先掌握一个n和s两个命令就行了,n是单步步过,就是执行一条指令,步过函数调用;s是单步步入,就是执行一条指令,进入函数调用。

                                            n命令跑几下来到我们要分析的浮点数运算的位置,发现尴尬了……

                                            Unidbg调试器只显示了Rx寄存器,没有显示Dx系列的寄存器,这下怎么分析,不能盲摸呀?

                                            打开 Unidbg 浮点数寄存器显示

                                            Unidbg是支持浮点数运算模拟的,那么一定是有地方去读取浮点数寄存器的,只是没有显示出来而已。

                                            我们先分析下Unidbg调试时寄存器显示部分的代码。

                                            先搜索 r0= 在哪里处理的?

                                            unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第41张

                                            showRegs 就是显示寄存器, 当参数为null的时候,通过 ARM.getAllRegisters 来显示所有的寄存器。但是为啥没有显示浮点寄存器呢?奇怪。

                                            我们再往下翻,发现在ARM64的模拟下显示了Q0-Q31寄存器,通过查阅资料,我们知道了原来它们都是一伙的。

                                            unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第42张

                                            那ARM32先放一下,我们把模拟环境切换到ARM64

                                            emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.fenfei.runfloatdemo").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分

                                            再跑一下,调试器没有激活?

                                            这是为什么? 原来我们把模拟器从Arm32切换到了Arm64,那么载入的so就是64位的了,所以 stringFromJNI 函数的地址也变了,需要把断点下在新的地址 0x12738 上面。

                                            unidbg 简介、基本使用、调用so中方法、unidbg-web,unidbg简介及基本使用,调用so中方法与unidbg-web应用 第43张

                                            这下不一样了,浮点寄存器都显示出来了。

                                            优化 浮点寄存器的显示

                                            这个0x400921f9f01b866e是啥意思呀,你是不是搞错了,浮点数寄存器显示的咋不是 3.14159 ,而是这个乱七八糟的数据?

                                            程序员的母语就是16进制,没有一眼把 0x400921f9f01b866e 认出是 3.14159的,晚饭是不配加鸡腿的,也不配变秃的。

                                            有理想的同学请自行搜索 IEEE754 二进制浮点数算术标准

                                            其他的同学请和我一起优化下浮点寄存器的显示。

                                            由于飞哥目前为止还没有变秃,确实也看不出来这玩意就是 3.14159, 只好另辟蹊径,给大家传授一个神奇的函数:

                                            public static double bytes2Double(byte[] arr) {

                                                long value = 0;

                                                for (int i = 0; i

                                                    value |= ((long) (arr[i] & 0xff))


0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码