JackLSQ 发表于 2024-3-17 20:59

unidbg 学习笔记

# unidbg教程前置知识之NDK静态、动态注册

## 声明

​                本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

​                本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责。


## unidbg是什么?

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

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

## unidbg项目的由来

​                由于现在的大多数 app 把签名算法已经放到了 so 文件中,所以要想破解签名算法,必须能够破解 so 文件。但是我们知道,C++ 的逆向远比 Java 的逆向要难得多了,所以好多时候是没法破解的,那么这个时候还可以采用 hook 的方法,直接读取程序中算出来的签名,但是这样的话,需要实际运行这个应用,需要模拟器或者真机,效率又不是很高。

unidbg 就是一个很巧妙地解决方案,他不需要直接运行 app,也无需逆向 so 文件,而是通过在 app 中找到对应的 JNI 接口,然后用 unicorn 引擎直接执行这个 so 文件,所以效率也比较高。

​                **更重要的是它可以和springBoot一起做成web服务。**



​        在安卓开发中,可以在Native层利用反射进行Java层方法的调用。它再和NDK动态注册进行配合能实现让逆向人员在so文件逆向中找不到与加密函数直接相关的函数名。这样能拦住一部分安卓逆向人员。

下面主要介绍静态注册和动态注册。

## 静态注册

###                 原理

​                                       

+ 根据函数名将Java代码中的native方法与so中的JNI方法一一对应,当Java层调用so层的函数时,如果发现其上有JNIEXPORT和JNICALL两个宏定义声明时,就会将so层函数链接到对应的native方法上。

+ 而native方法和so方法对应规则是:以字符串“Java”为前缀,并且用“_”下划线将包名、类名以及native方法名连接起来就是对应的JNI函数名了。

​               

```java
public class MainActivity extends AppCompatActivity{
    ...
    public native String stringFromJNI();                //Java 层Native方法
    ...
}
```



```cpp
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplicationndk_MainActivity_stringFromJNI(// so层方法
      JNIEnv* env,
      jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
```



- 也就是说,Java_com_example_ndk_MainActivity_stringFromJNI方法对应的是Java层包名为com.example.myapplicationndk的MainActivity类的stringFromJNI方法。

### CPP代码预览

​                新建一个Android项目,项目名为myapplicationndk,包名为com.example.myapplicationndk,选择语言为Java,并选择项目模板为“Native C++”,如下图所示:











项目创建完成后,其工程结构目录如下:




​                这里主要关注的是cpp文件夹,这是系统自动生成的,该文件夹下包含一个CMakeLists.txt,其文件内容也是系统自动生成的,其内容如下:

```txt
add_library( # Sets the name of the library.
      myapplicationndk

      # Sets the library as a shared library.
      SHARED

      # Provides a relative path to your source file(s).
      native-lib.cpp)
```

​                其中myapplicationndk是由native-lib.cpp文件编译后生成的so文件名,该文件名在MainActivity.java中作为参数被加载到应用中:

```java
public class MainActivity extends AppCompatActivity {

    // 应用启动时加载native-lib.so库
    static {
      System.loadLibrary("myapplicationndk");
    }
    ...
}
```



​                在MainActivity.java中定义一个native方法myFunc(),该方法包含一个int型的参数,且返回值类型为String,如下图所示:





​                虽然报错了但是没有多大的问题,是因为我们还没有注册这个方法。将鼠标移动到报错处,按下快捷键Alt+Shift+Enter。如下所示:




​                操作结束,在native层自动创建了对应的JNI方法,如下所示:





​                可以看出,上图中的方法上方有JNIEXPORT和JNICALL两个宏定义声明,且其命名符合native方法和so方法的对应规则。其中JNIEnv类型代表了Java环境,通过这个JNIEnv* 指针,就可以对Java端的代码进行操作。jobject thiz代表该native方法的类实例或者这个native方法的类的class对象实例。然后实现其函数功能:



在MainActivity.java中调用native方法:





将以上程序运行到模拟器或者真机上,可以看到在程序启动以后会弹出如下图所示的提示:







### 优点

- 理解和使用方式简单, 属于傻瓜式操作, 使用相关工具按流程操作就行, 出错率低

### 缺点

- 必须遵循某些规则
- JNI方法名过长
- 运行时根据函数名查找对应的JNI函数,程序效率不高
- 当需要更改类名,包名或者方法时, 需要按照之前方法重新生成头文件, 灵活性不高



## 动态注册



### 原理

​                在调用System.loadLibrary()时会在so层调用一个名为JNI_OnLoad()的函数,我们可以提供一个函数映射表,再在JNI_Onload()函数中通过JNI中提供的RegisterNatives()方法来注册函数。这样Java就可以通过函数映射表来调用函数,而不必通过函数名来查找对应函数。

### 实现流程

1. 利用结构体JNINativeMethod数组记录native方法与JNI方法的对应关系,即函数映射表
2. 实现JNI_OnLoad方法,在加载动态库后,执行动态注册
3. 调用FindClass方法,获取java对象
4. 调用RegisterNatives方法,传入java对象、JNINativeMethod数组以及注册数目完成注册

其中JNINativeMethod结构体如下所示:

```c
typedef struct {
    const char* name;         // native方法名
    const char* signature;    // 方法签名,例如()Ljava/lang/String;
    void*       fnPtr;      // 函数指针
} JNINativeMethod;
```

这里关注一下第二个参数,其类型为字符串,由一对小括号和若干签名符号组成,其中括号内写传入参数的签名符号,没有参数则不写,括号外写返回参数的签名符号。

| 签名符号      | C/C++         | java      |
| --------------- | ------------- | --------- |
| V               | void          | void      |
| Z               | jboolean      | boolean   |
| I               | jint          | int       |
| J               | jlong         | long      |
| D               | jdouble       | double    |
| F               | jfloat      | float   |
| B               | jbyte         | byte      |
| C               | jchar         | char      |
| S               | jshort      | short   |
| |
|    |
|     |
| |
|    |
|     |
|     |
|    |
| L+完整包名+类名 | jobject       | class   |

例如:Java层函数`String getText(int a,byte[] b)`的方法签名就是`(I[B)Ljava/lang/String;`。

其实没有必要去深究其签名的生成,将App编译好之后,用jadx或jeb反编译查看dex文件能获得smile汇编,在这里面有函数的名字,以及签名信息。



### CPP代码预览

​                还是在该项目中,对native层代码做一定的修改即可,修改之后的动态注册的代码。

```cpp
#include <jni.h>
#include <string>

std::string stringFromJNI(
      JNIEnv* env,
      jobject /* this */) {
    std::string hello = "Hello from C++";
    //env->FindClass()


    return reinterpret_cast<const char *>(env->NewStringUTF(hello.c_str()));
}


std::string myFunc(JNIEnv *env, jobject thiz, jint i_param) {
    // TODO: implement myFunc()

    std::string retValue="input value is :===> ";
    std::string temp;

   temp=std::to_string(i_param);
    retValue+=temp;
    return reinterpret_cast<const char *>(env->NewStringUTF(retValue.c_str()));
}

JNINativeMethod methods ={
      {"stringFromJNI","()Ljava/lang/String;",(void*)stringFromJNI},
      {"myFunc","(I)Ljava/lang/String;",(void*)f1}//

};


jint JNI_OnLoad(JavaVM* vm,void* reserved){
      JNIEnv *env=NULL;
      if(vm->GetEnv((void**)&env,JNI_VERSION_1_6)!=JNI_OK){
            return JNI_ERR;
      }


      jclass clazz = env->FindClass("com/example/myapplicationndk/MainActivity");
      if(clazz==NULL){
            returnJNI_ERR;
      }
    jint iMethod=sizeof(methods)/ sizeof(methods);

    jint result=env->RegisterNatives(clazz,methods,iMethod);

    if(result<0){
      return JNI_ERR;
    }

    return JNI_VERSION_1_6;
}
```



​                由此可见,JNI方法上方不再有JNIEXPORT和JNICALL两个宏定义声明,方法名也不需要遵循某些规则,而是通过RegisterNatives方法完成动态注册。添加完上述代码以后,MainActivity.java中的两个native方法也不再报错了。

### 优点

- 通过函数映射表来查找对应的JNI方法,运行效率高
- 不需要遵循命名规则,灵活性更好

### 缺点

- 实现起来相对复杂
- 对新手来说稍微有点难理解, 同时会由于搞错签名, 方法, 导致注册失败

## 动态注册JNI函数失败原因

###         此类问题一般有两种情况:

1. 包名,类名或者函数签名写错了, 这个解决方法就仔细检查一下就行, 当然如果一些签名不知道怎么写, 可以用静态注册方法通过javah生成头文件, 然后看下头文件里面注释, 把注释的签名复制过来就行

2. 由于加入了混淆机制, 导致无法通过类名来找到对应方法了, 这种情况我遇到过两次, 表现稍有差异, 但本质问题都是混淆引起的.
   - Java中定义的native方法, 在代码中其他地方用到了就能注册上, 没用到的只声明了的函数就会注册失败, 原因是有些没有用到的函数, 混淆是会自动剔除, 所以实际运行的代码中就没有相关方法了, 动态注册就会失败
   - 所有方法都注册不上, 同时也确定相关包名类名签名没有错, 这种情况就是即使你用到了相关方法, 但经过过混淆后已经不是原来的名称了, 所以注册失败



###         这两种错误解决方法也简单:

1. 禁用混淆, 这样做不是太好, 混淆对防止反编译和apk瘦身有很大帮助
2. 添加混淆白名单, 和JNI相关的类或者方法不做混淆,



### 如何定位crash原因

​        crash问题是开发中比较常见的, Java由于其特性, Crash问题我们直接看AndroidRuntime的Log就行, C/C++的就要麻烦些了.
说明: **如果Crash对应的so没有符号表, so库是别人编译的, 额外去除了符号表(Releas版本), 这种情况是没法定位crash代码具体是在哪个位置的.**
对于有源码并且是自己可以编译的情况下, 可通过如下方式对Crash代码进行定位:

- 通过ndk-stack定位

ndk-stack是NDK开发工具包中自带的, 配置好NDK后即可使用, 使用方式有如下两种:

1. `adb logcat |ndk-stack -sym 带符号表的so库路径`, 如果是Android源码方式编译, 带符号表的so库路径为(32位)`out/target/product/xxx/symbols/system/lib/`, 64位就在lib64目录想, 如果使用ndk-build编译的代码, 带符号表的so就在和libs同级的obj目录里面. 执行adb命令后, 只需复现crash即可看到Log中输出的Crash栈对应的代码行数和位置
2. 如果Log是以文件方式存储的, 可通过`ndk-stack -sym [带符号表的so库路径] -dump `进行查看

- 通过addr2line定位
addr2line相当于缩减版的ndk-stack, 每次只能看一个地址的位置, 使用非常简单`addr2line -f -e [带符号表的so路径] `

注意事项: 如果源码和so库不是完全对应的, 即so库发布后, 源码有过修改,这样会导致定位的行数有些偏移, 不完全准确, 需要额外注意下.

小技巧: 只看native层crash log, 可直接 `adb logcat *:F`



### C/C++中打印Log

这个比较常见, 教程也比较多, 我这里也做下记录:

1. 在C/C++中引入系统Log头文件 `#include <android/log.h>`

2. 在log.h中定义了相关的日志输出函数 ,例如`int __android_log_print(int prio, const char* tag, const char* fmt, ...)`

   参数prio代表了不同日志级别,不同日志级别如下所示

   ```c
   typedef enum android_LogPriority {
   /** For internal use only.*/
   ANDROID_LOG_UNKNOWN = 0,
   /** The default priority, for internal use only.*/
   ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
   /** Verbose logging. Should typically be disabled for a release apk. */
   ANDROID_LOG_VERBOSE,
   /** Debug logging. Should typically be disabled for a release apk. */
   ANDROID_LOG_DEBUG,
   /** Informational logging. Should typically be disabled for a release apk. */
   ANDROID_LOG_INFO,
   /** Warning logging. For use with recoverable failures. */
   ANDROID_LOG_WARN,
   /** Error logging. For use with unrecoverable failures. */
   ANDROID_LOG_ERROR,
   /** Fatal logging. For use when aborting. */
   ANDROID_LOG_FATAL,
   /** For internal use only.*/
   ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
   } android_LogPriority;
   ```

   在这个枚举类型中只需要注意

   - ANDROID_LOG_VERBOSE
   - ANDROID_LOG_DEBUG
   - ANDROID_LOG_INFO
   - ANDROID_LOG_WARN
   - ANDROID_LOG_ERROR

   这五个就行,因为这5个是常用的日志输出级别




# unidbg讲解函数流程

​        在之前对unidbg和ndk开发介绍之后相信你对unidbg这个项目诞生的原因了。下面主要介绍环境搭建以及unidbg调用的流程。

## 环境搭建

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

### IntelliJ IDEA

官网:https://www.jetbrains.com.cn/idea/ 下载社区版即可,一直下一步即可

​               

### Maven 环境

​        官网:https://maven.apache.org/download.cgi
​                在官网下载对应系统的maven 版本,maven 版本不闭合我一致,能用就行

​               

                下载解压。

​               

#### 配置环境变量





设置环境变量时,可以创建一个叫做”MAVEN_HOME“的系统变量名称,值是maven文件夹路径







在path系统变量中引用MAVEN_HOME变量,指向MAVEN_HOME中的bin目录。

​       



**测试maven是否配置完毕**

​        输入mvn -v命令,如果出现maven版本号,就表明安装成功。如下所示





#### 配置maven

​        Maven的环境变量配置之后,接下来我们还需要对Maven进行必要的配置,尤其是要配置Maven仓库。

**1. 配置settings.xml文件**

这个settings.xml文件很重要,里面可以配置maven的仓库,私服,jdk等。



随便在maven 解压目录下,创建一个文件夹,作为本地仓库。



然后在settings.xml文件中,在本地仓库路径配置本地仓库路径。





#### 在IDEA中关联maven

​               




至此,Maven的安装配置就做完了,接下来接下来我们就可以在idea中利用maven了

## unidbg 代码讲解

​                从https://github.com/zhkl0228/unidbg 下载项目代码



用IDEA打开项目

file ->open 然后选择unidbg 解压后的文件夹

项目打开大体结构

​       

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

### unidbg 基本流程

1. 创建32位模拟器实例,
   `emulator = AndroidEmulatorBuilder.for32Bit().build();`
2. 创建模拟器内存接口
   `final Memory memory = emulator.getMemory();`
3. 设置系统类库解析
   `memory.setLibraryResolver(new AndroidResolver(23));`
4. 创建 Android 虚拟机
   `vm = emulator.createDalvikVM();`
5. 加载 so 到虚拟内存,第二个参数的意思表示是否执行动态库的初始化代码
   `DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xxx/xxx.so"),true);`
6. 获取 so 模块的句柄
   `module = dm.getModule();`
7. 设置 JNI   需要继承`AbstractJni`
   `vm.setJni(this);`
8. 打印日志
   `vm.setVerbose(true);`
9. 调用 JNI_Onload
   `dm.callJNI_OnLoad(emulator);`
10. 创建 jobject, 如果没用到的话可以不写 ,要用需要调用函数所在的Java类完整路径,比如a/b/c/d等等,注意.需要用/代替
    `cNative = vm.resolveClass("com/xxx/xxx")`



下面是完整的代码

```java
package com.testUnidbg.testCode;


import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.arm.backend.DynarmicFactory;
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.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class MainActivity extends AbstractJni {
    public static void main(String[] args) {
      long start = System.currentTimeMillis();
      com.kanxueClass.testCode.MainActivity mainActivity = new com.kanxueClass.testCode.MainActivity();
      System.out.println("load offset=" + (System.currentTimeMillis() - start) + "ms");
      mainActivity.crack();
    }

    private final AndroidEmulator emulator;
    private final VM vm;

    private final DvmClass dvmClass ;

    private MainActivity() {

      emulator = AndroidEmulatorBuilder
                .for32Bit()
                .addBackendFactory(new DynarmicFactory(true))
                .build();
      Memory memory = emulator.getMemory();
      LibraryResolver resolver = new AndroidResolver(23);
      memory.setLibraryResolver(resolver);

      vm = emulator.createDalvikVM();
      vm.setVerbose(true);
      vm.setJni(this);
      DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/armeabi-v7a/lib2.so"), true);

      dm.callJNI_OnLoad(emulator);
      dvmClass = vm.resolveClass("com/xxx/xxxx/MainActivity");
    }


    private void crack() {



      String result = (String)         dvmClass.callStaticJniMethodObject(emulator,"Sign(Ljava/lang/String;)Ljava/lang/String;","21123").getValue();
      int i= 1000;

      System.out.println("result " + result); ;

      
    }

}

```

### 参数构建

#### 基本方式

`List<Object> args = new ArrayList<>(10);`

#### 兼容格式

// 参数1:JNIEnv *env
`args.add(vm.getJNIEnv());`

#### jobject 或 jclass

`DvmObject<?> cnative = cNative.newObject(null);`
` args.add(cnative.hashCode());`

如果用不到直接填0即可

` args.add(0);`

#### 字符串对象

`String input = "abcdef";`
`args.add(vm.addLocalObject(new StringObject(vm, input)));`

#### bytes 数组

`String str= "abcdef";`
`byte[] str_bytes = str.getBytes(StandardCharsets.UTF_8);`
`ByteArray str_bytes _array = new ByteArray(vm,str_bytes );`
`args.add(vm.addLocalObject(str_bytes _array));`

#### bool

// false 填 0,true 填 1
`args.add(1);`



### AndroidEmulator 实例

​        使用 AndroidEmulatorBuilder 可以来帮助你快速创建一个 AndroidEmulator 的实例。

#### AndroidEmulator 创建

```Java
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 实例之后,就可以直接来操作这个实例,常使用的一些API

```java
//获取内存操作接口
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 实例

```java
Memory memory = emulator.getMemory();
//指定Android SDK 版本,目前支持19和23两个版本
memory.setLibraryResolver(new AndroidResolver(23));

//拿到一个指针,指向内存地址,通过该指针可操作内存
UnidbgPointer pointer = memory.pointer(address);

//获取当前内存映射情况
Collection<MemoryMap> memoryMap = memory.getMemoryMap();

//根据模块名来拿到某个模块
Module module = memory.findModule("module name");

//根据地址拿到某个模块
Module module = memory.findModuleByAddress(address);

```

### VM 操作

​       

```java
//推荐指定APK文件,Unidbg会自动做许多固定的操作
VM vm = emulator.createDalvikVM();

//是否输出JNI运行日志
vm.setVerbose(true);

//加载SO模块 参数二设置是否自动调用init函数
DalvikModule dalvikModule = vm.loadLibrary(new File("so 文件路径"), 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 的代码的demo。

```java
//unidbg集成了HookZz框架
int address=0x11111;
HookZz hookZz = HookZz.getInstance(emulator);
hookZz.replace(address, new ReplaceCallback() {
      @Override
      public HookStatus onCall(Emulator<?> emulator, long originFunction) {
            return super.onCall(emulator, originFunction);
      }

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

```


# so文件算法流程分析



​                在通过unidbg主动调用算法成功计算出结果。下面通过IDA 分析so文件,还原算法的流程。



用IDA打开so文件分析,打开导出表,找到JNI_Load()函数。点进去,然后可以看到



​                                



从上图中看出,native层是通过动态加载的。加载的方法名在`method_table`中,之前的流程不知到做了什么事情,不用管,不会影响到后续的算法分析。




method_table 中可以看到在native层中重新定义的名字是 `fuck`,接下来到该函数中去看看。




```cpp
if ( !input_str )
    return 0;
        //将一个输入的jstring 转化成 UTF8的sting
input_str_src = (unsigned __int8 *)_JNIEnv::GetStringUTFChars(env, input_str, 0);
        //查找build类,去获得手机相关指纹信息
clazz = _JNIEnv::FindClass(env, "android/os/Build");
fieldID = _JNIEnv::GetStaticFieldID(env, clazz, "FINGERPRINT", "Ljava/lang/String;");
_JNIEnv::GetStaticObjectField(env, clazz, fieldID);
   //对输入的字符串追加REAL 字符
strcat((char *)input_str_src, "REAL");
        //对拼接后的字符串再进一步处理
obj = (_jobject *)j_o0OoOOOO(env, input_str_src);
_android_log_print(4, "roysuejni", "before entering aes => %s", (const char *)input_str_src);
   //引入Java MD5算法
Class = _JNIEnv::FindClass(env, "java/security/MessageDigest");
methodID = _JNIEnv::GetStaticMethodID(env, Class, "getInstance", "(Ljava/lang/String;)Ljava/security/MessageDigest;");
method_str = j_o0OoOOOO(env, "MD5");

v14 = _JNIEnv::CallStaticObjectMethod(env, Class, methodID, method_str);
v13 = _JNIEnv::GetMethodID(env, Class, "digest", "([B)[B");
v12 = _JNIEnv::FindClass(env, "java/lang/String");
v11 = _JNIEnv::GetMethodID(env, v12, "getBytes", "()[B");
v10 = _JNIEnv::CallObjectMethod(env, obj, v11);
array = (_jbyteArray *)_JNIEnv::CallObjectMethod(env, v14, v13, v10);
   //最终结果 被ByteArrayElements记录
ByteArrayElements = _JNIEnv::GetByteArrayElements(env, array, 0);
   //对最终的结果格式化
for ( i = 0; i <= 15; ++i )
    sprintf((char *)&v24, "%02x", (unsigned __int8)ByteArrayElements);
        //对输入的字符串加个REAL之后的字符在进行一遍处理
v23 = (char *)j_ll11l1l1ll(input_str_src);
        //字符拼接到v23中
strcat(v23, (const char *)v24);
    //获得最终加密结果
output_str = j_o0OoOOOO(env, (const unsigned __int8 *)v23);
_android_log_print(4, "roysuejni", "result is => %s ", v23);
        //释放之前的指针资源
_JNIEnv::ReleaseStringUTFChars(env, input_str, input_str_src);
free(v23);
return output_str;
```

wasm2023 发表于 2024-7-19 19:08

模拟执行报错如下:
JNIEnv->GetMethodID(java/lang/Boolean.booleanValue()Z) => 0x31f67dab was called from RX@0x400385880x38588
JNIEnv->CallBooleanMethodV(true, booleanValue() => true) was called from RX@0x400386b80x386b8
JNIEnv->GetObjectArrayElement([["82a9a01089d9dde7d24ff7d3ea0dbe9c"], "d7b7d042-d4f2-4012-be60-d97ff2429c17", java.lang.Integer@c038203, false, com.yxcorp.gifshow.App@cc285f4, null, true, "5059fbc5-cce9-4393-a49e-1dda976617c4"], 7) => "5059fbc5-cce9-4393-a49e-1dda976617c4" was called from RX@0x400422b40x422b4
JNIEnv->GetStringUtfChars("5059fbc5-cce9-4393-a49e-1dda976617c4") was called from RX@0x400422d40x422d4
JNIEnv->ReleaseStringUTFChars("5059fbc5-cce9-4393-a49e-1dda976617c4") was called from RX@0x400424a80x424a8
WARN (AbstractARM64Emulator$1:66) - Fetch memory failed: address=0x9c00, size=1, value=0x0
WARN (AbstractEmulator:417) - emulate RX@0x40040cd40x40cd4 exception sp=unidbg@0xbfffeae0, msg=unicorn.UnicornException: Invalid memory fetch (UC_ERR_FETCH_UNMAPPED), offset=18ms @ Runnable|Function64 address=0x40040cd4, arguments=0x640, 1734853116, 10418, 703504298]
Exception in thread "main" java.lang.NullPointerException
at com.ks.Ks.callTarget(Ks.java:72)
at com.ks.Ks.main(Ks.java:81)
这种说是unidbg的资源文件不支持arm64,请问如何修改才能支持呢

hanzong 发表于 2024-4-12 13:36

想咨询一下,如果是frida的RPC调用SO函数没问题,但是unidbg返回MD5有问题,一般是什么原因,参数值肯定传进去的都一样。

风子09 发表于 2024-3-17 21:53

能用现成的app举例应用一下,看了unidbg里面的案例,也成功运行,不知道他们在干什么,另外都是用模拟器,真机如何使用

hydome 发表于 2024-3-18 00:03

教程很不错,感谢分享

JackLSQ 发表于 2024-3-18 00:23

风子09 发表于 2024-3-17 21:53
能用现成的app举例应用一下,看了unidbg里面的案例,也成功运行,不知道他们在干什么,另外都是用模拟器, ...

可以,后面有空补一个。

yubuguosan 发表于 2024-3-18 02:01

很有用的知识

apaye 发表于 2024-3-18 06:43

学习了,谢谢分享

xiaomianao 发表于 2024-3-18 06:58

Lty20000423 发表于 2024-3-18 07:49

soglog 发表于 2024-3-17 21:28
很好 谢谢你的分享·······

支持原创,但是我用惯了win自带的note笔记

shz2008bj 发表于 2024-3-18 08:40

感谢大佬的分享{:301_987:}

xbjt2016 发表于 2024-3-18 08:49

很好的分享,涨知识了,希望多增加几个例子更好理解些,谢谢~
页: [1] 2 3 4 5
查看完整版本: unidbg 学习笔记