Java调用so

package com.gdufs.xman;

import android.app.Application;
import android.util.Log;

public class MyApp extends Application {
public static int m = 0;

public native void initSN();

public native void saveSN(String str);

public native void work();

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

public void onCreate() {
initSN();
Log.d("com.gdufs.xman m=", String.valueOf(m));
super.onCreate();
}
}

静态分析方法

IDA打开libmyjni.so:
Jump to segment (Ctrl+S),查找​​​.init_array​​​,没有。
可以再在Exports导出函数里找​​​JNI_Onload​​:

signed int __fastcall JNI_OnLoad(int a1)
{
if ( !(*(int (**)(void))(*(_DWORD *)a1 + 24))() )
{
_android_log_print(2, "com.gdufs.xman", "JNI_OnLoad()");
native_class = (*(int (**)(void))(*(_DWORD *)g_env + 24))();
if ( !(*(int (**)(void))(*(_DWORD *)g_env + 860))() )
{
_android_log_print(2, "com.gdufs.xman", "RegisterNatives() --> nativeMethod() ok");
return 65542;
}
_android_log_print(6, "com.gdufs.xman", "RegisterNatives() --> nativeMethod() failed");
}
return -1;
}

找到​​g_env​​​这种,按下Y(或右键​​Set Item Type​​​),定义成​​JNIEnv * g_env​​​。会自动解析出来​​RegisterNatives​​​,点击右键:​​Force call type​​,回把参数解析出来:

signed int __fastcall JNI_OnLoad(int a1)
{
if ( !(*(int (**)(void))(*(_DWORD *)a1 + 24))() )
{
_android_log_print(2, "com.gdufs.xman", "JNI_OnLoad()");
native_class = ((int (*)(void))(*g_env)->FindClass)();
if ( !(*g_env)->RegisterNatives(g_env, (jclass)native_class, (const JNINativeMethod *)off_5004, 3) )
{
_android_log_print(2, "com.gdufs.xman", "RegisterNatives() --> nativeMethod() ok");
return 65542;
}
_android_log_print(6, "com.gdufs.xman", "RegisterNatives() --> nativeMethod() failed");
}
return -1;
}

可以看到​​(const JNINativeMethod *)off_5004​​函数:

.data:00005004 off_5004        DCD aInitsn             ; DATA XREF: JNI_OnLoad+38↑o
.data:00005004 ; .text:off_1590↑o
.data:00005004 ; "initSN"
.data:00005008 DCD aV ; "()V"
.data:0000500C DCD n1+1
.data:00005010 DCD aSavesn ; "saveSN"
.data:00005014 DCD aLjavaLangStrin ; "(Ljava/lang/String;)V"
.data:00005018 DCD n2+1
.data:0000501C DCD aWork ; "work"
.data:00005020 DCD aV ; "()V"
.data:00005024 DCD n3+1
.data:00005024 ; .data ends

Hook方式

思路:RegisterNatives函数在libart.so里定义,首先在的libart.so找到RegisterNatives的模块,然后从模块里找到符号symbols,从而找到符号地址。
在​​​http://aospxref.com/​​​搜索art项目下,RegisterNatives的定义,找到jni_internal.cc:
​​​http://aospxref.com/android-11.0.0_r21/xref/art/runtime/jni/jni_internal.cc#2298​​ 可以看到RegisterNatives函数的调用参数。

static jint RegisterNatives(
JNIEnv* env,
jclass java_class,
const JNINativeMethod* methods,
jint method_count
)

现在想获取class的名称,Frida已经帮我们定义好了。可以通过项目: https://github.com/frida/frida-java-bridge 搜索​​getClassName​​​找到
​​​https://github.com/frida/frida-java-bridge/blob/fed9e61bfa2fbe5289e834200933b724bf8c7ff6/lib/env.js​

Env.prototype.getClassName = function (classHandle) {
const name = this.vaMethod('pointer', [])(this.handle, classHandle, this.javaLangClass().getName);
try {
return this.stringFromJni(name);
} finally {
this.deleteLocalRef(name);
}
};

这里需要一个Env,可以通过https://frida.re/docs/javascript-api/ 找到​​Java.vm​​​,可以拿到​​Env​​​。
另外,这个还要把方法打印出来,用到了​​​hexdump​​​函数,它可以从内存中把方法打印出来。
函数类型​​​JNINativeMethod​​​的​​定义​​,有3个类型:

typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;

JS Hook代码:

function hook_libart() {
// 找到模块
var module_libart = Process.findModuleByName("libart.so");
console.log(module_libart);
var addr_RegisterNatives = null;
// 枚举模块的所有符号
var symbols = module_libart.enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
var name = symbols[i].name;
// 去掉CheckJNI的函数,只要JNI的
if (name.indexOf("CheckJNI") == -1 && name.indexOf("JNI") > 0) {
if (name.indexOf("RegisterNatives") > 0) {
console.log(name);
// 符号地址
addr_RegisterNatives = symbols[i].address;
}

}
}

if (addr_RegisterNatives) {
// Hook Native 函数, --no-pause -f 方式启动
Interceptor.attach(addr_RegisterNatives, {
onEnter: function (args) {
var java_class = Java.vm.tryGetEnv().getClassName(args[1]);
var methods = args[2];
var method_count = parseInt(args[3]);
console.log("addr_RegisterNatives java_class:", java_class, "method_count:", method_count);
for (var i = 0; i < method_count; i++) {
console.log(methods.add(i * Process.pointerSize * 3).readPointer().readCString());
console.log(methods.add(i * Process.pointerSize * 3 + Process.pointerSize).readPointer().readCString());
var fnPtr = methods.add(i * Process.pointerSize * 3 + Process.pointerSize * 2).readPointer();
console.log(fnPtr); // 打印地址 fnPtr - 模块base地址,即相对地址。

}
}, onLeave: function (retval) {

}
})
}
}

按键G(Jump to address)到​​0x11F9​​​,F5反编译,就可以看到n2函数,注释一下,其实是:​​saveSN​​函数。

Frida 静态分析和Hook Native函数_java


n2参数a1的类型是JNIEnv * 类型(快捷键Y),改为:

Frida 静态分析和Hook Native函数_apache_02


Frida 静态分析和Hook Native函数_c函数_03


参数a3,改名(快捷键N):

Frida 静态分析和Hook Native函数_java_04


Frida可以通过​​stringFromJni或getStringChars ​​获取字符串参数。

Java.vm.tryGetEnv().getStringChars(arg[3])

Hook android_dlopen_ext

Android实现动态库的加载,一般需要调用​​android_dlopen_ext​​​函数。函数​​源码​​:

void* android_dlopen_ext(const char* filename, int flag, const android_dlextinfo* extinfo) {
const void* caller_addr = __builtin_return_address(0);
return __loader_android_dlopen_ext(filename, flag, extinfo, caller_addr);
}

Frida JS代码:

function hook_android_dlopen_ext() {
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
onEnter: function (args) {
this.name = args[0].readCString();
console.log("android_dlopen_ext:", this.name);
}, onLeave: function (retval) {
if (this.name.indexOf("libmyjni.so") > 0) {
hook_native();
}
}
})
}

Frida写文件

Frida读写文件用到了​​File类​​。

Frida 静态分析和Hook Native函数_c函数_05

function frida_file() {
var file = new File("/sdcard/xx.dat", "r+");
file.write("content111");
file.flush();
file.close();
}

用frida调用c函数

模拟调用C函数要用到​​NativeFunction​​函数。

Frida 静态分析和Hook Native函数_java_06


C函数定义见:​​https://www.cplusplus.com/reference/cstdio/​​​ Frida申请空间用​​Memory.alloc​​,申请string空间用​​Memory.allocUtf8String​​。

function c_read_file() {
//fopen: FILE * fopen ( const char * filename, const char * mode );
//fseek: int fseek ( FILE * stream, long int offset, int origin );
//ftell
//fread
//fclose
var fopen = new NativeFunction(Module.findExportByName("libc.so", "fopen"), "pointer", ["pointer", "pointer"]);
var fseek = new NativeFunction(Module.findExportByName("libc.so", "fseek"), "int", ["pointer", "int", "int"]);
var ftell = new NativeFunction(Module.findExportByName("libc.so", "ftell"), "long", ["pointer"]);
var fread = new NativeFunction(Module.findExportByName("libc.so", "fread"), "int", ["pointer", "int", "int", "pointer"]);
var fclose = new NativeFunction(Module.findExportByName("libc.so", "fclose"), "int", ["pointer"]);

var file = fopen(Memory.allocUtf8String("/sdcard/xx.dat"), Memory.allocUtf8String("r+"));
fseek(file, 0, 2);
var size = ftell(file);
var buffer = Memory.alloc(size + 1);
fseek(file, 0, 0); // 文件指针重新回到开头
fread(buffer, size, 1, file);
console.log("buffer:", buffer, buffer.readCString());
fclose(file);

}