Frida Native 层 Hook 技巧:JNI 函数调用、字符串解析、so 加载
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/
RegisterNatives
RegisterNatives 是 JNI(Java Native Interface)的一部分,用于在 Java 类和本地 C/C++ 代码之间注册本地方法。其原型如下:
jint RegisterNatives(JNIEnv* env, jclass clazz, const JNINativeMethod* methods, jint method_count);
参数说明:
env:JNIEnv 指针。
clazz:Java 类的 jclass 句柄。
methods:指向 JNINativeMethod 结构体数组的指针。
method_count:要注册的方法数量。
其中,methods 结构体的定义如下:
typedef struct {
const char* name; // 方法名称(指向字符串)
const char* signature; // 方法签名(指向字符串)
void* fnPtr; // 方法的本地实现(指向本地函数)
} JNINativeMethod;
可以看到,每个 JNINativeMethod 结构体由 三个指针 组成:
name(方法名指针)
signature(方法签名指针)
fnPtr(本地方法指针)
一般会有两个 RegisterNatives 函数,CheckJNI 版本只有在调试选项打开时才会调用,我们一般用 JNI 的那个就行。
[+] Found RegisterNatives symbol: _ZN3art12_GLOBAL__N_18CheckJNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi at 0x780d7757a8
[+] Found RegisterNatives symbol: _ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi at 0x780d7eed10
通过 hook RegisterNatives 实现监控 app 中动态注册的 JNI 函数。代码如下:
RegisterNatives.js
// 查找 libart.so 中的 RegisterNatives 地址
function findRegisterNativesAddr() {
let symbols = Module.enumerateSymbolsSync("libart.so");
for (let symbol of symbols) {
if (symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
console.log("[+] Found RegisterNatives symbol: " + symbol.name + " at " + symbol.address);
return symbol.address;
}
}
console.log("[!] No non-CheckJNI RegisterNatives symbol found!");
return null;
}
// Hook RegisterNatives 函数,打印方法相关信息
function hookRegisterNatives(addrRegisterNatives) {
if (!addrRegisterNatives) {
console.log("[!] Cannot hook RegisterNatives because the address is null.");
return;
}
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
let logs = [];
try {
const env = Java.vm.tryGetEnv();
const javaClass = args[1];
const methodsPtr = ptr(args[2]);
const methodCount = args[3].toInt32();
const className = env.getClassName(javaClass);
logs.push("\n==================== RegisterNatives ====================");
logs.push("[*] Class: " + className);
logs.push("[*] Method Count: " + methodCount);
// 遍历注册的每个 JNI 方法
for (let i = 0; i < methodCount; i++) {
// 读取每个方法的名称、签名和函数指针
const methodPtr = methodsPtr.add(i * Process.pointerSize * 3);
const namePtr = Memory.readPointer(methodPtr);
const sigPtr = Memory.readPointer(methodPtr.add(Process.pointerSize));
const fnPtr = Memory.readPointer(methodPtr.add(Process.pointerSize * 2));
const name = Memory.readCString(namePtr);
const sig = Memory.readCString(sigPtr);
const symbol = DebugSymbol.fromAddress(fnPtr);
const module = Process.findModuleByAddress(fnPtr);
// 打印每个 JNI 方法的详细信息
logs.push(` [${i}] Method: ${name}`);
logs.push(` Signature: ${sig}`);
logs.push(` Function Symbol: ${symbol.name} (${fnPtr})`);
// JNI 方法所在模块信息
if (module) {
const offset = fnPtr.sub(module.base);
logs.push(` Module: ${module.name}`);
logs.push(` Offset in Module: ${offset}`);
} else {
logs.push(` Module: Unknown`);
}
}
// 打印调用堆栈
const backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress)
.join('\n');
logs.push("\n[*] Backtrace:\n" + backtrace);
logs.push("=========================================================");
} catch (e) {
logs.push("[!] Exception in hookRegisterNatives: " + e);
}
// 统一打印
console.log(logs.join('\n'));
}
});
}
// 执行查找并 hook RegisterNatives
setImmediate(function () {
const addrRegisterNatives = findRegisterNativesAddr();
hookRegisterNatives(addrRegisterNatives);
});
// frida -H 127.0.0.1:1234 -l register_natives.js -f com.shizhuang.duapp -o register_natives.txt
// frida -H 127.0.0.1:1234 -l register_natives.js -f com.shizhuang.duapp
// frida -H 127.0.0.1:1234 -F -l register_natives.js
启动指定 app 并执行脚本
frida -H 127.0.0.1:1234 -l register_natives.js -f packageName
输出如下:
[+] Found RegisterNatives symbol: _ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi at 0x7256fe4560
==================== RegisterNatives ====================
[*] Class: lte.NCall
[*] Method Count: 1
[0] Method: dI
Signature: (I)I
Function Symbol: 0xb93c (0x71dd90f93c)
Module: libGameVMP.so
Offset in Module: 0xb93c
[*] Backtrace:
0x71dd90a998 libGameVMP.so!0x6998
0x71dd912650 libGameVMP.so!0xe650
0x71dd9105d8 libGameVMP.so!0xc5d8
0x71dd9068ac libGameVMP.so!0x28ac
0x71dd9081b4 libGameVMP.so!JNI_OnLoad+0x904
0x7256f85944 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP7_jclassPS9_+0xc9c
0x724d2ae184 libopenjdkjvm.so!JVM_NativeLoad+0x19c
0x71454af4 boot.oat!0xb6af4
=========================================================
==================== RegisterNatives ====================
[*] Class: lte.NCall
[*] Method Count: 1
[0] Method: dS
Signature: (Ljava/lang/String;)Ljava/lang/String;
Function Symbol: 0xbaec (0x71dd90faec)
Module: libGameVMP.so
Offset in Module: 0xbaec
[*] Backtrace:
0x71dd90a998 libGameVMP.so!0x6998
0x71dd912650 libGameVMP.so!0xe650
0x71dd9105d8 libGameVMP.so!0xc5d8
0x71dd9068ac libGameVMP.so!0x28ac
0x71dd9081b4 libGameVMP.so!JNI_OnLoad+0x904
0x7256f85944 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP7_jclassPS9_+0xc9c
0x724d2ae184 libopenjdkjvm.so!JVM_NativeLoad+0x19c
0x71454af4 boot.oat!0xb6af4
=========================================================
==================== RegisterNatives ====================
[*] Class: lte.NCall
[*] Method Count: 1
[0] Method: dL
Signature: (J)J
Function Symbol: 0xbad0 (0x71dd90fad0)
Module: libGameVMP.so
Offset in Module: 0xbad0
[*] Backtrace:
0x71dd90a998 libGameVMP.so!0x6998
0x71dd912650 libGameVMP.so!0xe650
0x71dd9105d8 libGameVMP.so!0xc5d8
0x71dd9068ac libGameVMP.so!0x28ac
0x71dd9081b4 libGameVMP.so!JNI_OnLoad+0x904
0x7256f85944 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP7_jclassPS9_+0xc9c
0x724d2ae184 libopenjdkjvm.so!JVM_NativeLoad+0x19c
0x71454af4 boot.oat!0xb6af4
=========================================================
==================== RegisterNatives ====================
[*] Class: lte.NCall
[*] Method Count: 1
[0] Method: IV
Signature: ([Ljava/lang/Object;)V
Function Symbol: 0xdfa8 (0x71dd911fa8)
Module: libGameVMP.so
Offset in Module: 0xdfa8
[*] Backtrace:
0x71dd90a998 libGameVMP.so!0x6998
0x71dd912650 libGameVMP.so!0xe650
0x71dd9105d8 libGameVMP.so!0xc5d8
0x71dd9068ac libGameVMP.so!0x28ac
0x71dd9081b4 libGameVMP.so!JNI_OnLoad+0x904
0x7256f85944 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP7_jclassPS9_+0xc9c
0x724d2ae184 libopenjdkjvm.so!JVM_NativeLoad+0x19c
0x71454af4 boot.oat!0xb6af4
=========================================================
==================== RegisterNatives ====================
[*] Class: lte.NCall
[*] Method Count: 1
[0] Method: IZ
Signature: ([Ljava/lang/Object;)Z
Function Symbol: 0xdfa8 (0x71dd911fa8)
Module: libGameVMP.so
Offset in Module: 0xdfa8
[*] Backtrace:
0x71dd90a998 libGameVMP.so!0x6998
0x71dd912650 libGameVMP.so!0xe650
0x71dd9105d8 libGameVMP.so!0xc5d8
0x71dd9068ac libGameVMP.so!0x28ac
0x71dd9081b4 libGameVMP.so!JNI_OnLoad+0x904
0x7256f85944 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP7_jclassPS9_+0xc9c
0x724d2ae184 libopenjdkjvm.so!JVM_NativeLoad+0x19c
0x71454af4 boot.oat!0xb6af4
=========================================================
==================== RegisterNatives ====================
[*] Class: lte.NCall
[*] Method Count: 1
[0] Method: IB
Signature: ([Ljava/lang/Object;)B
Function Symbol: 0xdfa8 (0x71dd911fa8)
Module: libGameVMP.so
Offset in Module: 0xdfa8
[*] Backtrace:
0x71dd90a998 libGameVMP.so!0x6998
0x71dd912650 libGameVMP.so!0xe650
0x71dd9105d8 libGameVMP.so!0xc5d8
0x71dd9068ac libGameVMP.so!0x28ac
0x71dd9081b4 libGameVMP.so!JNI_OnLoad+0x904
0x7256f85944 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP7_jclassPS9_+0xc9c
0x724d2ae184 libopenjdkjvm.so!JVM_NativeLoad+0x19c
0x71454af4 boot.oat!0xb6af4
=========================================================
==================== RegisterNatives ====================
[*] Class: lte.NCall
[*] Method Count: 1
[0] Method: IC
Signature: ([Ljava/lang/Object;)C
Function Symbol: 0xdfa8 (0x71dd911fa8)
Module: libGameVMP.so
Offset in Module: 0xdfa8
[*] Backtrace:
0x71dd90a998 libGameVMP.so!0x6998
0x71dd912650 libGameVMP.so!0xe650
0x71dd9105d8 libGameVMP.so!0xc5d8
0x71dd9068ac libGameVMP.so!0x28ac
0x71dd9081b4 libGameVMP.so!JNI_OnLoad+0x904
0x7256f85944 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP7_jclassPS9_+0xc9c
0x724d2ae184 libopenjdkjvm.so!JVM_NativeLoad+0x19c
0x71454af4 boot.oat!0xb6af4
=========================================================
==================== RegisterNatives ====================
[*] Class: lte.NCall
[*] Method Count: 1
[0] Method: IS
Signature: ([Ljava/lang/Object;)S
Function Symbol: 0xdfa8 (0x71dd911fa8)
Module: libGameVMP.so
Offset in Module: 0xdfa8
[*] Backtrace:
0x71dd90a998 libGameVMP.so!0x6998
0x71dd912650 libGameVMP.so!0xe650
0x71dd9105d8 libGameVMP.so!0xc5d8
0x71dd9068ac libGameVMP.so!0x28ac
0x71dd9081b4 libGameVMP.so!JNI_OnLoad+0x904
0x7256f85944 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP7_jclassPS9_+0xc9c
0x724d2ae184 libopenjdkjvm.so!JVM_NativeLoad+0x19c
0x71454af4 boot.oat!0xb6af4
=========================================================
==================== RegisterNatives ====================
[*] Class: lte.NCall
[*] Method Count: 1
[0] Method: II
Signature: ([Ljava/lang/Object;)I
Function Symbol: 0xdfa8 (0x71dd911fa8)
Module: libGameVMP.so
Offset in Module: 0xdfa8
[*] Backtrace:
0x71dd90a998 libGameVMP.so!0x6998
0x71dd912650 libGameVMP.so!0xe650
0x71dd9105d8 libGameVMP.so!0xc5d8
0x71dd9068ac libGameVMP.so!0x28ac
0x71dd9081b4 libGameVMP.so!JNI_OnLoad+0x904
0x7256f85944 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP7_jclassPS9_+0xc9c
0x724d2ae184 libopenjdkjvm.so!JVM_NativeLoad+0x19c
0x71454af4 boot.oat!0xb6af4
=========================================================
==================== RegisterNatives ====================
[*] Class: lte.NCall
[*] Method Count: 1
[0] Method: IF
Signature: ([Ljava/lang/Object;)F
Function Symbol: 0xdfe8 (0x71dd911fe8)
Module: libGameVMP.so
Offset in Module: 0xdfe8
[*] Backtrace:
0x71dd90a998 libGameVMP.so!0x6998
0x71dd912650 libGameVMP.so!0xe650
0x71dd9105d8 libGameVMP.so!0xc5d8
0x71dd9068ac libGameVMP.so!0x28ac
0x71dd9081b4 libGameVMP.so!JNI_OnLoad+0x904
0x7256f85944 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP7_jclassPS9_+0xc9c
0x724d2ae184 libopenjdkjvm.so!JVM_NativeLoad+0x19c
0x71454af4 boot.oat!0xb6af4
=========================================================
==================== RegisterNatives ====================
[*] Class: lte.NCall
[*] Method Count: 1
[0] Method: IJ
Signature: ([Ljava/lang/Object;)J
Function Symbol: 0xdfa8 (0x71dd911fa8)
Module: libGameVMP.so
Offset in Module: 0xdfa8
[*] Backtrace:
0x71dd90a998 libGameVMP.so!0x6998
0x71dd912650 libGameVMP.so!0xe650
0x71dd9105d8 libGameVMP.so!0xc5d8
0x71dd9068ac libGameVMP.so!0x28ac
0x71dd9081b4 libGameVMP.so!JNI_OnLoad+0x904
0x7256f85944 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP7_jclassPS9_+0xc9c
0x724d2ae184 libopenjdkjvm.so!JVM_NativeLoad+0x19c
0x71454af4 boot.oat!0xb6af4
=========================================================
==================== RegisterNatives ====================
[*] Class: lte.NCall
[*] Method Count: 1
[0] Method: ID
Signature: ([Ljava/lang/Object;)D
Function Symbol: 0xe028 (0x71dd912028)
Module: libGameVMP.so
Offset in Module: 0xe028
[*] Backtrace:
0x71dd90a998 libGameVMP.so!0x6998
0x71dd912650 libGameVMP.so!0xe650
0x71dd9105d8 libGameVMP.so!0xc5d8
0x71dd9068ac libGameVMP.so!0x28ac
0x71dd9081b4 libGameVMP.so!JNI_OnLoad+0x904
0x7256f85944 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP7_jclassPS9_+0xc9c
0x724d2ae184 libopenjdkjvm.so!JVM_NativeLoad+0x19c
0x71454af4 boot.oat!0xb6af4
=========================================================
==================== RegisterNatives ====================
[*] Class: lte.NCall
[*] Method Count: 1
[0] Method: IL
Signature: ([Ljava/lang/Object;)Ljava/lang/Object;
Function Symbol: 0xdfa8 (0x71dd911fa8)
Module: libGameVMP.so
Offset in Module: 0xdfa8
[*] Backtrace:
0x71dd90a998 libGameVMP.so!0x6998
0x71dd912650 libGameVMP.so!0xe650
0x71dd9105d8 libGameVMP.so!0xc5d8
0x71dd9068ac libGameVMP.so!0x28ac
0x71dd9081b4 libGameVMP.so!JNI_OnLoad+0x904
0x7256f85944 libart.so!_ZN3art9JavaVMExt17LoadNativeLibraryEP7_JNIEnvRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEP8_jobjectP7_jclassPS9_+0xc9c
0x724d2ae184 libopenjdkjvm.so!JVM_NativeLoad+0x19c
0x71454af4 boot.oat!0xb6af4
=========================================================
Native String 分析
hook libart.so 中字符串相关的 api ,用于分析 native 层字符串相关操作。
libart_string.js
function hookGetStringUTFChars() {
const symbols = Module.enumerateSymbolsSync("libart.so");
for (let sym of symbols) {
if (sym.name.indexOf("CheckJNI") < 0 &&
sym.name.indexOf("GetStringUTFChars") !== -1) {
console.log("[*] Found GetStringUTFChars at: " + sym.address + " (" + sym.name + ")");
Interceptor.attach(sym.address, {
onEnter: function (args) {
this.jstr = args[1];
this.isCopy = args[2];
console.log("GetStringUTFChars called with:");
console.log(" jstr: " + this.jstr);
console.log(" isCopy: " + this.isCopy);
},
onLeave: function (retval) {
if (retval.isNull()) {
console.log(" GetStringUTFChars returned NULL");
} else {
console.log(" C String: " + Memory.readUtf8String(retval));
}
}
});
break;
}
}
}
function hookNewStringUTF() {
const symbols = Module.enumerateSymbolsSync("libart.so");
for (let sym of symbols) {
if (sym.name.indexOf("CheckJNI") < 0 &&
sym.name.indexOf("NewStringUTF") !== -1) {
console.log("[*] Found NewStringUTF at: " + sym.address + " (" + sym.name + ")");
Interceptor.attach(sym.address, {
onEnter: function (args) {
this.env = args[0];
this.cstr = args[1];
console.log("NewStringUTF called with:");
console.log(" C String: " + Memory.readUtf8String(this.cstr));
},
onLeave: function (retval) {
console.log(" NewStringUTF returned Java String: " + retval);
}
});
break;
}
}
}
setImmediate(function () {
hookGetStringUTFChars();
hookNewStringUTF();
});
执行脚本:
frida -H 127.0.0.1:1234 -F -l libart_string.js -o libart_string.txt
效果如下:
JNIEnv
常用的 JNI 函数在 frida 的 env.js 中都已经定义好了
https://github.com/frida/frida-java-bridge/blob/main/lib/env.js
通过下面代码获取 JNIEnv 引用,就可以调用相关的 JNI 函数
let env = Java.vm.tryGetEnv()
文档:https://frida.re/docs/javascript-api/
读取 String 对象内容
通过 env.js 中定义的 stringFromJni 函数可以直接获取到字符串对象的值
示例代码:
let str = env.stringFromJni(stringObj)
console.log(str);
读取 std::string 内容
function readStdString (str) {
const isTiny = (str.readU8() & 1) === 0;
if (isTiny) {
return str.add(1).readUtf8String();
}
return str.add(2 * Process.pointerSize).readPointer().readUtf8String();
}
情况 | 说明 |
---|---|
Tiny String | 内容嵌在 std::string 对象中,直接读取内部 buffer。 |
Long String | 内容在堆中,std::string 中存指针,要先读指针再读字符串。 |
如果是 Tiny String 则 str.add(1) 跳过第一个控制字节。从之后的位置读取实际字符串。
如果是长字符串(Heap 分配),在 std::string 对象中,第 2 * pointerSize 的位置是指向实际字符串的指针(这个偏移依赖于具体实现,常见于 libc++)。readPointer() 拿到字符串地址后,再 readUtf8String() 读取字符串内容。
dlopen
dlopen 函数 在 linker 模块,是 Android 系统上的动态库加载函数,用于在运行时加载共享库(.so 文件)。
你可以在 bionic/linker/dlfcn.cpp 中找到 dlopen 的实现:
void* dlopen(const char* filename, int flags) {
return do_dlopen(filename, flags, nullptr);
}
http://aospxref.com/android-11.0.0_r21/xref/bionic/libc/include/dlfcn.h
android_dlopen_ext 是 Android 特有的 dlopen 扩展版本,允许开发者在加载共享库时使用额外的选项,比如 指定库的加载路径 或 共享库的保护标志。
android_dlopen_ext 函数原型
void* android_dlopen_ext(const char* filename, int flags, const android_dlextinfo* extinfo);
参数
filename:共享库路径(如 “libnative-lib.so”)。
flags:同 dlopen(如 RTLD_LAZY)。
extinfo:额外的加载选项,传 NULL 表示默认行为。
通过 frida hook android app 的 so 加载过程,代码如下:
dlopen.js
function hook_dlopen(package_name) {
// Hook dlopen 函数
const dlopenAddr = Module.findExportByName(null, "dlopen");
const dlopen = new NativeFunction(dlopenAddr, 'pointer', ['pointer', 'int']);
Interceptor.attach(dlopen, {
onEnter(args) {
// 获取传递给 dlopen 的参数(SO 文件路径)
const soPath = Memory.readUtf8String(args[0]);
// 如果是目标 app SO 文件,则打印路径
if (package_name) {
if (soPath.includes(package_name)) {
// 打印信息
console.log("dlopen() - Loaded SO file:", soPath);
}
}else{
console.log("dlopen() - Loaded SO file:", soPath);
}
}, onLeave(retval) {
}
});
// Hook android_dlopen_ext 函数
const android_dlopen_extAddr = Module.findExportByName(null, "android_dlopen_ext");
const android_dlopen_ext = new NativeFunction(android_dlopen_extAddr, 'pointer', ['pointer', 'int', 'pointer']);
Interceptor.attach(android_dlopen_ext, {
onEnter(args) {
// 获取传递给 android_dlopen_ext 的参数(SO 文件路径)
const soPath = Memory.readUtf8String(args[0]);
// 如果是目标 app SO 文件,则打印路径
if (package_name) {
if (soPath.includes(package_name)) {
// 打印信息
console.log("android_dlopen_ext() - Loaded SO file:", soPath);
}
}else{
console.log("android_dlopen_ext() - Loaded SO file:", soPath);
}
}, onLeave(retval) {
}
});
}
setImmediate(function () {
hook_dlopen("com.shizhuang.duapp")
});
执行脚本
frida -H 127.0.0.1:1234 -l dlopen.js -f packageName
输出如下:
____
/ _ | Frida 14.2.18 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://www.frida.re/docs/home/
Spawned `com.shizhuang.duapp`. Use %resume to let the main thread start executing!
[Remote::com.shizhuang.duapp]-> %resume
[Remote::com.shizhuang.duapp]-> android_dlopen_ext() - Loaded SO file: /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/oat/arm64/base.odex
android_dlopen_ext() - Loaded SO file: /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/lib/arm64/libGameVMP.so
android_dlopen_ext() - Loaded SO file: /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/lib/arm64/libmmkv.so
android_dlopen_ext() - Loaded SO file: /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/lib/arm64/libxcrash.so
android_dlopen_ext() - Loaded SO file: /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/lib/arm64/libdulog.so
android_dlopen_ext() - Loaded SO file: /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/lib/arm64/libduhook.so
android_dlopen_ext() - Loaded SO file: /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/lib/arm64/libheif.so
android_dlopen_ext() - Loaded SO file: /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/lib/arm64/libdewuffmpeg.so
android_dlopen_ext() - Loaded SO file: /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/lib/arm64/libduplayer.so
android_dlopen_ext() - Loaded SO file: /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/lib/arm64/libstatic-webp.so
android_dlopen_ext() - Loaded SO file: /data/user/0/com.shizhuang.duapp/files/soloader_x64/libxyvodsdk.so
android_dlopen_ext() - Loaded SO file: /data/user/0/com.shizhuang.duapp/files/soloader_x64/libijmdetect_drisk.so
android_dlopen_ext() - Loaded SO file: /data/user/0/com.shizhuang.duapp/files/soloader_x64/libdu_security.so
文件读写
Frida 文件读写相关 api
https://frida.re/docs/javascript-api/#file
写一个字符串到文件:
function writeStringToFile() {
try {
var appContext = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext();
var privateDir = appContext.getExternalFilesDir(null).getAbsolutePath();
var path = privateDir + "/cyrus_studio.dat";
var file = new File(path, "w");
file.write("CYRUS STUDIO");
file.flush();
file.close();
console.log("[+] Successfully wrote to " + path);
} catch (e) {
console.log("[!] Error: " + e.message);
}
}
setImmediate(function () {
Java.perform(writeStringToFile);
});
执行脚本:
frida -H 127.0.0.1:1234 -l write_string_to_file.js -F
输出如下:
[+] Successfully wrote to /storage/emulated/0/Android/data/com.shizhuang.duapp/files/cyrus_studio.dat
调用 C 函数
在 Frida 中,NativeFunction 用于调用 native 函数。它允许我们在 JavaScript 中调用 C 语言函数,就像普通 JavaScript 函数一样。
基本语法
var func = new NativeFunction(address, returnType, argTypes);
address:函数地址,可以用 Module.findExportByName 或 Module.findExportByName(null, “func_name”) 获取。
returnType:函数的返回类型,如 “void”、“int”、“pointer” 等。
argTypes:一个数组,指定函数的参数类型,如 [“pointer”, “int”]。
在 libc.so 文件操作相关函数原型如下:
// 打开文件
FILE *fopen(const char *filename, const char *mode);
/*
参数:
- filename: 需要打开的文件路径
- mode: 文件打开模式,如 "r"(只读),"w"(写入),"rb"(二进制只读)等
返回值:
- 成功返回 FILE* 指针,失败返回 NULL
*/
// 移动文件指针
int fseek(FILE *stream, long offset, int whence);
/*
参数:
- stream: 已打开的文件指针
- offset: 偏移量,以字节为单位
- whence: 参考位置
- SEEK_SET (0): 文件开头
- SEEK_CUR (1): 当前指针位置
- SEEK_END (2): 文件末尾
返回值:
- 成功返回 0,失败返回非零值
*/
// 获取当前文件指针位置
long ftell(FILE *stream);
/*
参数:
- stream: 已打开的文件指针
返回值:
- 返回当前文件指针的偏移量(从文件开头算起)
- 失败返回 -1L
*/
// 读取文件内容
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
/*
参数:
- ptr: 读取数据的存储缓冲区
- size: 每个数据块的大小(单位:字节)
- count: 读取数据块的个数
- stream: 已打开的文件指针
返回值:
- 返回成功读取的数据块数目
- 失败或到文件末尾返回小于 count 的值
*/
// 关闭文件
int fclose(FILE *stream);
/*
参数:
- stream: 需要关闭的文件指针
返回值:
- 成功返回 0,失败返回 EOF(通常为 -1)
*/
在 Android 系统中,这些函数的原型通常可以在 Bionic(Android 的 C 库)中找到。
路径:/bionic/libc/include/stdio.h
通过 NativeFunction 封装 系统中 libc.so 文件相关的函数,并调用读取文件内容
function readFileToString() {
// 定义 C 语言标准库函数的 Frida 封装
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", "long", "int", "pointer"]);
var fclose = new NativeFunction(Module.findExportByName("libc.so", "fclose"), "int", ["pointer"]);
try {
// 获取 Android 应用的上下文
var appContext = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext();
var privateDir = appContext.getExternalFilesDir(null).getAbsolutePath();
var path = privateDir + "/cyrus_studio.dat"; // 目标文件路径
// 分配内存存储文件路径和打开模式
var mode = Memory.allocUtf8String("rb"); // 以二进制只读模式打开文件
var filePath = Memory.allocUtf8String(path);
// 调用 fopen 打开文件
var fp = fopen(filePath, mode);
if (fp.isNull()) {
console.log(`[!] Failed to open file ${path} for reading`);
} else {
// 移动文件指针到文件末尾,计算文件大小
fseek(fp, 0, 2); // SEEK_END
var size = ftell(fp);
fseek(fp, 0, 0); // SEEK_SET(回到文件开头)
// 分配缓冲区存储文件内容
var buffer = Memory.alloc(size + 1);
fread(buffer, size, 1, fp); // 读取文件内容到 buffer
fclose(fp); // 关闭文件
// 读取缓冲区内容并转换为字符串
var content = Memory.readUtf8String(buffer);
console.log("[+] File content: " + content);
}
} catch (e) {
console.log("[!] Error: " + e.message);
}
}
setImmediate(function () {
Java.perform(readFileToString);
});
执行脚本
frida -H 127.0.0.1:1234 -l read_file_to_string.js -F
输出如下:
[+] File content: CYRUS STUDIO