前言
每次App重新运行后native函数加载的绝对地址是会变化的,唯一不变的是函数相对于基地址的偏移,因此我们可以在获取模块的基地址后加上固定的偏移地址获取相应函数的地址,Frida中也正好提供了这种方式:先通过Module.findBaseAddress(name)或Module.getBaseAddress(name)两个API函数获取对应模块的基地址,然后通过add(offset)函数传入固定的偏移offset获取最后的函数绝对地址。以下使用stringFromJNI3中的stringFromJNI3作为具体案例讲解下思路和注意的点。
动态注册的函数
// MainActivity.java
package com.roysue.ndktest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import com.roysue.ndktest.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
// Used to load the 'ndktest' library on application startup.
static {
System.loadLibrary("ndktest");
}
private ActivityMainBinding binding;
@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());
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("r0ysue", stringFromJNI3());
}
}
/**
* A native method that is implemented by the 'ndktest' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native String stringFromJNI2();
public native String stringFromJNI3();
}
// native-lib.cpp
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_roysue_ndktest_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
JNIEXPORT jstring JNICALL
Java_com_roysue_ndktest_MainActivity_stringFromJNI2(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
jstring JNICALL
sI3(JNIEnv *env, jobject /*this*/) {
std::string hello = "Hello from C++ stringFromJNI3 r0ysue...";
return env->NewStringUTF(hello.c_str());
}
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
vm->GetEnv((void **) &env, JNI_VERSION_1_6);
JNINativeMethod methods[] = {
{"stringFromJNI3", "()Ljava/lang/String;", (void *) sI3},
};
env->RegisterNatives(env->FindClass("com/roysue/ndktest/MainActivity"), methods, 1);
return JNI_VERSION_1_6;
}
获取stringFromJNI3函数的偏移地址
使用objection遍历ndktest.so模块的导出函数,会发现找不到stringFromJNI3()字符串相关函数了。这里介绍一个项目 frida_hook_libart,仓库地址为frida_hook_libart。这个项目中包含着一些对JNI函数和art相关函数的Frida Hook脚本,这里要使用的是RegisterNatives()函数的Hook脚本:hook_RegisterNatives.js。
执行以下命令进行遍历
frida -UF -f com.roysue.ndktest -l hook_RegisterNatives.js
注意:fnoffset不是地址偏移值,箭头指向的0x1f5bc才是正确的偏移量
hookNative.js
Hook脚本如下
function hook_nattive3() {
var libnative_addr = Module.findBaseAddress("libndktest.so");
console.log("libndktest_addr is =>", libnative_addr);
var stringFromJNI3 = libnative_addr.add(0x1f5bc);
console.log("stringFromJNI3 address is=>", stringFromJNI3);
Interceptor.attach(stringFromJNI3, {
onEnter: function (args) {
console.log("jnienv pointer =>", args[0]);
console.log("jobj pointer =>", args[1]);
},
onLeave: function (retval) {
console.log("retval is =>", Java.vm.getEnv().getStringUtfChars(retval, null).readCString());
console.log("============================")
}
})
}
function main() {
hook_nattive3();
}
setImmediate(main)
执行Hook命令:
frida -UF -l hookNative.js
Hook结果
注意:App不重新编译,基地址就不会变,如果App代码发生改变,并重新编译运行,那么新的App的函数的偏移地址是会发生改变的,此时应当重新使用hook_RegisterNatives.js脚本获取相应函数的新偏移地址并修改Hook脚本,然后再进行注入。