unidbg 学习笔记

查看 174|回复 10
作者:JackLSQ   
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.png (50.39 KB, 下载次数: 0)
    下载附件
    图片1
    2024-3-17 20:51 上传



    图片 2.png (28.11 KB, 下载次数: 0)
    下载附件
    图片2
    2024-3-17 20:52 上传

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


    图片 3.png (20.18 KB, 下载次数: 0)
    下载附件
    图片3
    2024-3-17 20:52 上传

    ​     这里主要关注的是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 (35.67 KB, 下载次数: 0)
    下载附件
    2024-3-17 20:53 上传

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



    图片 5.png (23.04 KB, 下载次数: 0)
    下载附件
    2024-3-17 20:53 上传

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


    图片 6.png (23.45 KB, 下载次数: 0)
    下载附件
    2024-3-17 20:53 上传

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


    图片 7.png (31.89 KB, 下载次数: 0)
    下载附件
    2024-3-17 20:53 上传

    在MainActivity.java中调用native方法:


    图片 8.png (23.29 KB, 下载次数: 0)
    下载附件
    2024-3-17 20:54 上传

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


    图片 9.png (20.61 KB, 下载次数: 0)
    下载附件
    2024-3-17 20:54 上传

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

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

    动态注册
    原理
    ​     在调用System.loadLibrary()时会在so层调用一个名为JNI_OnLoad()的函数,我们可以提供一个函数映射表,再在JNI_Onload()函数中通过JNI中提供的RegisterNatives()方法来注册函数。这样Java就可以通过函数映射表来调用函数,而不必通过函数名来查找对应函数。
    实现流程
    [ol]
  • 利用结构体JNINativeMethod数组记录native方法与JNI方法的对应关系,即函数映射表
  • 实现JNI_OnLoad方法,在加载动态库后,执行动态注册
  • 调用FindClass方法,获取java对象
  • 调用RegisterNatives方法,传入java对象、JNINativeMethod数组以及注册数目完成注册
    [/ol]
    其中JNINativeMethod结构体如下所示:
    typedef struct {
        const char* name;         // native方法名
        const char* signature;    // 方法签名,例如()Ljava/lang/String;
        void*       fnPtr;        // 函数指针
    } JNINativeMethod;
    这里关注一下第二个参数,其类型为字符串,由一对小括号和若干签名符号组成,其中括号内写传入参数的签名符号,没有参数则不写,括号外写返回参数的签名符号。
    [table]
    [tr]
    [td]签名符号[/td]
    [td]C/C++[/td]
    [td]java[/td]
    [/tr]
    [tr]
    [td]V

    方法, 函数

  • hanzong   

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

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

    感谢大佬的分享~
    hydome   

    教程很不错,感谢分享
    JackLSQ
    OP
      


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

    可以,后面有空补一个。
    yubuguosan   

    很有用的知识
    apaye   

    学习了,谢谢分享
    xiaomianao   

    看起来就很厉害,感谢分享
    Lty20000423   


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

    支持原创,但是我用惯了win自带的note笔记
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部