首页
社区
课程
招聘
[原创]native层逆向分析(上篇)
发表于: 2023-2-10 23:23 13842

[原创]native层逆向分析(上篇)

2023-2-10 23:23
13842

native层逆向分析内容太多,分篇发布

本公众号分享的所有技术仅用于学习交流,请勿用于其他非法活动,如有错漏,欢迎留言指正

native层逆向分析(上篇)

一、前置知识

  1. 什么是native层逆向分析
    逆向分析的两层
  • java层
  • native层
    • ndk进行编译生成的动态链接库.so文件,分析.so文件就是native层分析
  1. 为什么要学习so层逆向
  • 因为现在大部分软件的核心功能都会用C/C++来实现。通过java层进行调用,比如app破解、协议分析、脱壳。分析app加固原理、手游app分析。都会涉及到大量的so层分析。
  1. 怎么学习so层逆向
  • 主要涵盖以下几个方面的内容
    • 了解Android NDK开发
    • ARM汇编
    • IDA静态分析IDA动态调试
    • 实战:app实战分析

      二、NDK开发

      1. NDK

      NDK安装

      在windows系统下编译出linux下的可执行文件?
  • 一般不不可以的,因为没有linux平台相关工具。解决办法:交叉编译。
    • Windows下可以执行文件是:PE文件(后缀名是.exe)
    • Linux下可执行文件的是:.elf
    • 交叉编译:可以实现不同平台下(windows)编译出另一个平台下的可执行二进制文件;
    • Google给我们提个了一个工具:NDK(native develop kits)集成了交叉编译器,并提供了相应的Makefile文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改Makefile文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。NDK可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。

      Windows 32-bit 版本下载地址: http://dl.google.com/android/ndk/android-ndk-r9-windows-x86.zip
      Windows 64-bit 版本下载地址: http://dl.google.com/android/ndk/android-ndk-r9-windows-x86_64.zip

在Android Studio中下载即可

JNI接口(jni.h)

  1. 什么是jni
  • java native interface: java本地开发接口
  • 相当于桥梁的作用,一种协议
  • 通过jni就可以让java调用C语言或者C++代码,也可以用C语言调用java代码(反射)
  • JNI(Java Native Interface)同时支持C和C++主要是为了兼顾不同的需求和使用场景。
    • a. 兼容性:C语言是一种广泛使用的低级语言,它在许多操作系统和库函数中都有应用。因此,支持C语言可以使JNI更容易地与更多平台和资源进行交互。
      1. 扩展性:C++是C语言的超集,它拥有更丰富的语法和特性,可以更容易地实现复杂的逻辑和功能。支持C++可以让JNI更
  1. 为什么要用jni
  • 通过jni技术,可以扩展Android手机的功能-wifi热点
  • native coder执行高效:大量的运算(极品飞车),万能解码(ffmpeg),Opengl(3D 渲染)
  • 代码的复用:ffmpeg,onencv(人脸识别库),7-zip
  • 防破解:将原来java中的核心算法用C语言实现。
  1. 怎么用jni
  • 学C语言(看懂代码)
  • 熟悉jni开发流程

    c语言和java的数据类型区别

  1. 基本类型
  • C语言中char类型是1个字节(不可以表示汉字),Java语言是2个字节(可以表示汉字)
  • C语言中long类型是4个字节,Java中是8个字节
    • 根据:c99标准下,long类型的定义为:不可以比整形小;
  • boolean C语言中表示:0(flase),非0(true)
  • byte C语言中没有此类型;
  • c语言中没有字符串,但可以用char数组表示
  • unsigned 无符号 0~255
  • signed 有符号 -128~127
  • void 无类型,任意类型

    2. HelloNdk

    1. 创建一个android工程

    2. java测试代码

    app\src\main\java\com\example\ndk_jin01\MainActivity.java
  • java代码中声明native方法
  • java代码在静态语句块里load动态库
  • 调用native方法
    #坑/ndk 为什么要在静态语句块里面加载?::因为在一个类里面静态语句块执行时机是最早的,保证调用之前就加载了lib的函数,避免调用的时候找不到函数地址导致程序终止。
    #坑/逆向/app逆向/IDA/so 逆向场景:lib下有很多个so文件,如何确定so文件::
    System.loadLibrary("native-lib");java层代码这里写得很清楚了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// MainActivity.java
package com.example.ndk_jin01; 
 
import androidx.appcompat.app.AppCompatActivity; 
import android.os.Bundle; 
import android.util.Log; 
import android.widget.TextView; 
import com.example.ndk_jin01.databinding.ActivityMainBinding; 
 
public class MainActivity extends AppCompatActivity { 
 
private ActivityMainBinding binding; 
 
public String _field = "_field"
public static String _staticfield = "_staticfield"
 
    // C反射调用成员方法 
    public String method_text(String str,char ch){ 
        return str+ch; 
    
 
    // C反射调用静态方法 
    public static String staticmethod_text(String str,double num){ 
        return str+num; 
    
 
 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
 
     binding = ActivityMainBinding.inflate(getLayoutInflater()); // 1. 将布局文件通过布局加载器(getLayoutInflater())转换成对应的数据绑定类对象,这里的 ActivityMainBinding 是通过 Data Binding 编译器自动生成的一个类。 
     setContentView(binding.getRoot()); // 2. 设置当前页面的布局为数据绑定类对象的根视图,即布局文件本身。 
 
        // Example of a call to a native method 
        TextView tv = binding.sampleText; // 3. 获取布局文件中名为 sampleText 的 TextView 对象. 
        // 上面使用Android Data Binding 可以提高开发效率,避免代码冗长、维护困难以及错误易错的问题。上面的123代码等价于下面以前的两行代码。 
        // setContentView("R.layout.activity_main") // 设置当前页面的布局为名为 activity_main 的布局文件。 
        // TextView tv = findViewBuif(R.id.sampleText); // 获取布局文件中名为 sampleText 的 TextView 对象 
        Log.e("cisco","stringFromJNI()+ stringFromJNI2() + v1(1,2) + ..."); 
        tv.setText("stringFromJNI() = " + stringFromJNI() + '\n' 
                    + "stringFromJNI2() = " + stringFromJNI2() + '\n' 
                    + "v1(1,2) = " + v1(1,2) + '\n' 
                    + "v2(\"cisco\") = " + v2("cisco") + '\n' 
                    + "v3(\"cisco\") = " + v3("cisco") + '\n' 
                    + "v4(\"cisco\",\"123456\") = " + v4("cisco","123456") + '\n' 
                    + "v5(\"name=cisco&age=18\") = " + v5("name=cisco&age=18") + '\n' 
                    + "v6(\"name=cisco&age=18\".getBytes()) = " + v6("name=cisco&age=18".getBytes()) + '\n' 
                    + "field() = " + field() + '\n' 
                    + "method() = " + method() + '\n' 
                    + "staticfield() = " + staticfield() + '\n' 
                    + "staticfield1() = " + staticfield1() + '\n' 
                    + "staticmethod() = " + staticmethod() + '\n' 
                    + "ss(\"aid=123&page=9&size=19\").toString() = " + ss("aid=123&page=9&size=19").toString() + '\n' 
//                    + "v7() = " + v7(1,2) + '\n' 
//                    + "v8() = " + v8(1,2) + '\n' // 执行这里程序就奔溃 E/ample.ndk_jin0: No implementation found for int com.example.ndk_jin01.MainActivity.v8(int, int) (tried Java_com_example_ndk_1jin01_MainActivity_v8 and Java_com_example_ndk_1jin01_MainActivity_v8__II)                    + "v8() = " + v8(1,2) + '\n' // 改名也没有效果,不是名字的问题,应该是动态注册的是后面的函数没有注册上,调整注册顺序试试 
                    // + "v7() = " + v7(1,2) + '\n'  // #坑/逆向/app逆向/IDA/动态注册  #技术栈/app逆向/逆功能/程序奔溃  的确是动态注册的时候出了问题,具体原因@todo 
        );  // 调用native方法 
 
    
 
    /** 
     * A native method that is implemented by the 'ndk_jin01' native library,     * which is packaged with this application.     */    // 使用native关键字定义native方法。告诉编译器,这个函数的的逻辑是在C/C++代码(so层)里面实现的 
    public native String stringFromJNI();             // 非静态方法 
    public static native String stringFromJNI2();     // 静态方法 
 
    // 1.数字处理 
    public static native int v1(int v1, int v2); 
    // 2.字符串修改-指针 
    public static native String v2(String old); 
    // 3.字符串修改-数组 
    public static native String v3(String old); 
    // 4.字符串拼接 
    public static native String v4(String name, String role); 
    // 5.字符处理 
    public static native String v5(String data); 
    // 6.字节处理 
    public static native String v6(byte[] data); 
 
    // 7.在native层反射获取并修改java层普通字段的值 
    public native String field(); 
 
    // 8.在native层反射获取java层普通方法 
    public native String method(); 
 
    // 9.在native层反射获取并修改java层静态字段 
    public static native  String staticfield(); 
    public native  String staticfield1(); 
 
    // 10.在native层反射获取java层静态方法 
    public static native  String staticmethod(); 
 
    // 11. 在native层反射实例化对象返回给java层 
    public static native SignQuery ss(String data); 
 
    // 12. 动态注册 
    public  native int v7(int v1, int v2); 
    public  native int v8(int v1, int v2); 
 
 
    // 静态语句里面加载so库,#坑/ndk 为什么要在静态语句块里面加载?::因为在一个类里面静态语句块执行时机是最早的,保证调用之前就加载了lib的函数,避免调用的时候找不到函数地址导致程序终止。 
    // Used to load the 'native-lib' library on application startup.=6 
    static { 
        // 编译后的会生成libnative-lib.so,会打包apk的lib目录下。libnative-lib.so 去头=>lib和去尾=>.so, 把"native-lib"传进去 
        System.loadLibrary("native-lib"); // `动态链接库`so中代码和数据不会被链接到app中去的,所以so文件在内存中`只有一份`,多个app调用so的代码和数据的,其实是调用一个映射到so中对应代码和数据地址。 
        // 对动态链接库相关内容感兴趣可以看之前公众号发布的"系统安全/Windows安全/HOOK 这篇文章,这里面描述了windows平台的动态链接库DLL原理与开发以及dll注入的一些思路 
 
        System.loadLibrary("hellonative"); 
 
        System.loadLibrary("reflect"); 
 
        System.loadLibrary("dynamic");  // 动态注册 
    
 
// #坑/逆向/app逆向/IDA/返回对象 逆向场景:在native层通过反射,实例化一个对象,把加密后的参数放在这个个对象返回给java层:: 比如Blibili 
class SignQuery{ 
    public String token; 
    public String params; 
 
    public SignQuery(String token, String params) { 
        this.token = token; 
        this.params = params; 
    
 
    public String toString() { 
        return params + "&sign=" + token; 
    
}

3. JNI静态注册

#坑/逆向/app逆向/IDA/静态注册 逆向场景:C语言的函数和Java层native方法的对应关系,在函数名上就可以体现::逆向很方便,直接在so文件的导出函数里面(以Java_开头)就可以找到对应java层中声明的native方法

JNI的C++实现
  • app\src\main\cpp\natice-lib.cpp
    • 在Android工程的app\src\main\cpp目录下,编写C++代码,方法名字要对应java代码中声明的方法名对应起来。
      #坑/逆向/app逆向/jadx/native方法 逆向场景:抓包某个app的数据包发现某个关键参数,找遍反编译的java代码未发现关键代码,然后发现带有native的方法,则应该去找 System.loadLibrary( "xxx");::就去apk文件中Lib目
      录下寻找libxxx.so,用IDA对so文件进行反编译,得到C语言,分析C语言,同时Hook native层的NewStringUTF把返回值、调用栈打印出来来验证是不是关键代码,然后再还原核心算法。
      #坑/ndk 在c和c++中env的含义是不一样的::因为C++中的JNIEnv* env在c文件中是JNINativeInterface(指向JNINativeInterface结构体的二级指针)
      `(
      env).GetVersion调用结构体方法,(env).等价于env->所以(**env).等价于(env)->#坑/ndk #坑/漏洞/二进制漏洞/读溢出 #技术栈/app逆向/逆功能/程序奔溃 进入程序,TextView没有任何文字显示,大概2s后程序奔溃::是因为字符串没有结束符号,数据拷贝时候读溢出,如果紧接着字符串后面的这部分内存是无效的,就会导致程序奔溃(无效就是没有对应的物理内存,只有虚拟内存,没有物理内存,就会缺页错误,也就是虚拟内存无效) #坑/逆向/app逆向/Frida/NewStringUTFjstring NewStringUTF(const char bytes) // { return functions->NewStringUTF(this, bytes); } // 将char 类型转换成jstring类型,native层要返回给java层字符串都需要调用这个方法。逆向场景:在不确定加密参数是否是该native方法生成的时候::可以Hook NewStringUTF`这个方法,把返回值、调用栈打印出来来验证是不是关键代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// natice-lib.cpp
#include <jni.h>   // 必须引入jni.h的头文件,因为后面声明的方法中,会引用到jni.h中的类型; 
#include <string> 
#include <syslog.h> // native层要用syslog()输出日志 
 
// extern "C" .cpp文件代码的标识,必须加这个,否则会报错 
// JNIEXPORT export的意思 
// jstring 方法的返回值类型是String 
// JNICALL 这个方法是JNI的方法 
extern "C" JNIEXPORT jstring JNICALL 
 
// 静态注册。 
// 静态注册的方法名,标志是以Java_开头,后面跟的是包名(com_example_ndk_1jni_(点用下划线代替.))类名(MainActivity_)函数(stringFromJNI) 
Java_com_example_ndk_1jin01_MainActivity_stringFromJNI( 
        JNIEnv* env,                 // 默认第一个参数必然是JNIEnv* 
        // jobject obj:java中调用此方法的对对象。当前是:MainActivity.this对象 
        jobject obj) {    // 第二参数需要看java层native方法是否被static关键字修饰。如果被static修饰,则第二个参数是jclass.如果没有,则第二个参数是jobisct 
                                     // 后续的参数是java层native方法的参数列表 
    std::string hello = "Hello from C++"
    // hello.c_str() 方法返回字符串 hello 的C字符串形式,即以 null 结尾的字符数组。 
    syslog(LOG_ERR,"stringFromJNI() = %s", hello.c_str()); 
    return env->NewStringUTF(hello.c_str());// .cpp这样写没问题,但.c则需要使用(*env)-
    // c语言对应JNI接口(jni.h)的这个方法: 
    // jstring NewStringUTF(const char* bytes) 
    //    { return functions->NewStringUTF(this, bytes); }  // 将char* 类型转换成jstring类型,native层要返回给java层字符串都需要调用这个方法。在不确定加密参数是否是该native方法生成的时候,可以Hook NewStringUTF这个方法,把返回值、调用栈打印出来来验证是不是关键代码。 
    // #坑/ndk 在c和c++中`env`的含义是不一样的::因为C++中的`JNIEnv* env`在c文件中是JNINativeInterface**(指向JNINativeInterface结构体的二级指针) 
    //`(**env).GetVersion`调用结构体方法,`(*env).`等价于`env->`所以`(**env).`等价于`(*env)->` 
 
// 这个定义方式是固定的,每增加一个方法,都需要按照这个模板来写,这是静态注册的写法。 
// 动态注册则不需要加这么多关键字。比如:jint add(JNIEnv* env, jobject obj,jint num1,jint num2){...} 
extern "C" JNIEXPORT jstring JNICALL 
Java_com_example_ndk_1jin01_MainActivity_stringFromJNI2( 
        JNIEnv* env,                 // 默认第一个参数必然是JNIEnv* 
        jclass clazz) {   // 第二参数需要看java层native方法是否被static关键字修饰。如果被static修饰,则第二个参数是jclass.如果没有,则第二个参数是jobisct 
                                     // 后续的参数是java层native方法的参数列表 
    // std::string hello = "Static Hello from C++"; // c语言中没有string类型,下面是c语言的写法 
    // #坑/ndk #坑/漏洞/二进制漏洞/读溢出 进入程序,TextView没有任何文字显示,大概2s后程序奔溃::是因为字符串没有结束符号,数据拷贝时候读溢出,如果紧接着字符串后面的这部分内存是无效的,就会导致程序奔溃(无效就是没有对应的物理内存,只有虚拟内存,没有物理内存,就会缺页错误,也就是虚拟内存无效) 
    // char hello[] = {'S','t','a','t','i','c',' ','H','e','l','l','o',' ','f','r','o','m',' ','C','+','+'};    char hello[] = {'S','t','a','t','i','c',' ','H','e','l','l','o',' ','f','r','o','m',' ','C','+','+','\0'}; // `\0`结尾 
    hello[19] = '!'
    hello[20] = '!'
    syslog(LOG_ERR,"stringFromJNI2 = %s", hello); 
    return env->NewStringUTF(hello); 
}
JNI的C语言实现
  • 新增一个app\src\main\cpp\hellonative.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// hellonative.c
#include <jni.h>   // 必须引入jni.h的头文件,因为后面声明的方法中,会引用到jni.h中的类型; 
#include <syslog.h> // native层要用syslog()输出日志 
#include <stdlib.h> 
#include <string.h> 
 
// 1.数字处理 
JNIEXPORT jint JNICALL 
Java_com_example_ndk_1jin01_MainActivity_v1(JNIEnv *env, jclass clazz, jint v1, jint v2) { 
    // TODO: implement v1() 
    syslog(LOG_ERR,"v1() = %d", v1 + v2); 
    return v1 + v2; 
 
// 2.字符串修改-指针 
JNIEXPORT jstring JNICALL 
Java_com_example_ndk_1jin01_MainActivity_v2(JNIEnv *env, jclass clazz, jstring old) { 
    // TODO: implement v2() 
    // char info[]  = {'c','i','s','c','o','\0'}; 
    char *info = (*env)->GetStringUTFChars(env, old, 0); 
    syslog(LOG_ERR, "Befor v2() = %s", info); 
 
    info += 1
    *info = '*'
    info += 3
    *info = '*'
    info -= 4; // 恢复到首地址 
 
    syslog(LOG_ERR, "After v2() = %s", info); 
    return (*env)->NewStringUTF(env, info); 
 
// 3.字符串修改-数组 
JNIEXPORT jstring JNICALL 
Java_com_example_ndk_1jin01_MainActivity_v3(JNIEnv *env, jclass clazz, jstring old) { 
    // TODO: implement v3() 
    char *info = (*env)->GetStringUTFChars(env, old, 0); 
    syslog(LOG_ERR, "Befor v3() = %s", info); 
 
    info[0] = '['
    info[4] = ']'
 
    syslog(LOG_ERR, "After v3() = %s", info); 
    return (*env)->NewStringUTF(env, info); 
 
// 4.字符串拼接 
int GetStringLen(char *dataString) { 
    int count = 0
    for (int i = 0; dataString[i] != '\0'; i++) { 
        count += 1
    
    return count; 
 
JNIEXPORT jstring JNICALL 
Java_com_example_ndk_1jin01_MainActivity_v4(JNIEnv *env, jclass clazz, jstring name, jstring role) { 
    // TODO: implement v4() 
    // cisco 123456 
    char *nameString = (*env)->GetStringUTFChars(env, name, 0); 
    char *roleString = (*env)->GetStringUTFChars(env, role, 0); 
 
    // cisco123456 
    char *result = malloc(GetStringLen(nameString) + GetStringLen(roleString) + 1); 
    strcpy(result, nameString); 
    strcat(result, roleString); 
 
    syslog(LOG_ERR, "v4() = %s", result); 
    return (*env)->NewStringUTF(env, result); 
 
// 5.字符处理 
JNIEXPORT jstring JNICALL 
Java_com_example_ndk_1jin01_MainActivity_v5(JNIEnv *env, jclass clazz, jstring data) { 
    // TODO: implement v5() 
    // "name=cisco&age=18" 
    char *urlParams = (*env)->GetStringUTFChars(env, data, 0); 
    syslog(LOG_ERR, "Befor v5() = %s", urlParams); 
 
    int size = GetStringLen(urlParams); 
 
    // v34 = {1,6,,,,,随机值,,,,,,,} 
    char v34[size * 2]; 
    char *v28 = v34; 
 
    for (int i = 0; urlParams[i] != '\0'; i++) { 
        // syslog(LOG_ERR, "%02x", urlParams[i]); 
        sprintf(v28, "%02x", urlParams[i]); // 将urlParams[i]以16进制格式打印输出到(*v28)中,即ascci码的16进制 
        v28 += 2
    
 
    syslog(LOG_ERR, "After v5() = %s", v34); 
    return (*env)->NewStringUTF(env, v34); 
 
 
JNIEXPORT jstring JNICALL 
Java_com_example_ndk_1jin01_MainActivity_v6(JNIEnv *env, jclass clazz, jbyteArray data) { 
    // TODO: implement v6() 
    // jbyte *byteArray = (*env)->GetByteArrayElements(env, data, 0); 
    char *byteArray = (*env)->GetByteArrayElements(env, data, 0); 
    syslog(LOG_ERR, "Befor v6() = %s", byteArray); 
    int size = (*env)->GetArrayLength(env, data); 
 
    char v34[size * 2]; 
    char *v28 = v34; 
 
    for (int i = 0; byteArray[i] != '\0'; i++) { 
        // syslog(LOG_ERR, "%02x", byteArray[i]); 
        sprintf(v28, "%02x", byteArray[i]); // 同上 
        v28 += 2
    
 
    syslog(LOG_ERR, "After v6() = %s", v34); 
    return (*env)->NewStringUTF(env, v34); 
}
用C语言调用java代码(反射)
  • 新增一个app\src\main\cpp\reflect.c
    #坑/逆向/app逆向/IDA/返回对象 逆向场景:在native层通过反射,实例化一个对象,把加密后的参数放在这个个对象返回给java层:: 比如Blibili
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// reflect.c
#include <jni.h>   // 必须引入jni.h的头文件,因为后面声明的方法中,会引用到jni.h中的类型; 
#include <syslog.h> // native层要用syslog()输出日志 
#include <stdlib.h> 
#include <string.h> 
 
// 7.在native层反射获取并修改java层普通字段的值 
JNIEXPORT jstring JNICALL 
Java_com_example_ndk_1jin01_MainActivity_field(JNIEnv * env, jobject obj){ 
    // 反射获取对应的类 
    jclass clszz = (*env)->FindClass(env,"com/example/ndk_jin01/MainActivity"); 
    // 获取实例字段ID 
    jfieldID field_ID = (*env)->GetFieldID(env,clszz,"_field","Ljava/lang/String;"); 
    // 根据实例字段ID获取对应的值 
    jstring jstr = (*env)->GetObjectField(env,obj,field_ID); 
    // 将jstring类型转换为char数组 
    const char* chars = (*env)->GetStringUTFChars(env,jstr,0); 
    syslog(LOG_ERR, "Before field() changes = %s", chars); 
    // 根据实例字段ID设置相应的值 
    jstring str = (*env)->NewStringUTF(env,"hello! _field"); 
    (*env)->SetObjectField(env,obj,field_ID,str); 
    // 根据实例字段ID获取对应的值 
    jstring jstr2 = (*env)->GetObjectField(env,obj,field_ID); 
    // 将jstring类型转换为char数组 
    const char* chars2 = (*env)->GetStringUTFChars(env,jstr2,0); 
    syslog(LOG_ERR, "After field() changes = %s", chars2); 
    return jstr2; 
 
// 8.在native层反射获取java层普通方法 
JNIEXPORT jstring JNICALL 
Java_com_example_ndk_1jin01_MainActivity_method(JNIEnv * env, jobject obj) { 
    // 反射获取对应的类 
    jclass clszz = (*env)->FindClass(env,"com/example/ndk_jin01/MainActivity"); 
    // 获取成员方法的ID 
    jmethodID method_ID = (*env)->GetMethodID(env,clszz,"method_text","(Ljava/lang/String;C)Ljava/lang/String;"); 
    // 调用java层方法 
    jstring str = (*env)->NewStringUTF(env,"method!"); 
    jstring jstr = (*env)->CallObjectMethod(env,obj,method_ID,str,'A'); 
    const char* chars = (*env)->GetStringUTFChars(env,jstr,0); 
    syslog(LOG_ERR, "method()  = %s", chars); 
    return jstr; 
 
// 9.在native层反射获取并修改java层静态字段 
JNIEXPORT jstring JNICALL 
Java_com_example_ndk_1jin01_MainActivity_staticfield(JNIEnv * env, jclass clszz){ 
    // 获取静态字段ID 
    jfieldID field_ID = (*env)->GetStaticFieldID(env,clszz,"_staticfield","Ljava/lang/String;"); 
    // 根据静态字段ID获取该字段的值 
    jstring jstr = (*env)->GetStaticObjectField(env,clszz,field_ID); 
    // 将jstring类型转换为char数组 
    const char* chars = (*env)->GetStringUTFChars(env,jstr,0); 
    syslog(LOG_ERR, "Before staticfield() changes = %s", chars); 
    // 根据静态字段ID设置该字段的值 
    jstring str = (*env)->NewStringUTF(env,"hello static _staticfield!"); 
    (*env)->SetStaticObjectField(env,clszz,field_ID,str); 
    // 根据静态字段ID获取该字段的值 
    jstring jstr2 = (*env)->GetStaticObjectField(env,clszz,field_ID); 
    // 将jstring类型转换为char数组 
    const char* chars2 = (*env)->GetStringUTFChars(env,jstr2,0); 
    syslog(LOG_ERR, "After staticfield() changes = %s", chars2); 
    return jstr2; 
 
JNIEXPORT jstring JNICALL 
Java_com_example_ndk_1jin01_MainActivity_staticfield1(JNIEnv * env, jobject obj){ 
    // 反射获取对应的类 
    jclass clszz = (*env)->FindClass(env,"com/example/ndk_jin01/MainActivity"); // java这如果声明native方法为static,参数已经传入了clazz,可以跳过这步 
    // 获取静态字段ID 
    jfieldID field_ID = (*env)->GetStaticFieldID(env,clszz,"_staticfield","Ljava/lang/String;"); 
    // jfieldID field_ID = (*env)->GetFieldID(env,clszz,"_staticfield","Ljava/lang/String;"); // '_staticfield' is a static field and should be acquired by 'GetStaticFieldID'
    // 根据静态字段ID获取该字段的值 
     jstring jstr = (*env)->GetStaticObjectField(env,clszz,field_ID); 
    // 将jstring类型转换为char数组 
    const char* chars = (*env)->GetStringUTFChars(env,jstr,0); 
    syslog(LOG_ERR, "Before staticfield1() changes = %s", chars); 
    // 根据静态字段ID设置该字段的值 
    jstring str = (*env)->NewStringUTF(env,"hello _staticfield!"); 
    (*env)->SetStaticObjectField(env,clszz,field_ID,str); 
    // 根据静态字段ID获取该字段的值 
    jstring jstr2 = (*env)->GetStaticObjectField(env,clszz,field_ID); 
    // 将jstring类型转换为char数组 
    const char* chars2 = (*env)->GetStringUTFChars(env,jstr2,0); 
    syslog(LOG_ERR, "After staticfield1() changes = %s", chars2); 
    return jstr2; 
 
// 10.在native层反射获取java层静态方法 
JNIEXPORT jstring JNICALL 
Java_com_example_ndk_1jin01_MainActivity_staticmethod(JNIEnv * env, jclass clszz){ 
    // 获取静态方法方法的ID 
    jmethodID method_ID = (*env)->GetStaticMethodID(env,clszz,"staticmethod_text","(Ljava/lang/String;D)Ljava/lang/String;"); 
    // 调用java层方法 
    jstring str = (*env)->NewStringUTF(env,"staticmethod!"); 
    jstring jstr = (*env)->CallStaticObjectMethod(env,clszz,method_ID,str,3.14); 
    const char* chars = (*env)->GetStringUTFChars(env,jstr,0); 
    syslog(LOG_ERR, "method()  = %s", chars); 
    return jstr; 
 
// 11. 在native层反射实例化对象返回给java层 
JNIEXPORT jobject JNICALL 
Java_com_example_ndk_1jin01_MainActivity_ss(JNIEnv *env, jclass clazz, jstring data) { 
    // 反射获取对应的类 
    jclass cls = (*env)->FindClass(env, "com/example/ndk_jin01/SignQuery"); 
    // 获取类SignQuery的构造函数ID 
    jmethodID init = (*env)->GetMethodID(env, cls, "<init>","(Ljava/lang/String;Ljava/lang/String;)V"); 
    // C语言中的某个功能data数据进行加密,省略 
    jstring sign = (*env)->NewStringUTF(env, "hahahahhaha"); 
    // 实例化对象 new SignQuery(...)    jobject cls_obj = (*env)->NewObject(env, cls, init, sign, data); 
    syslog(LOG_ERR, "ss()  = %p", cls_obj); 
    return cls_obj; // 加密后的数据保存到一个对象中返回给java层 
}

4. 动态注册

  • 新增一个app\src\main\cpp\dynamic.c
    #坑/逆向/app逆向/IDA/动态注册 逆向场景::如果so文件的导出函数没有看到目标native函数。那很可能这个native函数是动态注册的::在JNI_OnLoad()中进行动态注册:(*env)->RegisterNatives(env, clazz, gMethods, 1); // 第三个参数时描述了描述C语言的函数和Java层native方法的对应关系的数组,找到gMethods数组就可以看到C语言的函数和Java层native方法的对应关系了
  • 情况1:动态注册函数的name sig被加密 函数地址可以正常得到用frida hook RegisterNative函数 打印出name sig address
  • 情况2:JNI_OnLoad函数被处理了(混淆 指令) -> RegisterNative 找不到IDA快捷键 ctrl+s 跳到 .data区域 看看有没有动态注册函数地址
  • 情况3:动态注册偏移处 三个字段都被处理 能看到的之后很多函数体用frida脚本去hook 打印出函数地址
  • @todo
    #坑/逆向/app逆向/IDA/动态注册 #技术栈/app逆向/逆功能/程序奔溃 程序奔溃::动态注册代码有问题,具体原因@todo
1
2
3
4
+ "v7() = " + v7(1,2) + '\n' 
// + "v8() = " + v8(1,2) + '\n' // 执行这里程序就奔溃 E/ample.ndk_jin0: No implementation found for int com.example.ndk_jin01.MainActivity.v8(int, int) (tried Java_com_example_ndk_1jin01_MainActivity_v8 and Java_com_example_ndk_1jin01_MainActivity_v8__II)                   
+ "v8() = " + v8(1,2) + '\n' // 改名也没有效果,不是名字的问题,应该是动态注册的是后面的函数没有注册上,调整注册顺序试试 
// + "v7() = " + v7(1,2) + '\n'  // 的确是动态注册的时候出了问题,具体原因@todo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// app\src\main\cpp\dynamic.c
#include <jni.h>   // 必须引入jni.h的头文件,因为后面声明的方法中,会引用到jni.h中的类型; 
#include <syslog.h> // native层要用syslog()输出日志 
 
jint add(JNIEnv *env, jobject obj, jint v1, jint v2) { 
    syslog(LOG_ERR, "v7()  = %d", (v1 + v2)); 
    return v1 + v2; 
 
jint sub(JNIEnv *env, jobject obj, jint v1, jint v2) { 
    syslog(LOG_ERR, "v8()  = %d", (v1 *- v2)); 
    return v1 - v2; 
// 描述C语言的函数和Java层native方法的对应关系的数组 
static JNINativeMethod gMethods[] =
        // java层方法   方法签名   c/c++函数 
        {"v8", "(II)I", (void *) sub}, 
        {"v7", "(II)I", (void *) add}, 
}; 
 
 
JNIEXPORT jint JNICALL 
JNI_OnLoad(JavaVM *vm, void *reserved) { // 加载.so时候,先执行这个JNI_OnLoad方法 
 
    JNIEnv *env = NULL; 
 
    // 在java虚拟机中获取env 
    if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) { 
        return JNI_ERR; 
    
 
    // 找到Java中的类 
    jclass clazz = (*env)->FindClass(env, "com/example/ndk_jin01/MainActivity"); 
 
    // 将类中的方法注册到JNI中 (RegisterNatives)    int res = (*env)->RegisterNatives(env, clazz, gMethods, 1); // 第三个参数时描述了描述C语言的函数和Java层native方法的对应关系的数组 
    if (res < 0) { 
        return JNI_ERR; 
    
 
    return JNI_VERSION_1_6; 
}

5. 配置Makefile

编写app\src\main\cpp\CMakeLists.txtMakefile文件,NDK编译生成动态链接库编译命令:ndk-build

6. 运行结果


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 2
支持
分享
最新回复 (8)
雪    币: 55
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
你好,大哥可以帮帮忙吗?
2023-2-13 11:43
1
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
这个具体原因应该是 虽然JNINativeMethod结构体数组里有两个元素 但在注册时最后一个参数填的是1 所以第二个元素动态绑定不了

// + "v8() = " + v8(1,2) + '\n' // 执行这里程序就奔溃 E/ample.ndk_jin0: No implementation found for int com.example.ndk_jin01.MainActivity.v8(int, int) (tried Java_com_example_ndk_1jin01_MainActivity_v8 and Java_com_example_ndk_1jin01_MainActivity_v8__II)                   
+ "v8() = " + v8(1,2) + '\n' // 改名也没有效果,不是名字的问题,应该是动态注册的是后面的函数没有注册上,调整注册顺序试试 
// + "v7() = " + v7(1,2) + '\n'  // 的确是动态注册的时候出了问题,具体原因@todo
2023-2-14 16:29
1
雪    币: 3738
活跃值: (3872)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
感谢分享。
2023-2-14 18:44
1
雪    币: 86
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
万里星河 这个具体原因应该是 虽然JNINativeMethod结构体数组里有两个元素 但在注册时最后一个参数填的是1 所以第二个元素动态绑定不了 // + "v8() = " + v ...
对的,感谢
2023-2-15 16:28
0
雪    币: 86
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
mb_tawpgynq 你好,大哥可以帮帮忙吗?
你好 什么问题?
2023-2-15 16:29
0
雪    币: 55
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
公众号坚毅猿 你好 什么问题?
大哥加qq说吧,1059356385
2023-2-16 08:50
0
雪    币: 49
活跃值: (2238)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
在实际应用中,动态注册那一步,比如在这个函数中:jint sub(JNIEnv *env, jobject obj, jint v1, jint v2) { 
    syslog(LOG_ERR, "v8()  = %d", (v1 *- v2)); 
    return v1 - v2; 
} 如何调用原函数呢?
2023-9-26 21:00
0
雪    币: 3070
活跃值: (30876)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
感谢分享
2023-9-27 09:49
1
游客
登录 | 注册 方可回帖
返回
//