jni开发
目标:在安卓程序中实现Java和C代码的相互调用。
JNI,java native interface ,Java本地开发接口,实现JAVA和C语言之间的相互调用。

NDK
NDK是JNI开发的工具包。
NDK,Native Develop Kits,本地开发工具(在Android Studio中下载即可)。

创建项目
-
普通项目:Empty Activity(Java)
-
jni项目:Native C++(Java + C)
创建的项目多了一些内容和配置(基于C++实现了一个算法,并在Java中进行了调用)。 - 有了默认配置后,我们就不需要自己的手动配置了。 - 会生成一些我们用不到的默认文件,等我们学会自己再回来删除他默认的这些文件。




快速上手
新建Java类
新建一个java的类,模拟实现项目中的核心算法。
-
v0,用Java实现的算法
-
v1,用C实现算法

创建C文件



#include <jni.h>
JNIEXPORT jint
JNICALL Java_com_nb_s4luffy_EncryptUtils_v1(JNIEnv* env,jclass clazz, jint v1,jint v2) {
// 编写C语言的代码
return v1 + v2;
}
不想手动编写的话,可以用命令自动生成:
>>>cd app/src/main/java
>>>javah com.nb.s4luffy.EncryptUtils
com.nb.s3song.EncryptUtils

C编译配置

再次回到 enc.c 文件中,再点击sync now就不再提示错误了。
加载C文件

Java调用C

删除默认native-lib
在创建项目时,自动生成的native-lib可以删除了。



逆向和反编译





关于类型

package com.nb.s4luffy;
class EncryptUtils {
static {
System.loadLibrary("enc");
}
public static native int v1(int v1, int v2);
public static native String v1(String origin);
}
JNIEXPORT jint
JNICALL Java_com_nb_s4luffy_EncryptUtils_v1(JNIEnv *env, jclass clazz, jint v1, jint v2) {
// 编写C语言的代码
return v1 + v2;
}
JNIEXPORT jstring
JNICALL Java_com_nb_s4luffy_EncryptUtils_v2(JNIEnv *env, jclass obj, jstring origin) {
// 获取参数中传入的字符串
const char *string = (*env)->GetStringUTFChars(env, origin, 0);
char data[4] = {string[0], string[2], string[4]}; // wpi
// C语言中的字符串转换成jstring
jstring response = (*env)->NewStringUTF(env, data);
return response; // jstring -> Java的字符串
}

一波案例(Java调C)
数字处理
public static native int v1(int v1, int v2);
#include <jni.h>
JNIEXPORT jint
JNICALL Java_com_nb_s4luffy_EncryptUtils_v1(JNIEnv *env, jclass clazz, jint v1, jint v2) {
return v1 + v2;
}
字符串修改-指针
public static native String v2(String old);
#include <jni.h>
#include <string.h>
#include <syslog.h>
JNIEXPORT jstring
JNICALL
Java_com_nb_s5dabai_EncryptUtils_v2(JNIEnv *env, jclass clazz, jstring old) {
// char info[] = {'w','w','p','e',..'w'...};
char *info = (*env)->GetStringUTFChars(env, old, 0);
syslog(LOG_ERR, "%s", info);
info += 1;
*info = 'w';
info += 3;
*info = 'w';
info -= 4;
syslog(LOG_ERR, "%s", info);
return (*env)->NewStringUTF(env, info);
}
字符串修改-数组
public static native String v3(String old);
JNIEXPORT jstring
JNICALL
Java_com_nb_s4luffy_EncryptUtils_v3(JNIEnv *env, jclass clazz, jstring old) {
// 字符串数组
char *info = (*env)->GetStringUTFChars(env, old, 0);
info[0] = 'x';
info[5] = 'x';
return (*env)->NewStringUTF(env, info);
}
字符串拼接
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;
for (int i = 0; dataString[i] != '\0'; i++) {
count += 1;
}
return count;
}
JNIEXPORT jstring
JNICALL
Java_com_nb_s4luffy_EncryptUtils_v4(JNIEnv *env, jclass clazz, jstring name, jstring role) {
// 字符数组=指针 alex dsb
char *nameString = (*env)->GetStringUTFChars(env, name, 0);
char *roleString = (*env)->GetStringUTFChars(env, role, 0);
// alexdsb
char *result = malloc(GetStringLen(nameString) + GetStringLen(roleString) + 1);
strcpy(result, nameString);
strcat(result, roleString);
syslog(LOG_ERR, "%s", result);
return (*env)->NewStringUTF(env, result);
}
字符处理
String n5 = EncryptUtils.v5("name=wupeiqi&age=19");
public static native String v5(String data);
#include <jni.h>
#include <string.h>
#include <syslog.h>
#include<stdlib.h>
int GetStringLen(char *dataString) {
int count = 0;
for (int i = 0; dataString[i] != '\0'; i++) {
count += 1;
}
return count;
}
JNIEXPORT jstring
JNICALL
Java_com_nb_s4luffy_EncryptUtils_v5(JNIEnv *env, jclass clazz, jstring data) {
// "name=wupeiqi&age=19"
char *urlParams = (*env)->GetStringUTFChars(env, data, 0);
int size = GetStringLen(urlParams);
// v34 = {1,2,,,,,,,,,,,}
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]);
v28 += 2;
}
return (*env)->NewStringUTF(env, v34);
}
字节处理
String n6 = EncryptUtils.v6( "name=wupeiqi&age=19".getBytes() );
public static native String v6(byte[] data);
#include <jni.h>
#include <string.h>
#include <syslog.h>
#include<stdlib.h>
JNICALL
Java_com_nb_s4luffy_EncryptUtils_v6(JNIEnv *env, jclass clazz, jbyteArray data) {
// jbyte *byteArray = (*env)->GetByteArrayElements(env, data, 0);
char *byteArray = (*env)->GetByteArrayElements(env, data, 0);
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;
}
return (*env)->NewStringUTF(env, v34);
}
字节处理-案例
public static native String v7(byte[] data);
#include <jni.h>
#include <string.h>
#include <syslog.h>
#include<stdlib.h>
JNIEXPORT jstring
JNICALL
Java_com_nb_s4luffy_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);
}
一波案例(C调Java)
静态方法
String n8 = EncryptUtils.v8();
package com.nb.s4luffy;
class EncryptUtils {
static {
System.loadLibrary("enc");
}
public static native String v8();
}
#include <jni.h>
#include <string.h>
#include <syslog.h>
#include<stdlib.h>
JNIEXPORT jstring
JNICALL
Java_com_nb_s4luffy_EncryptUtils_v8(JNIEnv *env, jclass clazz) {
// 找到类
jclass cls = (*env)->FindClass(env, "com/nb/s4luffy/SignQuery");
// 找到方法
jmethodID method1 = (*env)->GetStaticMethodID(env, cls, "getPart1", "()Ljava/lang/String;");
jmethodID method2 = (*env)->GetStaticMethodID(env, cls, "getPart2", "(I)Ljava/lang/String;");
jmethodID method3 = (*env)->GetStaticMethodID(env, cls, "getPart3","(Ljava/lang/String;)Ljava/lang/String;");
jmethodID method4 = (*env)->GetStaticMethodID(env, cls, "getPart4", "(Ljava/lang/String;I)I");
// 执行方法
jstring res1 = (*env)->CallStaticObjectMethod(env, cls, method1);
jstring res2 = (*env)->CallStaticObjectMethod(env, cls, method2, 100);
jstring res3 = (*env)->CallStaticObjectMethod(env,cls,method3,(*env)->NewStringUTF(env, "hahahahh"));
jint res4 = (*env)->CallStaticIntMethod(env,cls,method4,(*env)->NewStringUTF(env, "hahahahh"),18);
char *p1 = (*env)->GetStringUTFChars(env, res1, 0);
char *p2 = (*env)->GetStringUTFChars(env, res2, 0);
char *p3 = (*env)->GetStringUTFChars(env, res3, 0);
char *result = malloc(50);
strcat(result,p1);
strcat(result,p2);
strcat(result,p3);
return (*env)->NewStringUTF(env, result);
}
package com.nb.s4luffy;
public class SignQuery {
public static String getPart1() {
return "wupeiqi";
}
public static String getPart2(int len) {
return "root".substring(2);
}
public static String getPart3(String prev) {
return "xxx";
}
public static int getPart4(String prev, int v1) {
return 100;
}
}
实例方法
String n8 = EncryptUtils.v8();
package com.nb.s4luffy;
class EncryptUtils {
static {
System.loadLibrary("enc");
}
public static native String v9();
}
#include <jni.h>
#include <string.h>
#include <syslog.h>
#include<stdlib.h>
JNIEXPORT jstring
JNICALL
Java_com_nb_s4luffy_EncryptUtils_v9(JNIEnv *env, jclass clazz) {
// 找到类
jclass cls = (*env)->FindClass(env, "com/nb/s4luffy/SignQuery2");
// 找到构造方法
jmethodID init = (*env)->GetMethodID(env, cls, "<init>", "(Ljava/lang/String;I)V");
// 实例化对象 new SignQuery2(...)
jobject cls_obj = (*env)->NewObject(env, cls, init, (*env)->NewStringUTF(env, "hahahahh"), 22);
// 找到方法
jmethodID method1 = (*env)->GetMethodID(env, cls, "getPart1", "()Ljava/lang/String;");
jmethodID method2 = (*env)->GetMethodID(env, cls, "getPart2", "(I)Ljava/lang/String;");
jmethodID method3 = (*env)->GetMethodID(env, cls, "getPart3","(Ljava/lang/String;)Ljava/lang/String;");
jmethodID method4 = (*env)->GetMethodID(env, cls, "getPart4", "(Ljava/lang/String;I)I");
// 执行方法
jstring res1 = (*env)->CallObjectMethod(env, cls_obj, method1);
jstring res2 = (*env)->CallObjectMethod(env, cls_obj, method2, 100);
jstring res3 = (*env)->CallObjectMethod(env,cls_obj,method3,(*env)->NewStringUTF(env, "hahahahh"));
jint res4 = (*env)->CallIntMethod(env,cls_obj,method4,(*env)->NewStringUTF(env, "hahahahh"),18);
char *p1 = (*env)->GetStringUTFChars(env, res1, 0);
return (*env)->NewStringUTF(env, p1);
}
package com.nb.s4luffy;
public class SignQuery {
String name;
String city;
int count;
public SignQuery(String city, int count) {
this.name = "wupeiqi";
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;
}
}
静态和动态注册
静态注册
上述编写的C语言的函数和Java的对应关系,在函数名上就可以体现,例如:
Java_com_nb_s2long_EncryptUtils_s2
Java_com_nb_s2long_EncryptUtils_s5
Java_com_nb_s2long_EncryptUtils_s8
这种称为静态注册,如果是静态注册,那么在逆向时,是比较方便的,直接可以找到函数在C中的实现。例如:车智赢。




动态注册
package com.nb.s3jni;
class DynamicUtils {
static {
System.loadLibrary("dynamic");
}
public static native int add(int v1, int v2);
}

#include <jni.h>
jint plus(JNIEnv *env, jobject obj, jint v1, jint v2) {
// jclass clazz = (*env)->FindClass(env, "com/nb/fucker/DynamicUtils");
// jmethodID id = (*env)->GetMethodID(env, clazz, "", "(D)V");
// (*env)->NewObject(env,clazz,id)
// 调用Java中类中的方法。
return v1 + v2;
}
static JNINativeMethod gMethods[] = {
{"add", "(II)I", (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;
}
// 找到Java中的类
jclass clazz = (*env)->FindClass(env, "com/nb/s3jni/DynamicUtils");
// 将类中的方法注册到JNI中 (RegisterNatives)
int res = (*env)->RegisterNatives(env, clazz, gMethods, 1);
if (res < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}


这种情况下,在逆向时,就不能直接找对应关系了,就需要去找jni_onload





总结
-
逆向时,很多算法基于Java实现。 jadx
-
逆向时,看到了。
内部算法基于C实现。 想要硬核破解 app解压 lib/libdym.so 不需要:frida-rpc/unidbg![]()
-
反编译so -> ida
-
静态注册(直接分析c)
-
动态注册(jni_onload找到方法,分析)
JNI_Onload方法中 int res = (*env)->RegisterNatives(env, clazz, gMethods, 2);![]()
-
-
分析
-
c语言代码执行
-
调用Java中的方法/对象
"<init>" NewObject com/nb/s4dingning/SignQuery2 "getPart1", "()Ljava/lang/String;"![]()
-
1.反编译apk用jadx、反编译so文件IDA
2.以后逆向时,如果遇到某个关键字寻找生成过程 native,一定是基于C实现 - 先找so文件 + java代码 System.loadLibrary("dym"); -> libdym.so - 导出函数 -> 静态注册 Java报名类名_方法名 - 动态注册 -> RegisterNatives的第三个参数
3.逆向某个参数时,你怀疑是c实现。 Hook -> NewStringUTF,生成字符串 返回给java



浙公网安备 33010602011771号