吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1612|回复: 42
收起左侧

[Android 原创] unidbg 学习笔记

  [复制链接]
JackLSQ 发表于 2024-3-17 20:59

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函数名了。

​     

public class MainActivity extends AppCompatActivity{
    ...
    public native String stringFromJNI();       //Java 层Native方法
    ...
}
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++”,如下图所示:

图片1

图片1

图片2

图片2

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

图片3

图片3

​     这里主要关注的是cpp文件夹,这是系统自动生成的,该文件夹下包含一个CMakeLists.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中作为参数被加载到应用中:

public class MainActivity extends AppCompatActivity {

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

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

图片 4.png

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

图片 5.png

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

图片 6.png

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

图片 7.png

在MainActivity.java中调用native方法:

图片 8.png

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

图片 9.png

优点

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

缺点

  • 必须遵循某些规则
  • 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结构体如下所示:

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
[Z jbooleanArray boolean[]
[I jintArray int[]
[J jlongArray long[]
[D jdoubleArray double[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
L+完整包名+类名 jobject class

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

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

CPP代码预览

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

#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){
            return  JNI_ERR;
        }
    jint iMethod=sizeof(methods)/ sizeof(methods[0]);

    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 [log文件路径]进行查看
  • 通过addr2line定位
    addr2line相当于缩减版的ndk-stack, 每次只能看一个地址的位置, 使用非常简单addr2line -f -e [带符号表的so路径] [crash地址0xxxx]

注意事项: 如果源码和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代表了不同日志级别,不同日志级别如下所示

    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 版本不闭合我一致,能用就行

​      图片 1.jpg

    下载解压。

​      图片 2.png

配置环境变量

图片 3.png

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

图片 4.png

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

图片 5.png

测试maven是否配置完毕

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

图片 6.png

配置maven

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

1. 配置settings.xml文件

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

图片 7.png

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

图片 8.png

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

图片 9.png

在IDEA中关联maven

​      图片 10.png

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

unidbg 代码讲解

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

用IDEA打开项目

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

项目打开大体结构

图片 12.png

框架的目录 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")

下面是完整的代码

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 创建

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
//获取内存操作接口
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(address);

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

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

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

VM 操作

//推荐指定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。

//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()函数。点进去,然后可以看到

图片1.jpg

​                      图片2.jpg

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

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

图片4.jpg

  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[i], "%02x", (unsigned __int8)ByteArrayElements[i]);
    //对输入的字符串加个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;

免费评分

参与人数 8吾爱币 +7 热心值 +6 收起 理由
Fxizenta + 1 + 1 谢谢@Thanks!
xzl9552547 + 1 谢谢@Thanks!
debug_cat + 2 + 1 谢谢@Thanks!
gzlyylai + 1 用心讨论,共获提升!
lihuhu + 1 我很赞同!
yxcc123 + 1 + 1 我很赞同!
yiyuh95 + 1 热心回复!
风子09 + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

hanzong 发表于 2024-4-12 13:36
想咨询一下,如果是frida的RPC调用SO函数没问题,但是unidbg返回MD5有问题,一般是什么原因,参数值肯定传进去的都一样。
风子09 发表于 2024-3-17 21:53
能用现成的app举例应用一下,看了unidbg里面的案例,也成功运行,不知道他们在干什么,另外都是用模拟器,真机如何使用
Legend186 发表于 2024-3-17 22:12
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笔记
starryskyhello 发表于 2024-3-18 07:53
写得挺好
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-28 15:33

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表