frida_hook_libart(简单解释)
一:直接取代码
//frida -U -f com.xingin.xhs -l hook_art.js -o xhsart.log
//frida -U -f com.tencent.mobileqq -l hook_art.js -o qqart.log
const STD_STRING_SIZE = 3 * Process.pointerSize;
class StdString {
constructor() {
this.handle = Memory.alloc(STD_STRING_SIZE);
}
dispose() {
const [data, isTiny] = this._getData();
if (!isTiny) {
Java.api.$delete(data);
}
}
disposeToString() {
const result = this.toString();
this.dispose();
return result;
}
toString() {
const [data] = this._getData();
return data.readUtf8String();
}
_getData() {
const str = this.handle;
const isTiny = (str.readU8() & 1) === 0;
const data = isTiny ? str.add(1) : str.add(2 * Process.pointerSize).readPointer();
return [data, isTiny];
}
}
function prettyMethod(method_id, withSignature) {
const result = new StdString();
Java.api['art::ArtMethod::PrettyMethod'](result, method_id, withSignature ? 1 : 0);
return result.disposeToString();
}
/*
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;
var so_name = "lib"; //TODO 这里写需要过滤的so
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 &&
symbol.name.indexOf("_ZN3art3JNIILb0") >= 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);
} else if (symbol.name.indexOf("CallStatic") >= 0) {
console.log("CallStatic is at ", symbol.address, symbol.name);
Interceptor.attach(symbol.address, {
onEnter: function (args) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var java_class = args[1];
var mid = args[2];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) {
var method_name = prettyMethod(mid, 1);
console.log("<>CallStatic:", DebugSymbol.fromAddress(this.returnAddress), class_name, method_name);
}
}
},
onLeave: function (retval) { }
});
} else if (symbol.name.indexOf("CallNonvirtual") >= 0) {
console.log("CallNonvirtual is at ", symbol.address, symbol.name);
Interceptor.attach(symbol.address, {
onEnter: function (args) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var jobject = args[1];
var jclass = args[2];
var jmethodID = args[3];
var obj_class_name = Java.vm.tryGetEnv().getObjectClassName(jobject);
var class_name = Java.vm.tryGetEnv().getClassName(jclass);
if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) {
var method_name = prettyMethod(jmethodID, 1);
console.log("<>CallNonvirtual:", DebugSymbol.fromAddress(this.returnAddress), class_name, obj_class_name, method_name);
}
}
},
onLeave: function (retval) { }
});
} else if (symbol.name.indexOf("Call") >= 0 && symbol.name.indexOf("Method") >= 0) {
console.log("Call<>Method is at ", symbol.address, symbol.name);
Interceptor.attach(symbol.address, {
onEnter: function (args) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var java_class = args[1];
var mid = args[2];
var class_name = Java.vm.tryGetEnv().getObjectClassName(java_class);
if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) {
var method_name = prettyMethod(mid, 1);
console.log("<>Call<>Method:", DebugSymbol.fromAddress(this.returnAddress), class_name, method_name);
}
}
},
onLeave: function (retval) { }
});
}
}
}
if (addrGetStringUTFChars != null) {
Interceptor.attach(addrGetStringUTFChars, {
onEnter: function (args) {
},
onLeave: function (retval) {
if (retval != null) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var bytes = Memory.readCString(retval);
console.log("[GetStringUTFChars] result:" + bytes, DebugSymbol.fromAddress(this.returnAddress));
}
}
}
});
}
if (addrNewStringUTF != null) {
Interceptor.attach(addrNewStringUTF, {
onEnter: function (args) {
if (args[1] != null) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var string = Memory.readCString(args[1]);
console.log("[NewStringUTF] bytes:" + string, DebugSymbol.fromAddress(this.returnAddress));
}
}
},
onLeave: function (retval) { }
});
}
if (addrFindClass != null) {
Interceptor.attach(addrFindClass, {
onEnter: function (args) {
if (args[1] != null) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var name = Memory.readCString(args[1]);
console.log("[FindClass] name:" + name, DebugSymbol.fromAddress(this.returnAddress));
}
}
},
onLeave: function (retval) { }
});
}
if (addrGetMethodID != null) {
Interceptor.attach(addrGetMethodID, {
onEnter: function (args) {
if (args[2] != null) {
var clazz = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(clazz);
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var name = Memory.readCString(args[2]);
if (args[3] != null) {
var sig = Memory.readCString(args[3]);
console.log("[GetMethodID] class_name:" + class_name + " name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress));
} else {
console.log("[GetMethodID] class_name:" + class_name + " name:" + name, DebugSymbol.fromAddress(this.returnAddress));
}
}
}
},
onLeave: function (retval) { }
});
}
if (addrGetStaticMethodID != null) {
Interceptor.attach(addrGetStaticMethodID, {
onEnter: function (args) {
if (args[2] != null) {
var clazz = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(clazz);
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var name = Memory.readCString(args[2]);
if (args[3] != null) {
var sig = Memory.readCString(args[3]);
console.log("[GetStaticMethodID] class_name:" + class_name + " name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress));
} else {
console.log("[GetStaticMethodID] class_name:" + class_name + " name:" + name, DebugSymbol.fromAddress(this.returnAddress));
}
}
}
},
onLeave: function (retval) { }
});
}
if (addrGetFieldID != null) {
Interceptor.attach(addrGetFieldID, {
onEnter: function (args) {
if (args[2] != null) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var name = Memory.readCString(args[2]);
if (args[3] != null) {
var sig = Memory.readCString(args[3]);
console.log("[GetFieldID] name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress));
} else {
console.log("[GetFieldID] name:" + name, DebugSymbol.fromAddress(this.returnAddress));
}
}
}
},
onLeave: function (retval) { }
});
}
if (addrGetStaticFieldID != null) {
Interceptor.attach(addrGetStaticFieldID, {
onEnter: function (args) {
if (args[2] != null) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var name = Memory.readCString(args[2]);
if (args[3] != null) {
var sig = Memory.readCString(args[3]);
console.log("[GetStaticFieldID] name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress));
} else {
console.log("[GetStaticFieldID] name:" + name, DebugSymbol.fromAddress(this.returnAddress));
}
}
}
},
onLeave: function (retval) { }
});
}
if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
console.log("[RegisterNatives] method_count:", args[3], DebugSymbol.fromAddress(this.returnAddress));
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 脚本旨在监控 Android 应用程序中 libart.so
库的 Java Native Interface (JNI) 调用。这个脚本通过拦截特定的 JNI 方法,捕获和记录它们的调用信息,帮助我们分析应用程序的行为。
1. 脚本结构
该脚本主要分为几个部分:
- 常量和类定义: 定义了一些常量和
StdString
类,用于处理 C++ 字符串。 - 辅助函数:
prettyMethod
函数用于获取方法的可读名称。 - hook_libart 函数: 该函数负责查找和拦截
libart.so
中的 JNI 方法。 - 设置和执行: 最后调用
setImmediate
执行hook_libart
函数。
2. 关键功能
2.1 StdString
类
class StdString {
constructor() {
this.handle = Memory.alloc(STD_STRING_SIZE);
}
dispose() {
const [data, isTiny] = this._getData();
if (!isTiny) {
Java.api.$delete(data);
}
}
disposeToString() {
const result = this.toString();
this.dispose();
return result;
}
toString() {
const [data] = this._getData();
return data.readUtf8String();
}
_getData() {
const str = this.handle;
const isTiny = (str.readU8() & 1) === 0;
const data = isTiny ? str.add(1) : str.add(2 * Process.pointerSize).readPointer();
return [data, isTiny];
}
}
- 功能: 这个类用于处理 C++ 中的
std::string
对象,提供了字符串的读取和释放方法。 - 内存管理:
dispose
方法负责释放非小字符串的内存。
2.2 prettyMethod
函数
function prettyMethod(method_id, withSignature) {
const result = new StdString();
Java.api['art::ArtMethod::PrettyMethod'](result, method_id, withSignature ? 1 : 0);
return result.disposeToString();
}
- 功能: 该函数调用
art::ArtMethod::PrettyMethod
获取方法的可读名称,便于后续的日志记录。
2.3 hook_libart
函数
这是脚本的核心部分,负责查找和拦截多个 JNI 方法:
-
符号查找: 使用
Module.enumerateSymbolsSync
获取libart.so
中的符号,并根据名称过滤出相关的 JNI 方法。 -
拦截 JNI 方法: 对于每个找到的 JNI 方法,通过
Interceptor.attach
进行拦截,记录方法调用的详细信息。
例如,拦截 GetStringUTFChars
方法:
if (addrGetStringUTFChars != null) {
Interceptor.attach(addrGetStringUTFChars, {
onEnter: function (args) {
},
onLeave: function (retval) {
if (retval != null) {
var module = Process.findModuleByAddress(this.returnAddress);
if (module != null && module.name.indexOf(so_name) == 0) {
var bytes = Memory.readCString(retval);
console.log("[GetStringUTFChars] result:" + bytes, DebugSymbol.fromAddress(this.returnAddress));
}
}
}
});
}
- 参数和返回值处理: 在
onEnter
和onLeave
中处理方法的参数和返回值,记录相关信息。
3. 监控的 JNI 方法
该脚本监控了多个 JNI 方法,包括:
GetFieldID
GetMethodID
GetStaticFieldID
GetStaticMethodID
RegisterNatives
NewStringUTF
GetStringUTFChars
FindClass
CallStatic
CallNonvirtual
这些方法的调用通常涉及 Java 和 C/C++ 之间的交互,监控这些方法可以帮助分析应用程序的行为,尤其是在涉及 JNI 的部分。
4. 使用方法
要使用这个脚本,需要:
-
安装 Frida: 确保您已经在设备上安装了 Frida Server,并且在我们的计算机上安装了 Frida 工具。
-
运行命令: 使用如下命令运行脚本:
frida -U -f com.xingin.xhs -l hook_art.js -o xhsart.log frida -U -f com.tencent.mobileqq -l hook_art.js -o qqart.log
这里
-U
表示连接到 USB 设备,-f
指定要启动的应用程序,-l
指定要加载的脚本,-o
指定输出日志文件。
5. 总结
这个 Frida 脚本是一个强大的工具,用于监控 Android 应用程序中的 JNI 调用。通过拦截 libart.so
中的关键方法,可以深入了解 Java 和本地代码之间的交互,帮助进行调试、分析和安全研究。脚本的灵活性和可扩展性使其能够适应多种分析需求。