【逆向】Android Studio JNI开发基础

JNI开发

在安卓程序中实现Java和C代码的相互调用。

JNI,java native interface,Java本地开发接口,实现Java和C语言之间的相互调用。

1.NDK环境配置

打开Android Studio中的设置,左上角FIle-》setting,然后如下图配置

在这里插入图片描述

2.创建项目(快速上手)

在这里插入图片描述

在这里插入图片描述

默认设置即可

在这里插入图片描述

3.目录结构图

在这里插入图片描述

在默认创建号的项目中,在MainActivity加入两行假设加密的算法值

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());


        //调用核心算法,获取签名的值,发送到后端的API
        String signString=EncryptUtils.v0(11,"alex");

        //更NB的算法,让他以C语言的形式实现
        int sign2=EncryptUtils.v1(11,22);
        Log.e("---->",String.valueOf(sign2));
    }

MainActivity同级目录下创建加密类EncryptUtils

package com.example.jnistudy;

//定义一个加密类,假设我们调用这个加密
public class EncryptUtils {

    static {
        System.loadLibrary("enc");
    }

    public static String v0(int n1,String n2){
        //写了很多代码,实现了一个很NB的算法。
        return "alexdsb";
    }

    //C语言实现的功能调用入口也放这,加native作为标识
    public static native int v1(int n1,int n2);
}

创建后在cpp目录下,创建enc.c文件

#include <jni.h>

//跟加密方法的返回值一样,int则位jint
JNIEXPORT jint

//Java_开头,后续追加包名以及具体的方法名
JNICALL
Java_com_example_jnistudy_EncryptUtils_v1(JNIEnv *env, jclass clazz, jint n1, jint n2){
    //TODO:implement v1()
    //具体实现逻辑

    return n1+n2+100;
}

然后再去CMakeLists.txt文件中创建add_library

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.

# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)

# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("jnistudy")

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.


#额外加一个add_libray
add_library(
        enc
        SHARED
        enc.c
        )


add_library(${CMAKE_PROJECT_NAME} SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        native-lib.cpp)

# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this
# build script, prebuilt third-party libraries, or Android system libraries.
target_link_libraries(
		#配置enc
        enc
        ${CMAKE_PROJECT_NAME}
        # List libraries link to the target library
        android
        log
)

然后启动虚拟机,看控制台是否成功打印加密值133。出现则代表成功创建了java接口调用C语言加密函数的整套流程

在这里插入图片描述

3.1总结一下

安卓开发角度

//java:
package com.example.jnistudy;

//定义一个加密类,假设我们调用这个加密
public class EncryptUtils {

    static {
        System.loadLibrary("enc");
    }
    //C语言实现的功能调用入口也放这,加native作为标识
    public static native int v1(int n1,int n2);
}
//enc.c具体的代码
	Java_com_example_jnistudy_EncryptUtils_v1
        复杂的逻辑和算法来实现
//一些基本配置        

逆向角度:

1.使用jadx反编译apk,得到Java,根据关键字在Java中寻找相关算法
	- 直接找到
	- 找到带有native方法,则应该去找System.loadLibrary("enc");
	- 找到去apk文件中Lib目录下寻找libenc.so
2.IDA对so文件进行反编译,得到C语言,分析C语言+Hook机制	

4.补充一点

实际中上诉代码我们可以做删减,例如去除创建时自带的C文件,以及初始化MainActivity类中关于这个的Java代码,并且清除配置文件CMakeLists中的相关配置

5.类型的关系

java中的数据类型与C语言中的数据类型对应关系(前提:JNI开发

图中只是部分用做示例

在这里插入图片描述

5.1具体实现Demo

MainActivity中代码

package com.example.jnistudy;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

import com.example.jnistudy.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {


    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

		//测试类型关系
        String sign3=EncryptUtils.v2("root");
        Log.e("--->",sign3);
    }
}

EncryptUtils类

package com.example.jnistudy;

//定义一个加密类,假设我们调用这个加密
public class EncryptUtils {

    static {
        System.loadLibrary("enc");
    }

    public static native String v2(String origin);
}

enc.c文件

#include <jni.h>


JNIEXPORT jstring JNICALL
Java_com_example_jnistudy_EncryptUtils_v2(JNIEnv *env, jclass clazz, jstring origin) {
    // TODO: implement v2()
    //jstring转换C语言中的字符串数组
    //假设字符串info = "root"  {r,o,o,t}
    char *info=(*env)->GetStringUTFChars(env,origin,0);
    //*info目前是以指针的形式存在
    info+=1;
    *info='w';

    info+=1;
    *info='x';

    //因为指针偏移了,所以往前推两位则会返回完整的字符串
    info-=2;

    return (*env)->NewStringUTF(env,info);
}

重启项目运行结果如下:

在这里插入图片描述

5.2番外内容

假设逆向某个APP,发现X-sign参数是一个动态的值。例如:9841651sadsadsad

找遍了Java代码,你没找到。咋办?

//很有可能是下面这种情况
public static native String ss(int n1,int n2);
String data=ss(11,22);
x-sign=data

可以利用Hook的机制,Hook JNI语言中的newStringUTF方法。

hook.newStringUTF=function(arg){
	console.log(arg);
	return this.newStringUTF(arg);
}
//通过上诉hook出来的结果 ,我们可以拿9841651sadsadsad这个参数值在结果里面搜索。
//看看是否存在

类型关系对应如图

在这里插入图片描述

6.一波案例(Java调C)

6.1数字处理

public static native int v1(int n1,int n2);
#include <jni.h>

//跟加密方法的返回值一样,int则位jint
JNIEXPORT jint

//Java_开头,后续追加包名以及具体的方法名
JNICALL
Java_com_example_jnistudy_EncryptUtils_v1(JNIEnv *env, jclass clazz, jint n1, jint n2){
    //TODO:implement v1()
    //具体实现逻辑

    return n1+n2+100;
}

6.2字符串修改-指针

  public static native String v2(String origin);
JNIEXPORT jstring JNICALL
Java_com_example_jnistudy_EncryptUtils_v2(JNIEnv *env, jclass clazz, jstring origin) {
    // TODO: implement v2()
    //jstring转换C语言中的字符串数组
    //假设字符串info = "root"  {r,o,o,t}
    char *info=(*env)->GetStringUTFChars(env,origin,0);
    //*info目前是以指针的形式存在
    info+=1;
    *info='w';

    info+=1;
    *info='x';

    //因为指针偏移了,所以往前推两位则会返回完整的字符串
    info-=2;

    return (*env)->NewStringUTF(env,info);
}

6.3字符串修改-数组

public static native String v3(String old);
JNIEXPORT jstring JNICALL
Java_com_example_jnistudy_EncryptUtils_v3(JNIEnv *env, jclass clazz, jstring old) {
    // TODO: implement v3()
    //字符串数组
    char *info=(*env)->GetStringUTFChars(env,old,0);
    info[0]='x';
    info[5]='x';
    return (*env)->NewStringUTF(env,info);
}

6.4字符串拼接

    //字符串拼接
    public static native String v4(String name,String role);

#include <jni.h>
#include <string.h>
#include <syslog.h>
#include<stdlib.h>

int GetStringLen(char *dataString){
    int count=0;
    // \0在C中是隐藏得字符串末尾,
    for (int i = 0; i < dataString[i]!='\0'; ++i) {
        count+=1;
    }
    return count;
}


JNIEXPORT jstring JNICALL
Java_com_example_jnistudy_EncryptUtils_v4(JNIEnv *env, jclass clazz, jstring name, jstring role) {
    // 字符数组=指针
    char *nameString=(*env)->GetStringUTFChars(env,name,0);
    char *roleString=(*env)->GetStringUTFChars(env,role,0);

    //开辟块内存,长度以及算出来了。准备为后续得拼接做准备
    char *result=malloc(GetStringLen(nameString)+ GetStringLen(roleString)+1);

    //拷贝
    strcpy(result,nameString);
    //拼接
    strcat(result,roleString);
    //打印日志并返回结果
    syslog(LOG_ERR,"%s",result);
    return (*env)->NewStringUTF(env,result);
}

6.5字符处理(十六进制处理)

String n5=EncryptUtils.v5("name=ouo&age=18");
//字符处理
public static native String v5(String data);
JNIEXPORT jstring JNICALL
Java_com_example_jnistudy_EncryptUtils_v5(JNIEnv *env, jclass clazz, jstring data) {
    //"name=ouo&age=19"
    char *urlParams=(*env)->GetStringUTFChars(env,data,0);
    int size= GetStringLen(urlParams);

    //v34={1,9,,,,,}
    char v34[size * 2]; //扩大长度,开辟了一块内存
    char  *v28=v34;

    for (int i = 0; i < urlParams[i]!='\0'; ++i) {
//        syslog(LOG_ERR,"%02x",urlParams[i]);
        //转换成十六进制存入到v34
        sprintf(v28,"%02x",urlParams[i]);
        v28+=2;
    }
    //最终返回
    return (*env)->NewStringUTF(env,v34);
}

6.6字节处理

    //字节处理
    public static native String v6(byte[] data);
#include <jni.h>
#include <string.h>
#include <syslog.h>
#include<stdlib.h>

JNIEXPORT jstring JNICALL
Java_com_example_jnistudy_EncryptUtils_v6(JNIEnv *env, jclass clazz, jbyteArray data) {
    // TODO: implement v6()
    char *byteArray=(*env)->GetByteArrayElements(env,data,0);
    int size=(*env)->GetArrayLength(env,data);

    char v34[size*2];
    char *v28=v34;

    for (int i = 0; i < byteArray[i]!='\\0'; ++i) {
        sprintf(v28,"%02x",byteArray[i]);
        v28+=2;
    }
    return (*env)->NewStringUTF(env,v34);
}

6.7字节处理-案例

    //字节处理
    public static native String v7(byte[] data);
#include <jni.h>
#include <string.h>
#include <syslog.h>
#include<stdlib.h>
JNIEXPORT jstring JNICALL
Java_com_example_jnistudy_EncryptUtils_v7(JNIEnv *env, jclass clazz, jbyteArray data) {
    char *byteArray=(*env)->GetByteArrayElements(env,data,0);
    int size=(*env)->GetArrayLength(env,data);

    char v34[size*2];
    char *v28=v34;

    int v29=0;
    do {
        sprintf(v28,"%02x",byteArray[v29++]);
        v28+=2;
    } while (v29!=size);

    return (*env)->NewStringUTF(env,v34);
}

7.一波案例(C调用Java)

静态方法

//创建一个实体类
package com.example.jnistudy;

/**
 * 准备让C中调用Java中得某个方法
 */
public class SignQuery {
    public static String getPart1(){
        return "ouo";
    }

    public static String getPart2(int n1,int n2){
        return "ouo";
    }

    public static String getPart3(int n1,String n2){
        return "ouo";
    }
}

//字符串修改-指针--接口类
public static native String v2(String origin);

JNIEXPORT jstring JNICALL
Java_com_example_jnistudy_EncryptUtils_v2(JNIEnv *env, jclass clazz, jstring origin) {
    // TODO: implement v2()
    //jstring转换C语言中的字符串数组
    //假设字符串info = "root"  {r,o,o,t}
    char *info=(*env)->GetStringUTFChars(env,origin,0);
    //*info目前是以指针的形式存在
    info+=1;
    *info='w';

    info+=1;
    *info='x';

    //因为指针偏移了,所以往前推两位则会返回完整的字符串
    info-=2;

    //找到类,C调用java
    jclass cls=(*env)->FindClass(env,"com/example/jnistudy/SignQuery");
    //找到方法
    jmethodID method1=(*env)->GetStaticMethodID(env,cls,"getPart1", "()Ljava/lang/String;");
    jmethodID method2=(*env)->GetStaticMethodID(env,cls,"getPart2", "(II)Ljava/lang/String;");
    jmethodID method3=(*env)->GetStaticMethodID(env,cls,"getPart3", "(ILjava/lang/String;)Ljava/lang/String;");

    //执行方法1
    jstring res1=(*env)->CallStaticObjectMethod(env,cls,method1);
    //获得方法返回结果指针
    char *data=(*env)->GetStringUTFChars(env,res1,0);

    return (*env)->NewStringUTF(env,info);
}

实例方法

创建测试实体类SignQuery2

package com.example.jnistudy;

public class SignQuery2 {
    String name;
    String city;
    int count;


    public SignQuery2(String city, int count) {
        this.name="ouo";
        this.city = city;
        this.count = count;
    }


    public String getPart1(){
        return this.name;
    }

    public String getPart2(int len){
        return "root".substring(2);
    }
    public String getPart3(String prev){
        return "xxx-";
    }
    public int getPart4(String prev,int v1){
        return 100;
    }
}

在加密类中创建接口

   public static native String v8();

C中代码

#include <jni.h>
#include <string.h>
#include <syslog.h>
#include<stdlib.h>

JNIEXPORT jstring JNICALL
Java_com_example_jnistudy_EncryptUtils_v8(JNIEnv *env, jclass clazz) {
    //找到类
    jclass cls=(*env)->FindClass(env,"com/example/jnistudy/SignQuery2");

    //找到构造方法
    jmethodID init=(*env)->GetMethodID(env,cls,"<init>", "(Ljava/lang/String;I)V");

    //实例化对象new SignQuery2(...)
    //(*env)->NewStringUTF(env,"gg")--如果参数为String需要转成jstring类型
    jobject cls_obj=(*env)->NewObject(env,cls,init,(*env)->NewStringUTF(env,"gg"),22);

    //找到方法
    jmethodID jmethodId1=(*env)->GetMethodID(env,cls,"getPart1", "()Ljava/lang/String;");
    jmethodID jmethodId2=(*env)->GetMethodID(env,cls,"getPart2", "(I)Ljava/lang/String;");
    jmethodID jmethodId3=(*env)->GetMethodID(env,cls,"getPart3", "(Ljava/lang/String;)Ljava/lang/String;");
    jmethodID jmethodId4=(*env)->GetMethodID(env,cls,"getPart4", "(Ljava/lang/String;I)I");

    //执行方法
    jstring res1=(*env)->CallObjectMethod(env,cls_obj,jmethodId1);
    jstring res2=(*env)->CallObjectMethod(env,cls_obj,jmethodId2,100);
    jstring res3=(*env)->CallObjectMethod(env,cls_obj,jmethodId3,(*env)->NewStringUTF(env,"ouo"));
    jint res4=(*env)->CallIntMethod(env,cls_obj,jmethodId4,(*env)->NewStringUTF(env,"hhhhh"),18);

    char *p1=(*env)->GetStringUTFChars(env,res1,0);
//    char *p2=(*env)->GetStringUTFChars(env,res2,0);
//    char *p3=(*env)->GetStringUTFChars(env,res3,0);
//    char *p4=(*env)->GetStringUTFChars(env, (jstring) res4, 0);
    return (*env)->NewStringUTF(env,p1);
}

额外补充场景:

创建实体类SignQuery3

package com.example.jnistudy;

public class SignQuery3 {
    public String token;
    public String params;

    public SignQuery3(String token, String params) {
        this.token = token;
        this.params = params;
    }

    @Override
    public String toString() {
        return params+"&sign="+token;
    }
}

在加密工具类EncryptUtils中新增接口

    //返回实体类
    public static native SignQuery3 ss(String data);

在C文件中加密


JNIEXPORT jobject JNICALL
Java_com_example_jnistudy_EncryptUtils_ss(JNIEnv *env, jclass clazz,jstring data) {
    //找到类
    jclass cls=(*env)->FindClass(env,"com/example/jnistudy/SignQuery3");
    //找到构造方法
    jmethodID init=(*env)->GetMethodID(env,cls,"<init>", "(Ljava/lang/String;Ljava/lang/String;)V");

    //C语言中的某个功能data数据进行加密
    jstring sign=(*env)->NewStringUTF(env,"hahah");
    jobject cls_obj=(*env)->NewObject(env,cls,init,sign,data);
    return cls_obj;
}

在MainActivity文件中调用

      //假设通过实体类加密
        SignQuery3 ss = EncryptUtils.ss("aid=123&page=9&size=19");
        String result=ss.toString();
//结果:aid=123&page=9&size=19&sign=hahah
        Log.e("---->",result);

上述场景就是有的加密是通过上述流程完成,主打一个出其不意,通过返回一个实体类,并将加密放在实体类的某个函数上,再通过C中加密,最终返回实体类后在toString取出来。

8.静态注册与动态注册

静态注册

  • java中得native方法
package com.example.jnistudy;   
public class EncryptUtils {
	static {
        System.loadLibrary("enc");
    }   
	//C语言实现的功能调用入口也放这,加native作为标识
    public static native int v1(int n1,int n2);
}
  • C语言函数名的对应关系
#include <jni.h>
//跟加密方法的返回值一样,int则位jint
JNIEXPORT jint JNICALL //Java_开头,后续追加包名以及具体的方法名
Java_com_example_jnistudy_EncryptUtils_v1(JNIEnv *env, jclass clazz, jint n1, jint n2){
    //TODO:implement v1()
    //具体实现逻辑

    return n1+n2+100;
}

逆向时比较方便,定位算法在so文件的那个函数中定义

动态注册

Java的native方法

package com.example.jnistudy;

/**
 * 测试动态注册
 */
public class DynamicUtils {
    static {
        System.loadLibrary("dym");
    }
    public static native String testOne(int v1,int v2);
}

  • 注意RegisterNatives的第三个参数,那里面放了函数对应关系
#include <jni.h>

//对应着方法的参数
jstring plus(JNIEnv *env, jclass clazz, jint n1, jint n2){
    jstring sign=(*env)->NewStringUTF(env,"ouohaha");
    return sign;
}

//对应动态注册的那个加密类里面的方法数组
static JNINativeMethod methods[]={
        {"testOne", "(II)Ljava/lang/String;",(void *)plus},
};



JNIEXPORT jint

JNICALL JNI_OnLoad(JavaVM *vm,void *reserved){
    JNIEnv *env=NULL;

    //在java虚拟机中获取env
    if((*vm)->GetEnv(vm,(void **) &env,JNI_VERSION_1_6)!=JNI_OK){
        return JNI_ERR;
    }


    //3.调用FindClass方法,获取目标java对象
    jclass clazz=(*env)->FindClass(env,"com/example/jnistudy/DynamicUtils");
    if (clazz == NULL){
        return JNI_ERR;
    }
    //4.调用RegisterNatives方法,传入env,java对象、JNINativeMethod数组、注册数目,执行动态注册
    jint result=(*env)->RegisterNatives(env,clazz, methods, 1);
    if (result < 0){    // 注册失败会返回一个负值
        return JNI_ERR;
    }
    return JNI_VERSION_1_6;
}

总结

1.反编译APK用jadx,反编译so文件用IDA。

2.以后逆向时,如果遇到某个关键字寻找生成过程,native,一定是基于C实现

- 先找so文件 + java代码System.loadLibrary("xx");--->再去lib目录下找到对应的so文件
- 导出函数-》静态注册 Java_包名_类名_方法名
- 动态注册 -> RegisterNatives的第三个参数是关键

3.逆向某个参数时,怀疑是C实现

可以尝试去Hook->NewStringUTF,生成字符串,返回给java

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OUO~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值