frida初探3:frida常见的hook用法
文章最后更新时间为:2022年05月13日 16:00:16
系列文章:
本篇文章主要讲一下frida的常见用法。都是看着肉丝师傅的文章总结的,比较工具化,请大佬勿喷。
1. 智能补全
git clone https://github.com/oleavr/frida-agent-example.git
cd frida-agent-example/
npm install
然后使用VSCode等IDE打开此工程,在agent下编写typescript,会有智能提示。
npm run watch会监控代码修改自动编译生成js文件
frida -U -f com.example.android --no-pause -l _agent.js
2. frida数据类型
Frida中的基本类型全名 | Frida中的基本类型缩写(定义数组时使用) |
---|---|
boolean | Z |
byte | B |
char | C |
double | D |
float | F |
int | I |
long | J |
short | S |
在Frida中用[表示数组。例如是int类型的数组,写法为:[I,如果是String类型的数组,则写法为:[java.lang.String; 注意:后面还有个;号。
2.1 char[]
const gson = Java.use('com.r0ysue.gson.Gson');
Java.use("java.util.Arrays").toString.overload('[C').implementation = function(charArray){
var result = this.toString(charArray);
console.log("charArray,result:",charArray,result)
console.log("charArray Object Object:",gson.$new().toJson(charArray));
return result;
}
gson的调用查看:https://bbs.pediy.com/thread-259186.htm
2.2byte[]
const gson = Java.use('com.r0ysue.gson.Gson');
Java.use("java.util.Arrays").toString.overload('[B').implementation = function(byteArray){
var result = this.toString(byteArray);
console.log("byteArray,result):",byteArray,result)
console.log("byteArray Object Object:",gson.$new().toJson(byteArray));
return result;
}
2.3 java array构造
Java.use("java.util.Arrays").toString.overload('[C').implementation = function(charArray){
var newCharArray = Java.array('char', [ '一','去','二','三','里' ]);
var result = this.toString(newCharArray);
console.log("newCharArray,result:",newCharArray,result)
console.log("newCharArray Object Object:",gson.$new().toJson(newCharArray));
var newResult = Java.use('java.lang.String').$new(Java.array('char', [ '烟','村','四','五','家']))
return newResult;
}
3. 枚举所有的类
hook第一步是需要找到需要hook的类,可以通过以下方式枚举所有加载到内存中的类:
function main() {
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (name, handle){
console.log("name:" + name + " handle:" + handle)
},
onComplete: function () {
console.log("end")
}
})
})
}
setImmediate(main)
一般情况下需要通过关键字来过滤一下类:
function main() {
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (name, handle){
if (name.split(".")[1] == "bluetooth"){
console.log("name:" + name + " handle:" + handle)
}
if (name.indexOf("android.bluetooth.BluetoothMap") != -1) {
console.log("name:" + name + " handle:" + handle)
}
// console.log("name:" + name + " handle:" + handle)
},
onComplete: function () {
console.log("end")
}
})
})
}
setImmediate(main)
4. 枚举类中的方法
function main() {
var a = enumMethods("android.bluetooth.BluetoothDevice")
a.forEach(function(s) {
console.log(s);
});
}
function enumMethods(targetClass)
{
var hook = Java.use(targetClass);
var ownMethods = hook.class.getDeclaredMethods();
hook.$dispose;
return ownMethods;
}
setImmediate(main)
5. 主动调用
- 静态函数直接use class然后调用方法,非静态函数需要先choose实例然后调用。
- 设置成员变量的值,写法是xx.value = yy,其他方面和函数一样。如果有一个成员变量和成员函数的名字相同,则在其前面加一个_,如_xx.value = yy
function main() {
Java.perform(function () {
//hook静态函数直接调用
var demoClass = Java.use("com.example.demo.DemoClass")
demoClass.setAge()
//hook动态函数,找到instance实例,从实例调用函数方法
Java.choose("com.example.demo.DemoClass", {
onMatch: function (instance) {
instance.setName()
},
onComplete: function () {
console.log("end")
}
})
})
}
setImmediate(main)
6. 打印栈回溯
java层
function main() { Java.perform(function () { var demoClass = Java.use("com.example.demo.DemoClass") demoClass.isHook.implementation = function () { showStacks() printStack("com.example.demo.DemoClass.IsHook") var result = this.isHook() return false }}) } function printStack(name) { Java.perform(function () { var Exception = Java.use("java.lang.Exception"); var ins = Exception.$new("Exception"); var straces = ins.getStackTrace(); if (straces != undefined && straces != null) { var strace = straces.toString(); var replaceStr = strace.replace(/,/g, "\n"); console.log("=============================" + name + " Stack strat======================="); console.log(replaceStr); console.log("=============================" + name + " Stack end=======================\r\n"); Exception.$dispose(); } }); } setImmediate(main)
- so层
Interceptor.attach(Module.findExportByName("libnative-lib.so", 'Java_cn_hluwa_fridasamples_MainActivity_stringFromJNI'),
{
onEnter: function (args) {
console.log(Thread.backtrace(this.context, Backtracer.FUZZY)
.map(DebugSymbol.fromAddress).join("\n"))
},
onLeave: function (retval) {
}
});
/** output
*
* 0xb38796ec base.odex!oatexec+0x596ec
* 0x73fc33de system@framework@boot.oat!oatexec+0x104a3de
* 0x73755a08 system@framework@boot.oat!oatexec+0x7dca08
* 0x73fd47fc system@framework@boot.oat!oatexec+0x105b7fc
* 0x73755a5c system@framework@boot.oat!oatexec+0x7dca5c
* 0x73d31dee system@framework@boot.oat!oatexec+0xdb8dee
* 0xb48febf0 libart.so!0xd9bf0
* 0xb48fd332 libart.so!0xd8332
* 0xb4c199ae libart.so!art_quick_invoke_static_stub+0xad
* 0xb4b427f6 libart.so!0x31d7f6
* 0xb4903992 libart.so!_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+0x125
* 0xb4b45c62 libart.so!_ZN3art12InvokeMethodERKNS_33ScopedObjectAccessAlreadyRunnableEP8_jobjectS4_S4_j+0x2a5
* 0x7425b0d8 system@framework@boot.oat!oatexec+0x12e20d8
* 0x7425b0d8 system@framework@boot.oat!oatexec+0x12e20d8
* 0xb4afb0c0 libart.so!0x2d60c0
* 0x731cdeea system@framework@boot.oat!oatexec+0x254eea
*/
/** 官方文档: https://frida.re/docs/javascript-api/#thread
*
* Thread.backtrace([context, backtracer]): generate a backtrace for the current thread, returned as an array of NativePointer objects.
* If you call this from Interceptor’s onEnter or onLeave callbacks you should provide this.context for the optional context argument,
* as it will give you a more accurate backtrace. Omitting context means the backtrace will be generated from the current stack location,
* which may not give you a very good backtrace due to V8’s stack frames. The optional backtracer argument specifies the kind of backtracer to use,
* and must be either Backtracer.FUZZY or Backtracer.ACCURATE, where the latter is the default if not specified.
* The accurate kind of backtracers rely on debugger-friendly binaries or presence of debug information to do a good job,
* whereas the fuzzy backtracers perform forensics on the stack in order to guess the return addresses, which means you will get false positives,
* but it will work on any binary.
*/
7. hook构造函数
通过clazz.$init.implementation来构造函数。
function main() {
Java.perform(function () {
Java.use("com.tlamb96.kgbmessenger.b.a").$init.implementation = function (i, str1, str2, z) {
this.$init(i, str1, str2, z)
console.log(i, str1, str2, z)
}
})
}
setImmediate(main)
8. 方法的重载
对方法有重载时,直接hook该方法会报错。
void fun(int x , int y ){
Log.d("ROYSUE.Sum" , String.valueOf(x+y));
}
String fun(String x){
total +=x;
return x.toLowerCase();
}
所以我们需要指定具体hook的是哪个重载的方法,用法如下:
my_class.fun.overload("int" , "int").implementation = function(x,y){
...
my_class.fun.overload("java.lang.String").implementation = function(x){
当我们要hook某个方法的所有重载时,可以这么做:
//目标类
var hook = Java.use(targetClass);
//重载次数
var overloadCount = hook[targetMethod].overloads.length;
//打印日志:追踪的方法有多少个重载
console.log("Tracing " + targetClassMethod + " [" + overloadCount + " overload(s)]");
//每个重载都进入一次
for (var i = 0; i < overloadCount; i++) {
//hook每一个重载
hook[targetMethod].overloads[i].implementation = function() {
console.warn("n*** entered " + targetClassMethod);
// 打印参数
if (arguments.length) console.log();
for (var j = 0; j < arguments.length; j++) {
console.log("arg[" + j + "]: " + arguments[j]);
}
//打印返回值
var retval = this[targetMethod].apply(this, arguments); // rare crash (Frida bug?)
console.log("nretval: " + retval);
console.warn("n*** exiting " + targetClassMethod);
return retval;
}
}
9. rpc调用
也就是开放一个rpc接口,给别的程序或者平台去调用。
- js代码
// s3.js
console.log("Script loaded successfully ");
function callSecretFun() { //定义导出函数
Java.perform(function () { //找到隐藏函数并且调用
Java.choose("com.roysue.demo02.MainActivity", {
onMatch: function (instance) {
console.log("Found instance: " + instance);
console.log("Result of secret func: " + instance.secret());
},
onComplete: function () { }
});
});
}
rpc.exports = {
callsecretfunction: callSecretFun //把callSecretFun函数导出为callsecretfunction符号,导出名不可以有大写字母或者下划线
};
- python代码
# loader3.py
import time
import frida
def my_message_handler(message, payload):
print message
print payload
device = frida.get_usb_device()
pid = device.spawn(["com.roysue.demo02"])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
with open("s3.js") as f:
script = session.create_script(f.read())
script.on("message", my_message_handler)
script.load()
command = ""
while 1 == 1:
command = raw_input("Enter command:\n1: Exit\n2: Call secret function\nchoice:")
if command == "1":
break
elif command == "2": #在这里调用
script.exports.callsecretfunction()
效果如下:
$ python loader3.py
Script loaded successfully
Enter command:
1: Exit
2: Call secret function
choice:2
Found instance: com.roysue.demo02.MainActivity@2eacd80
Result of secret func: @@@###@@@LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!
Enter command:
1: Exit
2: Call secret function
choice:2
Found instance: com.roysue.demo02.MainActivity@2eacd80
Result of secret func: @@@###@@@LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!
Enter command:
1: Exit
2: Call secret function
choice:2
Found instance: com.roysue.demo02.MainActivity@2eacd80
Result of secret func: @@@###@@@LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!LoWeRcAsE Me!!!!!!!!!
Enter command:
1: Exit
2: Call secret function
choice:1
10. 枚举so以及其导入导出表
// 列举所有加载的module,也就是so
function listso() {
Java.perform(function () {
//枚举当前加载的模块
var process_Obj_Module_Arr = Process.enumerateModules();
for (var i = 0; i < process_Obj_Module_Arr.length; i++) {
//包含"lib"字符串的
if (process_Obj_Module_Arr[i].path.indexOf("lib") != -1) {
console.log("模块名称:", process_Obj_Module_Arr[i].name);
console.log("模块地址:", process_Obj_Module_Arr[i].base);
console.log("大小:", process_Obj_Module_Arr[i].size);
console.log("文件系统路径", process_Obj_Module_Arr[i].path);
}
}
});
}
// 列举某个module的导入导出函数
function listsoinout(name) {
Java.perform(function () {
var imports = Module.enumerateImportsSync(name);
var exports = Module.enumerateExportsSync(name);
for (var i = 0; i < imports.length; i++) {
console.log(imports[i].name + ": " + imports[i].address);
}
var exports = Module.enumerateExportsSync("libhello.so");
for (var i = 0; i < exports.length; i++) {
console.log(exports[i].name + ": " + exports[i].address);
}
})
}
// 列举某个module的Symbol函数
function frida_Module() {
Java.perform(function () {
const hooks = Module.load('libc.so');
var Symbol = hooks.enumerateSymbols();
for(var i = 0; i < Symbol.length; i++) {
console.log("isGlobal:",Symbol[i].isGlobal);
console.log("type:",Symbol[i].type);
console.log("section:",JSON.stringify(Symbol[i].section));
console.log("name:",Symbol[i].name);
console.log("address:",Symbol[i].address);
}
});
}
11. hook native基本用法
有导出函数时:
function hook_native() { console.log("[*] Starting Hook Script."); var so_base_address = Module.findBaseAddress("libcyberpeace.so") console.log("so_base_address is: " + so_base_address) if (so_base_address) { var string_with_jni_addr = Module.findExportByName("libcyberpeace.so", "Java_com_testjava_jack_pingan2_cyberpeace_CheckString") console.log("string_with_jni_addr is: " + string_with_jni_addr) Interceptor.attach(string_with_jni_addr, { onEnter: function (args) { console.log("string_with_jni args: " + args[0], args[1], args[2]) console.log(Java.vm.getEnv().getStringUtfChars(args[2], null).readCString()) }, onLeave: function (retval) { console.log("[*] 原始的So层函数返回值是:", retval) console.log(Java.vm.getEnv().getStringUtfChars(retval, null).readCString()) var newRetval = Java.vm.getEnv().newStringUtf("new retval from hook_native"); retval.replace(ptr(newRetval)); } }) } else { console.log("find so base address fail", so_base_address) } }
- 无导出函数时
需要手动定位要hook的函数地址,一般通过静态分析得出:so地址 + 函数偏移
function hook_native1() {
console.log("[*] Starting Hook Script.");
var so_base_address = Module.findBaseAddress("libcyberpeace.so")
console.log("so_base_address is: " + so_base_address) // 32位需要加1,这里是64位
if (so_base_address) {
//要hook的函数在函数里面的偏移
var n_addr_func_offset = 0x840;
//加载到内存后 函数地址 = so地址 + 函数偏移
var n_addr_func = so_base_address.add(n_addr_func_offset)
var ptr_func = new NativePointer(n_addr_func);
Interceptor.attach(ptr_func, {
onEnter: function (args) {
console.log("string_with_jni args: " + args[0], args[1], args[2])
console.log(Java.vm.getEnv().getStringUtfChars(args[2], null).readCString())
},
onLeave: function (retval) {
console.log("[*] 原始的So层函数返回值是:", retval)
console.log(Java.vm.getEnv().getStringUtfChars(retval, null).readCString())
var newRetval = Java.vm.getEnv().newStringUtf("new retval from hook_native");
retval.replace(ptr(newRetval));
retval.replace(1);
}
})
} else {
console.log("find so base address fail", so_base_address)
}
}
或者也可以通过symbols符号来定位native方法地址:
function find_func_from_symbols() {
var NewStringUTF_addr = null;
var symbols = Process.findModuleByName("libart.so").enumerateSymbols();
for (var i in symbols) {
var symbol = symbols[i];
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0
){
if (symbol.name.indexOf("NewStringUTF") >= 0) {
console.log("find target symbols", symbol.name, "address is ", symbol.address);
NewStringUTF_addr = symbol.address;
}
}
}
console.log("NewStringUTF_addr is ", NewStringUTF_addr);
Interceptor.attach(NewStringUTF_addr, {
onEnter: function (args) {
console.log("args0",args[0])
},
onLeave: function (returnResult) {
console.log("result: ", Java.cast(returnResult, Java.use("java.lang.String")));
}
})
}
关于so层的hook和逆向,后续单独再写。