Frida hook思路备忘
Marce1

Java层

hook程序流程中被调用的普通方法,重写该方法的实现

模板:

1
2
3
4
5
6
7
8
9
10
11
12
Java.perform(function() {

var <class_reference> = Java.use("<package_name>.<class>");
<class_reference>.<method_to_hook>.implementation = function(<args>) {

/*
OUR OWN IMPLEMENTATION OF THE METHOD
*/

}

})
  • implementation中可以通过this.mathod_name()来调用原本类下的实现,同样也可以声明一个变量接收方法的返回值
  • 传入的参数可以自己定,注意类型要与原方法相同
  • 适用于button之类的触发式交互
  • 重载方法通过这样子定义:
1
2
3
4
5
6
7
<class_reference>.<method_to_hook>.overload('data_type1','data_type2').implementation = ...
//example
Act.check.overload('int', 'int').implementation = function(a, b){
console.log("random number:"+a);
console.log("your input:"+b);
this.check(a,b);
}

但是重载字符串的时候要注意,java是没有字符串这个数据类型的,因此重载的数据类型要写入java.lang.String

例子:

1
2
3
4
5
6
7
8
9
10
11
function test(){
Java.perform(function(){
var Act = Java.use("com.ad2001.frida0x1.MainActivity");
Act.check.overload('int', 'int').implementation = function(a, b){
console.log("random number:"+a);
console.log("your input:"+b);
this.check(a,b);
}
});
}
test();

hook类中的构造方法,重写实现

模板:

1
2
3
4
5
6
7
8
9
10
11
12
Java.perform(function() {

var <class_reference> = Java.use("<package_name>.<class>");
<class_reference>.$init.implementation = function(<args>) {

/*
OUR OWN IMPLEMENTATION OF THE METHOD
*/

}

})
  • 将方法名更改为$init

  • 为了在执行构造函数之前重写,不要先开启应用,执行命令开启应用

    1
    frida -U -f name.of.class -l test.js --no-pause

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
setTimeout(function(){
if(Java.available) {
Java.perform(function(){
console.log("start hook");
//同样的在这里先获取User类对象
var User = Java.use("com.roysue.roysueapplication.User");
if(User != undefined) {
//注意在使用钩子拦截构造函数时需要使用到 $init 也要注意参数的个数,因为该构造函数是2个所以此处填2个参数
User.$init.implementation = function (name,age){
//这里打印成员变量name和age的在运行中被调用填写的值
console.log("name:"+name);
console.log("age:"+age);
//最终要执行原本的init方法否则运行时会报异常导致原程序无法正常运行。
this.$init(name, age);
}
} else {
console.log("User: undefined");
}
console.log("hook end");
});
}
});

hook类的静态方法,重写实现

静态方法对实例无依赖,无需实例就能调用,只要有类在。因此是比较简单的hook

模板:

1
2
3
4
5
6
7
8
9
10
11
12
Java.perform(function() {

var <class_reference> = Java.use("<package_name>.<class>");
<class_reference>.<static_method>.implementation = function(<args>) {

/*
OUR OWN IMPLEMENTATION OF THE METHOD
*/

}

})

调用类的普通方法或修改成员变量,拥有实例

通过java.choose在堆上扫描实例化的对象

模板:

1
2
3
4
5
6
7
8
9
Java.performNow(function() {
Java.choose('<Package>.<class_Name>', {
onMatch: function(instance) {
instance.var.value = something;
// TODO
},
onComplete: function() {}
});
});
  • onMatch在每次实例被匹配到时执行,function(instance)中的instance就是匹配到的实例对象,你可以将它写成你喜欢的名字,不必时instance,比如ins。用instance.mathod()来直接调用该实例中可调用的方法
  • onComplete在每次Java.choose结束时执行,可有可无

例子:

1
2
3
4
5
6
7
8
9
10
function main(){
Java.perform(function(){
Java.choose("com.ad2001.frida0x5.MainActivity", {
onMatch: function(ins){
ins.flag(1337);
}
})
})
}
main()

调用类的普通方法,但缺少实例

缺少实例我们可以new()一个(MainActivity类之外的实例方法可以这么做)

模板:

1
2
3
4
5
6
7
Java.perform(function() {

var <class_reference> = Java.use("<package_name>.<class>");
var <class_instance> = <class_reference>.$new(); // Class Object
<class_instance>.<method>(); // Calling the method

})
  • 在该实例中我们可以随便执行方法,传入我们想传入的参数,给成员变量赋值

例子:

1
2
3
4
5
6
7
8
9
10
function main(){
Java.perform(function(){
let check = Java.use("com.ad2001.frida0x4.Check");
let ch = check.$new();
let result = ch.get_flag(1337);
console.log("result"+result);
console.log("test")
})
}
main()

MainActivity中的未创建实例的普通方法,我们不能创建实例,MainActivity可以看作是安卓程序的入口,具有唯一性,新建的实例没有上下文,不会参与到被Hook的程序已经执行的流程中去,所以我们要在内存中找到目标进行Hook。

  • Java.performNow :用于在 Java 运行时上下文中执行代码的函数。
  • Java.choose:在运行时枚举指定 Java 类的实例(作为第一个参数提供)。
1
2
3
4
5
6
7
8
9
Java.performNow(function() {
Java.choose('com.ad2001.frida0x5.MainActivity', {
onMatch: function(instance) {
console.log("Instance found");
instance.flag(1337);
},
onComplete: function() {}
});
});

拦截内部类

1
2
3
4
5
6
7
8
9
10
11
12
Java.perform(function() {

var <class_reference> = Java.use("<package_name>.<class>.User$inner_class");
<class_reference>.<method_to_hook>.implementation = function(<args>) {

/*
OUR OWN IMPLEMENTATION OF THE METHOD
*/

}

})

拦截动态加载dex

先通过枚举查找类加载器,然后寻找目标类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function hook_dyn_dex() {
  Java.perform(function () {
    //hook 动态加载的dex
    Java.enumerateClassLoaders({
      onMatch: function (loader) {
        try {
          if (loader.findClass("com.xxx")) {
            console.log(loader);
            Java.classFactory.loader = loader; //切换classloader
           }
          } catch (error) {
          }
      }, onComplete: function () {
      }
   });
    var DynamicCheck = Java.use("com.xxx");
    console.log(DynamicCheck);
    DynamicCheck.check.implementation = function () {
      console.log("DynamicCheck.check");
      return true;
    }
  });
}

Native层

Native层一般是一些本地服务和链接库,让我想起之前linux pwn中的elf文件动态链接,令人惊讶的是native层也是通过c语言和c++实现的。让我更震惊的是,在pwn中很多想都不敢想的操作——监视链接库函数调用,随意调用链接库函数,直接修改返回值,甚至修改.text段的权限,通过Frida都可以举重若轻的完成。

通过Module对象枚举、查找函数

Module就是模块,链接库,在frida中他作为一个对象有一个这样的结构:

索引 属性 含义
1 name 模块名称
2 base 模块地址,其变量类型为NativePointer
3 size 大小
4 path 完整文件系统路径

例子:

1
2
3
4
5
6
7
function frida_module(){
Process.EnumererateModules();//枚举一个进程的所有模组
console.log(Module.enumerateImports('lib****.so'));//在frida的CLI上枚举某个链接库导入的函数,如frida_labs中libfrida0x9的cmpstr函数通过引入libc.so的strcmp函数来实现
console.log(Module.enumerateExports('lib****.so'));//枚举链接库导出的函数
method_addr = addrModule.findExportByName(‘lib****.so','method_name');//已知要查找的具体函数的名字的情况下,在链接库的导出库中查找该函数的地址,返回十六进制地址
base_addr = Module.findBaseAddress(name);//返回模块(即链接库)的基地址
}
  • 顺便一提,安卓的链接库地址是自动开启aslr的,但同样也能通过计算基址偏移找到函数地址
  • module对象下的方法名字都很长,但都遵循首字母小写的驼峰命名法
  • 通过以上方法,结合分析apk文件反编出的Java代码,可以很容易的找出我们感兴趣的链接函数地址和函数链接情况
  • getExportByName方法的模块参数可有可无

通过Interceptor对象拦截函数

获取到模块函数的地址之后Interceptor.attach方法可以直接拦截该函数的调用,读取甚至更改参数和返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Interceptor.attach(targetAddress, {
onEnter: function (args) {
console.log('Entering ' + functionName);
var arg0 = Memory.readUtf8String(args[0]);
console.log(arg0)
// Modify or log arguments if needed
},
onLeave: function (retval) {
console.log('Leaving ' + functionName);
console.log("Original return value :" + retval);
retval.replace(1337) // changing the return value to 1337.
// Modify or log return value if needed
}
});
  • attach方法第一个参数传入目标函数的地址,第二个参数是两个回调函数,和Java.choose一样,onEnter是进入函数时触发,onLeave是函数退出时触发,不同的是这两个回调函数传入的分别是参数数组和返回值
  • replace方法极其强大,它不仅能更改参数和返回值,他甚至能直接劫持链接函数的实现!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 Java.perform(function () {
//这个c_getSum方法有两个int参数、返回结果为两个参数相加
//这里用NativeFunction函数自己定义了一个c_getSum函数
var add_method = new NativeFunction(Module.findExportByName('libhello.so', 'c_getSum'),
'int',['int','int']);
//输出结果 那结果肯定就是 3
console.log("result:",add_method(1,2));
//这里对原函数的功能进行替换实现
Interceptor.replace(add_method, new NativeCallback(function (a, b) {
//h不论是什么参数都返回123
return 123;
}, 'int', ['int', 'int']));
//再次调用 则返回123
console.log("result:",add_method(1,2));
});
}

当然,如果你觉得直接劫持函数不方便,你甚至还可以自己new一个!

通过Nativepointer对象创建函数

1
2
3
var native_adr = new NativePointer(<address_of_the_native_function>);//获取函数地址之后,我们要把它转换成native指针对象,才能作为参数传入
const native_function = new NativeFunction(native_adr, '<return type>', ['argument_data_type']);
native_function(<arguments>);

在定义的时候必须要将参数类型个数和参数类型以及返回值完全匹配,假设有三个参数都是int,则new NativeFunction(address, returnType, ['int', 'int', 'int']),而返回值是intnew NativeFunction(address, 'int', argTypes[, options]),必须要全部匹配,并且第一个参数一定要是函数地址指针。

例子:

1
2
3
4
var adr = Module.findBaseAddress("libfrida0xa.so").add(0x18BB0) // Address of the get_flag() function
var get_flag_ptr = new NativePointer(adr);
const get_flag = new NativeFunction(get_flag_ptr, 'void', ['int', 'int']);
get_flag(1,2);

通过Memory对象进行内存读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. 读取指定地址的字符串
const baseAddr = Module.findBaseAddress('libleidian.so')
console.log(baseAddr.add(0x1234).readCString())

// 2. dump指定地址的内存
console.log(hexdump(baseAddr.add(0x1234)))

// 3. 读指定地址的内存
console.log(baseAddr.add(0x1234).readByteArray(16))
console.log(Memory.readByteArray(baseAddr.add(0x2c00), 16)) //原先的API

// 4. 写指定地址的内存
baseAddr.add(0x1234).writeByteArray(stringToBytes('xiaojianbang'))

// 5. 申请新内存写入
Memory.alloc()
Memory.allocUtf8String()

// 6. 修改内存权限
Memory.protect(ptr(libso.base), libso.size, 'rwx')

reference

Frida Java Hook 详解(安卓9):代码及示例(上)

https://github.com/DERE-ad2001/Frida-Labs

FRIDA-API使用篇:Java、Interceptor、NativePointer(Function/Callback)使用方法及示例-安全KER - 安全资讯平台

FRIDA-API使用篇:rpc、Process、Module、Memory使用方法及示例-安全KER - 安全资讯平台

通过Frida-Labs入门Frida | Closure

由 Hexo 驱动 & 主题 Keep
总字数 47.9k 访客数 访问量