hook神器FRIDA

hook神器FRIDA

0x00 安装

  1. 使用python 一键安装
1
2
pip3 install frida
pip3 install frida-tools
  1. 在安卓手机上安装frida-server

    下载地址https://github.com/frida/frida/releases

    搜索frida-server,现在的手机一般都是arm64,平板是x86选择对应的版本下载

    使用adb工具将firda-server推送到已root手机的/data/loca/tmp中(其他目录也可以,一般是使用这个目录)

1
2
3
adb push frida-server-14.2.7-android-arm64 /data/local/tmp 
adb shell chmod +x /data/local/tmp/frida-server-14.2.7-android-arm64
adb shell /data/local/tmp/frida-server-14.2.7-android-arm64
  1. 使用命令frida-ps查看是否成功链接上frida-server
1
frida-ps -U

​ 如果出现了apk的包名则表示成功连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PID  Name
---- -------------------------------
261 adbd
1008 android.process.media
1656 com.android.defcontainer
4026 com.android.documentsui
4053 com.android.externalstorage
1759 com.android.gallery3d
1383 com.android.keychain
1718 com.android.phone
1791 com.android.settings
1690 com.android.settings:superuser
4081 com.android.shell
743 com.android.systemui
824 com.tencent.mm
1140 com.tencent.mm:push
.....

0x01 基础

先查看手册英文原版 中文版上中文版下

roysue大佬写了详细的教程

1. hook参数修改结果

例子下载地址

先安装apk,然后启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//apk主要代码
public class my_activity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_activity);
while (true){ //无限循环

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

fun(50,30);
}
}

void fun(int x , int y ){
Log.d("Sum" , String.valueOf(x+y)); //打印x+y的值
}


}

使用adb命令查看程序启动时打印的x+y的值

1
2
3
4
5
6
7
8
9
10
11
12
01-21 22:28:04.489  4910  4910 D Sum     : 80
01-21 22:28:05.523 4910 4910 D Sum : 80
01-21 22:28:06.614 4910 4910 D Sum : 80
01-21 22:28:07.615 4910 4910 D Sum : 80
01-21 22:28:08.617 4910 4910 D Sum : 80
01-21 22:28:09.618 4910 4910 D Sum : 80
01-21 22:28:10.619 4910 4910 D Sum : 80
01-21 22:28:11.619 4910 4910 D Sum : 80
01-21 22:28:12.620 4910 4910 D Sum : 80
01-21 22:28:13.630 4910 4910 D Sum : 80
01-21 22:28:14.639 4910 4910 D Sum : 80
01-21 22:28:15.639 4910 4910 D Sum : 80

按照官方的手册写一段劫持的js代码,命名为demo1.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
console.log("Script loaded successfully ");
Java.perform(function x() { //Java.perform(fn): ensure that the current thread is attached to the VM and call fn
console.log("Inside java perform function");
//添加要劫持的类
var my_class = Java.use("com.example.a11x256.frida_test.my_activity");
//重写函数
my_class.fun.implementation = function (x, y) {
//打印函数原来的值
console.log("original call: fun(" + x + ", " + y + ")");
//使用2,5代替x,y返回参数
var ret_value = this.fun(2, 5);
console.log("now call: fun(2, 5)");
return ret_value;
}
});

使用python调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import sys, frida


with open("./demo1.js","r+") as jsfile: #读取刚才写的js的内容
jscode=jsfile.read()

device = frida.get_usb_device() //获取usb设备
pid = device.spawn("com.example.a11x256.frida_test") #注入apk
device.resume(pid)
time.sleep(1) #休眠1秒,如无此操作,部分类可能无法hook
session = device.attach(pid)
script = session.create_script(jscode) #创建脚本
script.load() #加载脚本

sys.stdin.read() #持续查看输入

当执行此脚本时,观察logcat打印的信息,发现不是打印80而是改成7了。

当脚本执行时

2. 劫持重载函数,主动调用

例子下载链接

同样先安装apk,然后打开
主要代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.example.a11x256.frida_test;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

public class my_activity extends AppCompatActivity {
private String total = "@@@###@@@";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_activity);
while (true){

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
fun(50,30);
Log.d("string" , fun("LoWeRcAsE Me!!!!!!!!!"));
}
}
void fun(int x , int y ){

Log.d("Sum" , String.valueOf(x+y));
}

String fun(String x){
total +=x;
return x.toLowerCase();
}
String secret(){
return total;
}
}

与例子一比较,多了2个地方,一个fun的重载函数和一个secret函数。

启动app后使用logcat查看打印的日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
01-22 01:54:11.019  5264  5264 D Sum     : 80
01-22 01:54:11.019 5264 5264 D string : lowercase me!!!!!!!!!
01-22 01:54:12.020 5264 5264 D Sum : 80
01-22 01:54:12.020 5264 5264 D string : lowercase me!!!!!!!!!
01-22 01:54:13.030 5264 5264 D Sum : 80
01-22 01:54:13.030 5264 5264 D string : lowercase me!!!!!!!!!
01-22 01:54:14.037 5264 5264 D Sum : 80
01-22 01:54:14.037 5264 5264 D string : lowercase me!!!!!!!!!
01-22 01:54:15.038 5264 5264 D Sum : 80
01-22 01:54:15.038 5264 5264 D string : lowercase me!!!!!!!!!
01-22 01:54:16.040 5264 5264 D Sum : 80
01-22 01:54:16.040 5264 5264 D string : lowercase me!!!!!!!!!
01-22 01:54:17.045 5264 5264 D Sum : 80
01-22 01:54:17.045 5264 5264 D string : lowercase me!!!!!!!!!
01-22 01:54:18.049 5264 5264 D Sum : 80
01-22 01:54:18.049 5264 5264 D string : lowercase me!!!!!!!!!
01-22 01:54:19.055 5264 5264 D Sum : 80
01-22 01:54:19.055 5264 5264 D string : lowercase me!!!!!!!!!

重载函数需要加一个overload来处理,参数为String类的时候,由于String类不是Java基本数据类型,而是java.lang.String类型,所以在替换参数的构造上,需要使用java.lang.String类。

1
2
3
my_class.fun.overload("int" , "int").implementation = function(x,y){
...
my_class.fun.overload("java.lang.String").implementation = function(x){

js脚本就变成了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Java.perform(function x() { 
console.log("Inside java perform function");

var my_class= Java.use("com.example.a11x256.frida_test.my_activity")
var string_class = Java.use("java.lang.String"); //参数为String类的时候,由于String类不是Java基本数据类型,而是java.lang.String类型,所以在替换参数的构造上,需要使用java.lang.String类

my_class.fun.overload("java.lang.String").implementation = function (x) {
console.log("****************")
//print the original arguments
var my_string = string_class.$new("MYSTRINGS");
console.log("original call: fun(" + this.fun(x)+")"); //调用原函数
var ret_value = this.fun(my_string);
console.log("modefied strings is :" + my_string);
console.log("****************")
return ret_value; //返回修改后的值MYSTRINGS
}

my_class.fun.overload("int","int").implementation = function (x,y) {
console.log("****************")
console.log("original call: fun(" +x+","+y+")"); //打印原来的参数
var ret_value = this.fun(x,45);
console.log("modefied args is :" + x+",45" );
console.log("****************")
return ret_value; //返回修改后的值X+45
}

});

在此处再加一个python的消息处理接受函数,使用script.on()调用

1
2
3
4
5
def on_message(message, data):
if message['type'] == 'seend':
print("[*] {0}".format(message['payload']))
else:
print(message)

完整的python脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import sys, frida, time

def on_message(message, data): #消息处理
if message['type'] == 'seend':
print("[*] {0}".format(message['payload']))
else:
print(message)

with open("./demo2.js","r+") as jsfile: #读取js文件的内容
jscode=jsfile.read()


device = frida.get_usb_device() #使用usb设备
pid = device.spawn("com.example.a11x256.frida_test") #使用spwan模式
device.resume(pid)
time.sleep(1) #休眠一秒
session = device.attach(pid)
script = session.create_script(jscode) #创建脚本
script.on("message", on_message) #消息处理
script.load() #加载脚本
sys.stdin.read()

启动脚本,并且查看logcat的日志输出的日志变成了劫持后的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
01-22 01:56:36.312  5291  5291 D Sum     : 95
01-22 01:56:36.323 5291 5291 D string : mystrings
01-22 01:56:37.324 5291 5291 D Sum : 95
01-22 01:56:37.328 5291 5291 D string : mystrings
01-22 01:56:38.328 5291 5291 D Sum : 95
01-22 01:56:38.329 5291 5291 D string : mystrings
01-22 01:56:39.329 5291 5291 D Sum : 95
01-22 01:56:39.330 5291 5291 D string : mystrings
01-22 01:56:40.331 5291 5291 D Sum : 95
01-22 01:56:40.332 5291 5291 D string : mystrings
01-22 01:56:41.332 5291 5291 D Sum : 95
01-22 01:56:41.333 5291 5291 D string : mystrings
01-22 01:56:42.334 5291 5291 D Sum : 95
01-22 01:56:42.335 5291 5291 D string : mystrings
01-22 01:56:43.336 5291 5291 D Sum : 95
01-22 01:56:43.337 5291 5291 D string : mystrings

3. 主动调用,远程调用

此时还有一个sercet函数没有调用。想要调用可以搜索内存中的此函数并进行调用。搜索函数使用Java.choose

关于Java.use和Java.choose的

Java.use(fn): 把当前线程附加到Java VM环境中去,并且执行Java函数fn(如果已经在Java函数的回调中,则不需要再附加到VM)

Java.choose(className, callbacks): 在Java的内存堆上扫描指定类名称的Java对象,每次扫描到一个对象,则回调callbacks:

  • onMatch: function(instance): 每次扫描到一个实例对象,调用一次,函数返回stop结束扫描的过程
  • onComplete: function(): 当所有的对象都扫描完毕之后进行回调
1
2
3
4
5
6
7
8
9
10
11
12
13
console.log("Script loaded successfully ");
Java.perform(function x() {
console.log("Inside java perform function");

Java.choose("com.example.a11x256.frida_test.my_activity", {
onMatch: function (instance) { //在内存上搜索class的实例
console.log("Found instance: " + instance); //搜索到后打印
console.log("Result of secret func: " + instance.secret()); //调用隐藏方法
},
onComplete: function () {}
});

});

python脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import sys, frida, time


def on_message(message, data):
if message['type'] == 'seend':
print("[*] {0}".format(message['payload']))
else:
print(message)

with open("./demo2.js","r+") as jsfile:
jscode=jsfile.read()

device = frida.get_usb_device()
pid = device.spawn("com.example.a11x256.frida_test")
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
script = session.create_script(jscode)
script.on("message", on_message)
script.load()

python的输出结果为

1
2
3
4
5
6
7
Script loaded successfully 
Inside java perform function
Found instance: com.example.a11x256.frida_test.my_activity@e0b1cfc
Result of secret func: @@@###@@@LoWeRcAsE Me!!!!!!!!!
end...

Process finished with exit code 0

这么写只能调用一次,现在需要调用很多次时候。rpc.exports登场

rpc.exports: 可以在你的程序中导出一些 RPC-Style API函数,Key指定导出的名称,Value指定导出的函数,函数可以直接返回一个值,也可以是异步方式以 Promise 的方式返回

对上面的代码用一个函数包起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Secret() {   //Defining the function that will be exported
Java.perform(function () { //code that calls `secret` function from the previous example
Java.choose("com.example.a11x256.frida_test.my_activity", {
onMatch: function (instance) {
console.log("Found instance: " + instance);
console.log("Result of secret func: " + instance.secret());
},
onComplete: function () {
}
});

});
}

pc.exports = {
secret:Secret //使用secret函数名称代替Secret函数名,不能有大写字母下划线。在处理的时候会被转化成小写,下划线会被去掉
}

python脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import frida,time

def on_message(message, data):
if message['type'] == 'seend':
print("[*] {0}".format(message['payload']))
else:
print(message)

with open ("./demo3.js","r+") as jsfile:
jscode=jsfile.read()

device =frida.get_usb_device()
pid = device.attach("com.example.a11x256.frida_test")
script=pid.create_script(jscode)
script.on("message",on_message)
script.load()

while 1 :
script.exports.call()

0x02 参考资料

https://11x256.github.io/
https://frida.re/docs/javascript-api/
https://github.com/r0ysue/AndroidSecurityStudy
https://eternalsakura13.com/2020/07/04/frida/#more

  • © 2019-2023 sunny250
  • Hexo Theme Ayer by shenyu
    • PV:
    • UV: