JNI 导入
IDA中加入头文件解析
File - loadfile - parse C header
选择 jni.h 文件- 鼠标指向函数 - 右键 -
Convert to struct *
- 选择
_JNIEnv
Hook 方式
So 的 Hook 分为 两种类型及: PLT/GOT 与 InLine , 《Android中Native Hook的两种方式的区别(PLT hook与Inline hook)》 个人而言比较喜欢 InLine Hook
PLT/GOT Hook
通俗的说就是利用 连接器 和 符号 进行 Hook
, 并不需要地址
InLine Hook
通俗的说就是通过 内存地址 进行 hook
function inline_hook() {
var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so");
if (libnative_lib_addr) {
console.log("libnative_lib_addr:", libnative_lib_addr);
var addr_101F4 = libnative_lib_addr.add(0x102BC);
console.log("addr_101F4:", addr_101F4);
Java.perform(function () {
Interceptor.attach(addr_101F4, {
onEnter: function (args) {
console.log("addr_101F4 OnEnter :", this.context.PC, this.context.x1, this.context.x5, this.context.x10);
},
onLeave: function (retval) {
console.log("retval is :", retval)
}
}
)
})
}
}
引入指定 So
参考本文下面的 Art Trace
var module_libext = null;
if (Process.arch === "arm64") {
module_libext = Module.load("/data/app/libext64.so");
} else if (Process.arch === "arm") {
module_libext = Module.load("/data/app/libext.so");
}
if (module_libext != null) {
var addr_PrettyMethod = module_libext.findExportByName("PrettyMethod");
var PrettyMethod = new NativeFunction(addr_PrettyMethod, "void", ["pointer", "pointer", "pointer", "int"]);
if (ArtMethod_Invoke) {
var foo_ArtMethod_PrettyMethod = new NativeFunction(ArtMethod_PrettyMethod, "pointer", ["pointer", "int"]);
console.log(foo_ArtMethod_PrettyMethod);
Interceptor.attach(ArtMethod_Invoke, {
onEnter: function (args) {
try {
var result = Memory.alloc(0x100);
PrettyMethod(ArtMethod_PrettyMethod, args[0], result, 0x100);
console.log(result.readCString());
} catch (error) {
console.log(error);
}
}, onLeave: function (retval) {
}
});
}
}
数据读写
读数据
显示参数或者函数内容
readCString
读字符串的时候用hexdump
读取hex信息readByteArray
读指定长度内存 (要写内存的话只能用他的兄弟 writeByteArray 了)env
主动调用函数读取
// so 地址 : soAddr
// 方法1:(字符串)根据地址直接读
soAddr.add(0x2c00).readCString();
// 方法2 hexdump(根据地址显示dump内容)
hexdump(soAddr.add(0x2c00));
// 方法3 功能同上,但指定长度
// 读16个字节 将内存数据,输出可见数据 (必须用console.log才能输出)
soAddr.add(0x2c00).readByteArray(16);
// 使用和 navcat 中相同的方法转换成 cstring, 如果是 jstring 需要先转换为 cstring 才能阅读
Interceptor.attach(nativePointer, {
onEnter: function(args){
var env = Java.vm.getEnv();
var jstring = env.getStringUtfChars(args[2], null).readCString();
},
onLeave: function(retval){}
});
写数据
用来修改一些函数的返回值等等
// 指定地址写入数据
soAddr.add(0x2C00).writeByteArray(stringToBytes("love you"));
// 替换返回数据,具体运用的 env 函数要看 ndk 是怎么写的。
Interceptor.attach(nativePointer, {
onEnter: function(args){
},
onLeave: function(retval){
var env = Java.vm.getEnv();
var newRetval = env.newStringUtf("newRetval"); // 根据ndk中对应的env方法生成新的值
retval.replace(ptr(newRetval)) // 这是官方api, 需要replace 一个指针进去(ptr方法)
}
});
修改参数、返回值
在分析协议的时候较少情况回去修改参数或者返回值
修改 JNIEnv
这里是一个替换 NewStringUTF
的例子
function hook_replace() {
var addr_NewStringUTF = null;
// 找到 NewStringUTF 地址
//console.log( JSON.stringify(Process.enumerateModules()));
var symbols = Process.findModuleByName("libart.so").enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i].name;
if ((symbol.indexOf("CheckJNI") == -1) && (symbol.indexOf("JNI") >= 0)) {
if (symbol.indexOf("NewStringUTF") >= 0) {
console.log(symbols[i].name);
console.log(symbols[i].address);
addr_NewStringUTF = symbols[i].address;
}
}
}
console.log("addr_NewStringUTF:", addr_NewStringUTF);
// 替换
var NewStringUTF = new NativeFunction(addr_NewStringUTF, 'pointer', ['pointer', 'pointer']); // 该函数有2个返回值
Java.perform(function () {
Interceptor.replace(addr_NewStringUTF,
new NativeCallback(
function (parg0, parg1) { // 该函数有2个参数
console.log("original args:", parg0, Memory.readCString(parg1));
var newParg = Memory.allocUtf8String("stringFromFridaNativeHookReplace")
var NS = NewStringUTF(parg0, newParg);
return NS;
},
"pointer",
["pointer", "pointer"]))
})
}
function main() {
hook_replace();
}
setImmediate(main)
修改普通函数
- 参数修改:
参数 = ptr(1000);
- 返回值修改:
返回值.replace(10000);
Java.perform(function(){
// 参数1:so文件全名
// 参数2:导出函数名
// 返回值:地址指针
var nativePointer = Module.findExportByName("libhello.so", "Java_com_xiaojianbang_app_NativeHelper_add");
send("native指针地址: " + nativePointer);
// hook 这个地址
// hook 函数不需要写参数类型、参数个数
Interceptor.attach(nativePointer, {
// 进入函数前Hook
onEnter: function(args){
send(args[0]);
send(args[1]);
send(args[2].toInt32());
send(args[3].toInt32());
send(args[4].toInt32());
args[4] = ptr(1000); //new NativePointer
send(args[4].toInt32());
},
// 完成函数hook, retval是返回值
onLeave: function(retval){
send(retval.toInt32()); // 转到10 进制显示内容
retval.replace(10000);
send(retval.toInt32());
}
});
});
例:干掉检测线程
这是一个示例代码: hook 掉检测线程,防止被检测到 frida
function hook_libc_replace() {
var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so");
console.log("libnative_lib_addr is :", libnative_lib_addr);
if (libnative_lib_addr) {
var detect_frida_loop = Module.findExportByName("libnative-lib.so", "_Z17detect_frida_loopPv");
console.log("_detect_frida_loop ", detect_frida_loop); // 先找到创建检测frida的线程位置
}
var pthread_create_addr = null;
var symbols = Process.findModuleByName("libc.so").enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i].name;
if (symbol.indexOf("pthread_create") >= 0) {
//console.log(symbols[i].name);
//console.log(symbols[i].address);
pthread_create_addr = symbols[i].address;
}
}
console.log("pthread_create_addr,", pthread_create_addr);
// 把主动调用干掉
var pthread_create = new NativeFunction(pthread_create_addr, "int", ["pointer", "pointer", "pointer", "pointer"]);
Java.perform(function () {
Interceptor.replace(pthread_create_addr, new NativeCallback(function (parg0, parg1, parg2, parg3) {
console.log("parg0,parg1,parg2,parg3:", parg0, parg1, parg2, parg3);
var PC = null;
if (String(parg2).endsWith("db0")) {
console.log("anti-debug sequence");
} else {
console.log("ordinary sequence")
var PC = pthread_create(parg0, parg1, parg2, parg3);
}
return PC;
}, "int", ["pointer", "pointer", "pointer", "pointer"]))
})
}
function main() {
hook_libc_replace();
}
setImmediate(main)
主动调用
调用JNIEnv
通过Java.vm.getEnv()
可以获取到env
对象, 从而进行env
操作非常方便。
// 使用和 navcat 中相同的方法转换成 cstring, 如果是 jstring 需要先转换为 cstring 才能阅读
Interceptor.attach(nativePointer, {
onEnter: function(args){
var env = Java.vm.getEnv();
var jstring = env.getStringUtfChars(args[2], null).readCString();
},
onLeave: function(retval){}
});
调用静态函数
// 获取函数地址过程省略 addr_hard
// 第一个int 是返回值类型, 第二三个int 是函数的参数
var funcAddr = new NativeFunction(addr_hard, "int", ["int", "int"])
send(funcAddr(1,1)); // 调用函数
Hook JNIEnv
想要 java 与 c 代码链接,那么就必须调用 JNIEnv 来进行交互,那么我们就可以通过hook JNIEnv
来实现部分自己想要的结果, 也就是 hook 标准函数。
手动hook - 指定偏移
再知道偏移量的情况下,这里NewStringUTF
的偏移量是668 0x29c
上面这种方法是再知道偏移量的情况下直接使用的并且 指针32位下4个字节, 64位下8个字节不同平台偏移量也是不同的。不是最推荐的方法
var env = Java.vm.getEnv(); // 获取 JNIEnv 对象
send(env);
var handlePointer = Memory.readPointer(env.handle); // 读出 【env结构体的指针】
send("env handle 结构体的指针: " + handlePointer);
// hook env 的 NewStringUTF函数
var NewStringUTFPtr = Memory.readPointer(handlePointer.add(0x29C)); // 偏移量是668 十六进制就是29c
send("NewStringUTFPtr addr: " + NewStringUTFPtr);
Interceptor.attach(NewStringUTFPtr, {
onEnter: function (args) {
//send(Memory.readUtf8String(args[1]));
send(Memory.readCString(args[1]));
var buffer = Memory.readByteArray(args[1],16);
console.log(hexdump(buffer, {
offset: 0,
length: 16,
header: true,
ansi: false
}));
},
onLeave: function(retval){
send("jni返回的:" + retval);
}
});
手动hook - libart(推荐)
动态获取libart的导出函数, 并过滤出想要hook
的jni
函数
var addr_GetStringUTFChars = null;
//console.log( JSON.stringify(Process.enumerateModules()));
var symbols = Process.findModuleByName("libart.so").enumerateSymbols();
for(var i = 0;i<symbols.length;i++){
var symbol = symbols[i].name;
if((symbol.indexOf("CheckJNI")==-1)&&(symbol.indexOf("JNI")>=0)){
if(symbol.indexOf("GetStringUTFChars")>=0){ // 以GetStringUTFChars为例
console.log(symbols[i].name);
console.log(symbols[i].address);
addr_GetStringUTFChars = symbols[i].address;
}
}
}
console.log("addr_GetStringUTFChars:",addr_GetStringUTFChars);
Java.perform(function () {
Interceptor.attach(addr_GetStringUTFChars, {
onEnter: function (args) {
console.log("addr_GetStringUTFChars OnEnter args[0],args[1]", args[0], args[1]);
//console.log(hexdump(args[0].readPointer()));
//console.log(Java.vm.tryGetEnv().getStringUtfChars(args[0]).readCString());
}, onLeave: function (retval) {
console.log("addr_GetStringUTFChars OnLeave", ptr(retval).readCString());
}
})
})
JNIEnv Traes
参考 GitHub 功能等同于 jnitrace 更轻量级, 用于自吐 JNIEnv
函数的调用
通用hook代码
hook_art.js
// hook_art.js
/*
GetFieldID is at 0xe39b87c5 _ZN3art3JNI10GetFieldIDEP7_JNIEnvP7_jclassPKcS6_
GetMethodID is at 0xe39a1a19 _ZN3art3JNI11GetMethodIDEP7_JNIEnvP7_jclassPKcS6_
NewStringUTF is at 0xe39cff25 _ZN3art3JNI12NewStringUTFEP7_JNIEnvPKc
RegisterNatives is at 0xe39e08fd _ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
GetStaticFieldID is at 0xe39c9635 _ZN3art3JNI16GetStaticFieldIDEP7_JNIEnvP7_jclassPKcS6_
GetStaticMethodID is at 0xe39be0ed _ZN3art3JNI17GetStaticMethodIDEP7_JNIEnvP7_jclassPKcS6_
GetStringUTFChars is at 0xe39d06e5 _ZN3art3JNI17GetStringUTFCharsEP7_JNIEnvP8_jstringPh
FindClass is at 0xe399ae5d _ZN3art3JNI9FindClassEP7_JNIEnvPKc
*/
function hook_libart() {
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrGetStringUTFChars = null;
var addrNewStringUTF = null;
var addrFindClass = null;
var addrGetMethodID = null;
var addrGetStaticMethodID = null;
var addrGetFieldID = null;
var addrGetStaticFieldID = null;
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0
) {
if (symbol.name.indexOf("GetStringUTFChars") >= 0) {
addrGetStringUTFChars = symbol.address;
console.log("GetStringUTFChars is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("NewStringUTF") >= 0) {
addrNewStringUTF = symbol.address;
console.log("NewStringUTF is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("FindClass") >= 0) {
addrFindClass = symbol.address;
console.log("FindClass is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("GetMethodID") >= 0) {
addrGetMethodID = symbol.address;
console.log("GetMethodID is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("GetStaticMethodID") >= 0) {
addrGetStaticMethodID = symbol.address;
console.log("GetStaticMethodID is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("GetFieldID") >= 0) {
addrGetFieldID = symbol.address;
console.log("GetFieldID is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("GetStaticFieldID") >= 0) {
addrGetStaticFieldID = symbol.address;
console.log("GetStaticFieldID is at ", symbol.address, symbol.name);
} else if (symbol.name.indexOf("RegisterNatives") >= 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
}
}
}
if (addrGetStringUTFChars != null) {
Interceptor.attach(addrGetStringUTFChars, {
onEnter: function(args) {},
onLeave: function(retval) {
if (retval != null) {
var bytes = Memory.readCString(retval);
console.log("[GetStringUTFChars] result:" + bytes);
}
}
});
}
if (addrNewStringUTF != null) {
Interceptor.attach(addrNewStringUTF, {
onEnter: function(args) {
if (args[1] != null) {
var string = Memory.readCString(args[1]);
console.log("[NewStringUTF] bytes:" + string);
}
},
onLeave: function(retval) {}
});
}
if (addrFindClass != null) {
Interceptor.attach(addrFindClass, {
onEnter: function(args) {
if (args[1] != null) {
var name = Memory.readCString(args[1]);
console.log("[FindClass] name:" + name);
}
},
onLeave: function(retval) {}
});
}
if (addrGetMethodID != null) {
Interceptor.attach(addrGetMethodID, {
onEnter: function(args) {
if (args[2] != null) {
var name = Memory.readCString(args[2]);
if (args[3] != null) {
var sig = Memory.readCString(args[3]);
console.log("[GetMethodID] name:" + name + ", sig:" + sig);
} else {
console.log("[GetMethodID] name:" + name);
}
}
},
onLeave: function(retval) {}
});
}
if (addrGetStaticMethodID != null) {
Interceptor.attach(addrGetStaticMethodID, {
onEnter: function(args) {
if (args[2] != null) {
var name = Memory.readCString(args[2]);
if (args[3] != null) {
var sig = Memory.readCString(args[3]);
console.log("[GetStaticMethodID] name:" + name + ", sig:" + sig);
} else {
console.log("[GetStaticMethodID] name:" + name);
}
}
},
onLeave: function(retval) {}
});
}
if (addrGetFieldID != null) {
Interceptor.attach(addrGetFieldID, {
onEnter: function(args) {
if (args[2] != null) {
var name = Memory.readCString(args[2]);
if (args[3] != null) {
var sig = Memory.readCString(args[3]);
console.log("[GetFieldID] name:" + name + ", sig:" + sig);
} else {
console.log("[GetFieldID] name:" + name);
}
}
},
onLeave: function(retval) {}
});
}
if (addrGetStaticFieldID != null) {
Interceptor.attach(addrGetStaticFieldID, {
onEnter: function(args) {
if (args[2] != null) {
var name = Memory.readCString(args[2]);
if (args[3] != null) {
var sig = Memory.readCString(args[3]);
console.log("[GetStaticFieldID] name:" + name + ", sig:" + sig);
} else {
console.log("[GetStaticFieldID] name:" + name);
}
}
},
onLeave: function(retval) {}
});
}
if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function(args) {
console.log("[RegisterNatives] method_count:", args[3]);
var env = args[0];
var java_class = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
var name = Memory.readCString(name_ptr);
var sig = Memory.readCString(sig_ptr);
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));
}
},
onLeave: function(retval) {}
});
}
}
setImmediate(hook_libart);
使用:
frida -U --no-pause -f package_name -l hook_art.js
Env 调用无处遁形
Hook JNI_Onload
参考 GitHub
动态注册的代码
JNIEXPORT jstring JNICALL stringFromJNI2(JNIEnv *env, jclass clazz) {
std::string hello = "Hello from C++2";
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[] = {
{"stringFromJNI2","()Ljava/lang/String;",(void*)stringFromJNI2},
};
env->RegisterNatives(env->FindClass("com/example/navso/MainActivity"),methods,1);
return JNI_VERSION_1_6;
}
通用hook代码
hook_RegisterNatives.js
// hook_RegisterNatives.js
function hook_RegisterNatives() {
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
//_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
}
}
if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function(args) {
console.log("[RegisterNatives] method_count:", args[3]);
var env = args[0];
var java_class = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
//console.log(class_name);
var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
var name = Memory.readCString(name_ptr);
var sig = Memory.readCString(sig_ptr);
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));
}
}
});
}
}
setImmediate(hook_RegisterNatives);
使用:
frida -U --no-pause -f package_name -l hook_RegisterNatives.js
效果:
注册的参数位置,偏移清晰可见
拓展
当然如果被深度反调试 Frida, 那么久改源码AOSP插桩输出。 参考文章
Hook libc
hook c 中的标准函数: 日路创建线程 pthread_create
var pthread_create_addr = null;
var symbols = Process.findModuleByName("libc.so").enumerateSymbols();
for(var i = 0;i<symbols.length;i++){ // 遍历
var symbol = symbols[i].name;
if(symbol.indexOf("pthread_create")>=0){ // 以pthread_create为例
//console.log(symbols[i].name);
//console.log(symbols[i].address);
pthread_create_addr = symbols[i].address;
}
}
console.log("pthread_create_addr,",pthread_create_addr);
Interceptor.attach(pthread_create_addr,{
onEnter:function(args){
console.log("pthread_create_addr args[0],args[1],args[2],args[3]:",args[0],args[1],args[2],args[3]);
},onLeave:function(retval){
console.log("retval is:",retval)
}
})
Hook dlopen
效果就是 hook java 中的 System.loadLibrary("xxxx");
和直接hook这个函数的区别就是 dlopen
更底层, 在普通 hook
无法达到预期效果的时候,请用这招。
通常也可以使用 dlopen
或者 mmap
函数,在Native 中引用 so
dlopen
(低版本用)android_dlopen_ext
(高版本用)
// 这里是高低版本都hook
function Test(){
var dlopen = Module.findExportByName(null, "dlopen");
console.log(dlopen);
if(dlopen != null){
Interceptor.attach(dlopen,{
onEnter: function(args){
var soName = args[0].readCString();
console.log(soName);
if(soName.indexOf("libxiaojianbang.so") != -1){
this.hook = true;
}
},
onLeave: function(retval){
if(this.hook) { hookTest5() };
}
});
}
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
console.log(android_dlopen_ext);
if(android_dlopen_ext != null){
Interceptor.attach(android_dlopen_ext,{
onEnter: function(args){
var soName = args[0].readCString();
console.log(soName);
if(soName.indexOf("libxiaojianbang.so") != -1){
this.hook = true;
}
},
onLeave: function(retval){
if(this.hook) { hookTest5() };
}
});
}
}
// 功能函数转换
function hexToBytes(str) {
var pos = 0;
var len = str.length;
if (len % 2 != 0) {
return null;
}
len /= 2;
var hexA = new Array();
for (var i = 0; i < len; i++) {
var s = str.substr(pos, 2);
var v = parseInt(s, 16);
hexA.push(v);
pos += 2;
}
return hexA;
}
// 功能函数转换
function stringToBytes(str) {
var ch, st, re = [];
for (var i = 0; i < str.length; i++ ) {
ch = str.charCodeAt(i);
st = [];
do {
st.push( ch & 0xFF );
ch = ch >> 8;
}
while ( ch );
re = re.concat( st.reverse() );
}
return re;
}
Hook init_array
在Jni_onload
之前还有一个init_array
和init
, 是其更低一层的函数。
静态分析
init
名字不可更改 (可以在IDA中直接搜索init
直接找到)init_array
名字可以更改 (IDA中按F7, 找到init_array
并进入)
动态分析
利用 IDA 动态调试获取 init 的函数
InLine Hook 常见实例
例:动态获取So Hook函数
这是 InLine Hook 的方法,通过函数地址hook
注意!! 你用ida的so版本,要和你手机使用的so相同才行, 比如你看的arm平台,但是你手机并没有使用该平台的so,这种情况永远调试错误
import frida, sys
jsCode = """
Java.perform(function(){
// 获取 So 地址
var soAddr = Module.findBaseAddress("libhello.so");
send("So地址:" + soAddr);
// 函数地址 = so地址.add(偏移地址 + 1) // 是否+1 取决于cpu平台型号
var MD5FinalAddr = soAddr.add(0x1768+1) // 0x 代表 16进制
send('MD5Final地址:'+MD5FinalAddr)
// hook 这个地址
// hook 函数不需要写参数类型、参数个数
Interceptor.attach(MD5FinalAddr, {
// 进入函数前Hook
onEnter: function(args){
send(args[0]);
send(args[1]);
},
// 完成函数hook, retval是返回值
onLeave: function(retval){
send(retval); // 16进制 指针
console.log(hexdump(retval)); // 这是直接返回可视信息
}
});
});
"""
def message(message, data):
if message["type"] == 'send':
print(u"[*] {0}".format(message['payload']))
else:
print(message)
process = frida.get_remote_device().attach("com.xxx.xxx")
script = process.create_script(jsCode)
script.on("message", message)
script.load()
sys.stdin.read()
流程:
- 动态获取 So 地址
- hook函数地址 = so地址.add(偏移地址 + 1)
这些就是偏移地址
例:Hook 遍历导入导出表
有时候,不知道导入导出表名,这个时候需要遍历
import frida, sys
jsCode = """
Java.perform(function(){
// 导入
var imports = Module.enumerateImportsSync("libhello.so");
send(imports); // 打印全部
for(var i = 0; i < imports.length; i++) {
// 判断是否是自己需要的
if(imports[i].name == 'strncat'){
send(imports[i].name + ": " + imports[i].address);
break;
}
}
// 导出
var exports = Module.enumerateExportsSync("libhello.so");
for(var i = 0; i < exports.length; i++) {
// 判断是否是自己需要的
if(exports[i].name.indexOf('add') != -1){
send(exports[i].name + ": " + exports[i].address);
break;
}
}
});
"""
def message(message, data):
if message["type"] == 'send':
print(u"[*] {0}".format(message['payload']))
else:
print(message)
process = frida.get_remote_device().attach("com.xxx.xxx")
script = process.create_script(jsCode)
script.on("message", message)
script.load()
sys.stdin.read()
例:Hook 指针函数的返回值(参考形式的返回值)
这是一个hook so 中的MD5 加密结果
import frida, sys
jsCode = """
Java.perform(function(){
var soAddr = Module.findBaseAddress("libhello.so");
send('soAddr: ' + soAddr);
var resultPtr = null; // 准备一个临时变量储存地址指针
var MD5FinalAddr = soAddr.add(0x1768 + 1);
send('MD5FinalAddr: ' + MD5FinalAddr);
Interceptor.attach(MD5FinalAddr, {
onEnter: function(args){
send(args[0]);
send(args[1]);
resultPtr = args[1]; // 用临时变量来储存这个指针地址(等待函数执行完之后,才是需要的值)
},
onLeave: function(retval){
send(retval);
// 通过指针地址(resultPtr),获取内存数据
var buffer = Memory.readByteArray(resultPtr, 16); // 参数1: 地址 ;参数2:读多少位
// 将内存数据,输出可见数据 (必须用console.log才能输出)
// hexdump 参数1: 内存数; 参数2:参数对象
console.log(hexdump(buffer, {
offset: 0, // 起始
length: 16, // 结尾
header: true,
ansi: false // false = utf8
}));
}
});
});
"""
def message(message, data):
if message["type"] == 'send':
print(u"[*] {0}".format(message['payload']))
else:
print(message)
process = frida.get_remote_device().attach("com.xxx.xxx")
script = process.create_script(jsCode)
script.on("message", message)
script.load()
sys.stdin.read()
简易版示例
function hookTest5(){
var soAddr = Module.findBaseAddress("xxx.so");
console.log(soAddr);
var sub_930 = soAddr.add(0x930); //函数地址计算 thumb+1 ARM不加
console.log(sub_930);
var sub_208C = soAddr.add(0x208C); //函数地址计算 thumb+1 ARM不加
console.log(sub_208C);
if(sub_208C != null){
Interceptor.attach(sub_208C,{
onEnter: function(args){
this.args1 = args[1]; // 储存参考形参数
},
onLeave: function(retval){
console.log(hexdump(this.args1)); // 取出
}
});
}
}
上面是一个hook so 中的MD5 加密结果
- 指针函数需要一个临时变量来储存指针地址
- 当函数执行完成后才能开始获取指针对应的内存数据
- 通过指针地址,获取内存数据
readByteArray
- 将内存数据,输出可见数据 (必须用console.log才能输出)
hexdump
方法
hook 结果
这就是hook 到的 md5 结果
例:修改指针指向的内存数据
继续上面一则(我们获取到了md5 的值),接下来我们要修改这个值
- [x]
readByteArray()
根据指针读取内存指定数据 - [x]
writeByteArray()
修改数据
# -*- encoding: utf-8 -*-
# Auth: Zok Email: 362416272@qq.com
# Date: 2020/2/10
import frida, sys
jsCode = """
Java.perform(function(){
var soAddr = Module.findBaseAddress("libhello.so");
send('soAddr: ' + soAddr);
var resultPtr = null; // 准备一个临时变量储存地址指针
var MD5FinalAddr = soAddr.add(0x1768 + 1);
send('MD5FinalAddr: ' + MD5FinalAddr);
Interceptor.attach(MD5FinalAddr, {
onEnter: function(args){
send(args[0]);
send(args[1]);
resultPtr = args[1]; // 用临时变量来储存这个指针地址(等待函数执行完之后,才是需要的值)
},
onLeave: function(retval){
send(retval);
// 通过指针地址(resultPtr),获取内存数据
var buffer = Memory.readByteArray(resultPtr, 16);
// 将内存数据,输出可见数据 (必须用console.log才能输出)
// hexdump 参数1: 内存书; 参数2:参数对象
console.log(hexdump(buffer, {
offset: 0,
length: 16,
header: true,
ansi: false
}));
// 修改内存数据 参数2需要字节说数据
Memory.writeByteArray(resultPtr, stringToBytes('1234567890'));
// 重新读出确认
buffer = Memory.readByteArray(resultPtr, 32);
console.log(hexdump(buffer, {
offset: 0,
length: 32,
header: true,
ansi: false
}));
}
});
function hexToBytes(str) {
var pos = 0;
var len = str.length;
if (len % 2 != 0) {
return null;
}
len /= 2;
var hexA = new Array();
for (var i = 0; i < len; i++) {
var s = str.substr(pos, 2);
var v = parseInt(s, 16);
hexA.push(v);
pos += 2;
}
return hexA;
}
function stringToBytes(str) {
var ch, st, re = [];
for (var i = 0; i < str.length; i++ ) {
ch = str.charCodeAt(i);
st = [];
do {
st.push( ch & 0xFF );
ch = ch >> 8;
}
while ( ch );
re = re.concat( st.reverse() );
}
return re;
}
});
"""
def message(message, data):
if message["type"] == 'send':
print(u"[*] {0}".format(message['payload']))
else:
print(message)
process = frida.get_remote_device().attach("com.xxx.xxx")
script = process.create_script(jsCode)
script.on("message", message)
script.load()
sys.stdin.read()
修改成功
Traes
JNI
Hook 调用 Jni 函数, hook较全 Jni
的标准函数用它足矣
pip install jnitrace
使用方法在 github 上还是有详细的说明的, 或者通过 jnitrace --help
查看
例如:
jnitrace -l libnative-lib.so com.example.myapplication
就可以快速 trace 该项目还有很多的功能,看文档来慢慢使用!
Art
hook artmethod
用于打印 so 中调用 java层的函数, 参考 GitHub
js代码不能单独运行!! 需要下载项目中的 lib 文件夹中的 so
function hook_native() {
var module_libart = Process.findModuleByName("libart.so");
var symbols = module_libart.enumerateSymbols();
var ArtMethod_Invoke = null;
var ArtMethod_PrettyMethod = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
var address = symbol.address;
var name = symbol.name;
var indexArtMethod = name.indexOf("ArtMethod");
var indexInvoke = name.indexOf("Invoke");
var indexThread = name.indexOf("Thread");
if (indexArtMethod >= 0
&& indexInvoke >= 0
&& indexThread >= 0
&& indexArtMethod < indexInvoke
&& indexInvoke < indexThread) {
console.log(name);
ArtMethod_Invoke = address;
}
if (indexArtMethod >= 0 && name.indexOf("PrettyMethod") >= 0 && name.indexOf("Eb") >= 0) {
console.log(name);
ArtMethod_PrettyMethod = address;
}
}
var module_libext = null;
if (Process.arch === "arm64") {
module_libext = Module.load("/data/app/libext64.so");
} else if (Process.arch === "arm") {
module_libext = Module.load("/data/app/libext.so");
}
if (module_libext != null) {
var addr_PrettyMethod = module_libext.findExportByName("PrettyMethod");
var PrettyMethod = new NativeFunction(addr_PrettyMethod, "void", ["pointer", "pointer", "pointer", "int"]);
if (ArtMethod_Invoke) {
var foo_ArtMethod_PrettyMethod = new NativeFunction(ArtMethod_PrettyMethod, "pointer", ["pointer", "int"]);
console.log(foo_ArtMethod_PrettyMethod);
Interceptor.attach(ArtMethod_Invoke, {
onEnter: function (args) {
try {
var result = Memory.alloc(0x100);
PrettyMethod(ArtMethod_PrettyMethod, args[0], result, 0x100);
console.log(result.readCString());
} catch (error) {
console.log(error);
}
}, onLeave: function (retval) {
}
});
}
}
}
function main() {
hook_native();
}
setImmediate(main);
用法
frida -U --no-pause -f package_name -l hook_artmethod.js
or
frida -U --no-pause -f package_name -l hook_artmethod.js > hook_artmethod.log
Libc
frida-trace
官方工具,(可以hookso和java函数) 当然他的主要作用是hook native
的标准函数, 因为其他的trace已经很好用了。
不清楚就 frida-trace --help
// hook 所有函数
frida-trace -U -I libnative-lib.so com.xxx.xxx
// hook 指定函数 strcmp 然后 -o 保存
frida-trace -U -i "strcmp" -f com.xxx.xxx -o test.js
Linker
在Jni_onload
之前还有init_array
和init
。
没有支持arm64,可以在安装app的时候adb install --abi armeabi-v7a强制让app运行在32位模式
- 这个脚本整体来说就是hook callfunction,然后打印出init_array里面的函数地址和参数等。
- 从源码看,关键就是call_array这里调用的call_function,第一个参数代表这是注册的init_array里面的function,第二个参数则是init_array里存储的函数的地址。
function LogPrint(log) {
var theDate = new Date();
var hour = theDate.getHours();
var minute = theDate.getMinutes();
var second = theDate.getSeconds();
var mSecond = theDate.getMilliseconds()
hour < 10 ? hour = "0" + hour : hour;
minute < 10 ? minute = "0" + minute : minute;
second < 10 ? second = "0" + second : second;
mSecond < 10 ? mSecond = "00" + mSecond : mSecond < 100 ? mSecond = "0" + mSecond : mSecond;
var time = hour + ":" + minute + ":" + second + ":" + mSecond;
var threadid = Process.getCurrentThreadId();
console.log("[" + time + "]" + "->threadid:" + threadid + "--" + log);
}
function hooklinker() {
var linkername = "linker";
var call_function_addr = null;
var arch = Process.arch;
LogPrint("Process run in:" + arch);
if (arch.endsWith("arm")) {
linkername = "linker";
} else {
linkername = "linker64";
LogPrint("arm64 is not supported yet!");
}
var symbols = Module.enumerateSymbolsSync(linkername);
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
//LogPrint(linkername + "->" + symbol.name + "---" + symbol.address);
if (symbol.name.indexOf("__dl__ZL13call_functionPKcPFviPPcS2_ES0_") != -1) {
call_function_addr = symbol.address;
LogPrint("linker->" + symbol.name + "---" + symbol.address)
}
}
if (call_function_addr != null) {
var func_call_function = new NativeFunction(call_function_addr, 'void', ['pointer', 'pointer', 'pointer']);
Interceptor.replace(new NativeFunction(call_function_addr,
'void', ['pointer', 'pointer', 'pointer']), new NativeCallback(function (arg0, arg1, arg2) {
var functiontype = null;
var functionaddr = null;
var sopath = null;
if (arg0 != null) {
functiontype = Memory.readCString(arg0);
}
if (arg1 != null) {
functionaddr = arg1;
}
if (arg2 != null) {
sopath = Memory.readCString(arg2);
}
var modulebaseaddr = Module.findBaseAddress(sopath);
LogPrint("after load:" + sopath + "--start call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);
if (sopath.indexOf('libnative-lib.so') >= 0 && functiontype == "DT_INIT") {
LogPrint("after load:" + sopath + "--ignore call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);
} else {
func_call_function(arg0, arg1, arg2);
LogPrint("after load:" + sopath + "--end call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);
}
}, 'void', ['pointer', 'pointer', 'pointer']));
}
}
setImmediate(hooklinker)
5 comments
谢谢
谢谢 , 小技巧 ,小方法很实用; 经常来参考
很全面,点赞
太牛逼了
赞赞赞,突然就可以评论了