5

hook神器FRIDA

 1 year ago
source link: https://sunny250.github.io/2021/02/14/hook%E7%A5%9E%E5%99%A8FRIDA/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

hook神器FRIDA

0x00 安装

  1. 使用python 一键安装
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中(其他目录也可以,一般是使用这个目录)

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
frida-ps -U

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

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,然后启动

//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的值

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

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调用

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了。

当脚本执行时

demo1.png

demo1_stop.png

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

例子下载链接

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

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查看打印的日志

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类。

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

js脚本就变成了

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()调用

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

完整的python脚本

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的日志输出的日志变成了劫持后的

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(): 当所有的对象都扫描完毕之后进行回调
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脚本如下

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的输出结果为

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 的方式返回

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

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脚本如下

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK