Frida

Frida原理

frida注入的原理就是找到目标进程,使用ptrace跟踪目标进程获取mmap,dlpoen,dlsym等函数库的偏移获取mmap在目标进程申请一段内存空间将在目标进程中找到存放frida-agent-32/64.so的空间启动执行各种操作由agent去实现

Frida基础指令

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
frida-ps --help
使用方式: frida-ps [选项]

选项:
-h, --help 显示帮助信息并退出
-D ID, --device ID 连接到具有给定ID的设备
-U, --usb 连接到USB设备
-R, --remote 连接到远程frida-server
-H HOST, --host HOST 连接到HOST上的远程frida-server
--certificate CERTIFICATE
与HOST进行TLS通信,期望的CERTIFICATE
--origin ORIGIN 连接到设置了"Origin"头为ORIGIN的远程服务器
--token TOKEN 使用TOKEN验证HOST
--keepalive-interval INTERVAL
设置心跳包间隔(秒),或设置为0以禁用(默认为-1,根据传输方式自动选择)
--p2p 与目标建立点对点连接
--stun-server ADDRESS
设置与--p2p一起使用的STUN服务器地址
--relay address,username,password,turn-{udp,tcp,tls}
添加与--p2p一起使用的中继
-O FILE, --options-file FILE
包含额外命令行选项的文本文件
--version 显示程序版本号并退出
-a, --applications 只列出应用程序
-i, --installed 包括所有已安装的应用程序
-j, --json 以JSON格式输出结果

先开启adb,再运行frida

1
2
3
4
5
6
7
C:\Users\n1ng>adb root
restarting adbd as root

C:\Users\n1ng>adb shell
gracelte:/ # su
gracelte:/ # cd data/local/tmp
gracelte:/data/local/tmp # ./fr

1.操作模式

CLI(命令模式):通过命令行直接将JavaScript脚本注入到进程中

RPC模式:使用python进行JavaScript脚本的注入,可以通过RPC传输给python脚本进行复杂数据的处理

2.注入模式和启动命令

Spawn模式:将启动App的权利交由Frida来控制,即使目标App已经启动,在使用Frida注入程序时还是会重新启动App

1
frida -U -f 进程名 -l hook.js     //Spawn模式

Attach模式:在目标App已经启动的情况下,Frida通过ptrace注入程序从而执行Hook的操作

1
frida -U 进程名 -l hook.js        //Attach模式

frida_server自定义端口:

1
2
3
4
5
6
frida server 默认端口:27042

taimen:/ $ su
taimen:/ # cd data/local/tmp/
taimen:/data/local/tmp # ./fs1280 -l 0.0.0.0:6666

3.基础语法

API名称 描述
Java.use(className) 获取指定的Java类并使其在JavaScript代码中可用。
Java.perform(callback) 确保回调函数在Java的主线程上执行。
Java.choose(className, callbacks) 枚举指定类的所有实例。
Java.cast(obj, cls) 将一个Java对象转换成另一个Java类的实例。
Java.enumerateLoadedClasses(callbacks) 枚举进程中已经加载的所有Java类。
Java.enumerateClassLoaders(callbacks) 枚举进程中存在的所有Java类加载器。
Java.enumerateMethods(targetClassMethod) 枚举指定类的所有方法。

4.日志输出语法

日志方法 描述 区别
console.log() 使用JavaScript直接进行日志打印 多用于在CLI模式中,console.log()直接输出到命令行界面,使用户可以实时查看。在RPC模式中,console.log()同样输出在命令行,但可能被Python脚本的输出内容掩盖。
send() Frida的专有方法,用于发送数据或日志到外部Python脚本 多用于RPC模式中,它允许JavaScript脚本发送数据到Python脚本,Python脚本可以进一步处理或记录这些数据。

安装Frida代码提示

进入工程文件

1
npm i @types/frida-gum

Frida使用

Frida启动前注入

1
frida -U -f com.n1ng.app -l Hook.js

注入后app启动但停滞在启动界面,输入‘%resume’进入app,后面加‘–no–pause ’则不会停滞

使用python来注入,此时使用的是ip加端口来连接,在启动服务端时需要监听端口,即加上 “-l 0.0.0.0:27042”

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# -*- coding: UTF-8 -*-
import frida, sys


jsCode = """


Java.perform(function(){

var RequestUtil = Java.use('com.dodonew.online.http.RequestUtil');
RequestUtil.encodeDesMap.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(a, b, c){
console.log('data: ', a);
console.log('desKey: ', b);
console.log('desIV: ', c);
var retval = this.encodeDesMap(a, b, c);
console.log('retval: ', retval);
return retval;
}

var Utils = Java.use('com.dodonew.online.util.Utils');
Utils.md5.implementation = function(a){
console.log('MD5 string: ', a);
var retval = this.md5(a);
console.log('retval: ', retval);
return retval;
}

});




""";


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

#process = frida.get_usb_device().attach(27082)
#process = frida.get_remote_device().attach('com.dodonew.online')
process1 = frida.get_device_manager().add_remote_device('192.168.1.11:9999').attach('com.dodonew.online')
process2 = frida.get_device_manager().add_remote_device('127.0.0.1:6666').attach('com.dodonew.online')
script1 = process1.create_script(jsCode)
script2 = process2.create_script(jsCode)
#script.on("message", message)

script1.load()
script2.load()

print("开始运行");

sys.stdin.read()

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
import frida, sys

jscode = """

function hookTest1(){
Java.perform(function(){
var Utils = Java.use('com.xiaojianbang.app.Utils');
Utils.getCalc.implementation = function(a, b){
console.log('getCalc: ', a, b);
var retval = this.getCalc(a, b);
console.log('retval: ', retval);
return 4000;
}

});
}
hookTest1();

"""

rdev = frida.get_usb_device()
pid = rdev.spawn(["com.xiaojianbang.app"]) #已挂起方式创建进程
print(pid)
process = rdev.attach(pid) #附加到该进程
script = process.create_script(jscode)
script.load()
rdev.resume(pid) #创建完脚本, 恢复进程运行
sys.stdin.read()

1
2
3
4
5
6
7
8
9
10
Java.perform(function(){
var RequestUtil = Java.use("com.dodonew.online.http.RequestUtil");
console.log(RequestUtil);
RequestUtil.encodeDesMap.implementation = function(a,b,c){//利用implementation来将encodeDesMap函数替换为function
var retval = this.encodeDesMap(a,b,c);//调用原函数
console.log(retval);
return retval;
}
});
//如果报重载的错误则改为RequestUtil.encodeDesMap.overload('java.lang.string').implementation = function(a,b,c),overload内的参数即需要hook的重载函数的参数

Hook Java层(Hook底层函数)

可以去HookHashMap,ArrayList,自吐算法,String,JsonObject,StringBuilder(批量的字符串处理,如签名时排序,很多会用StringBuilder作为容器)

1
2
3
4
5
6
7
Java.perform(function(){
var Map = Java.use("Java.util.Map");
Map.put.implementation=function(a,b){
console.log("Map:",a,b);
return this.put(a,b);
}
});

函数调用栈的打印

1
2
3
4
5
6
7
8
9
function showStack(){
//var log = Java.use('android.util.log');
//var throwable = Java.use('Java.lang.Thtowable');
//log.d('n1ng','Stack Dump',throwable.$new());

var stack = Java.use('android.util.log').getStackTraceString(Java.use('Java.lang.Throwable').$new());
//利用getStackTraceString来得到堆栈
console.log(stack);
}

Hook构造函数

需要加‘$init’

1
2
3
4
5
6
7
8
9
10
11
function hookTest2(){
Java.perform(function(){
var money = Java.use("com.xiaojianbang.app.Money");
money.$init.overload('java.lang.String', 'int').implementation = function(str, num){
console.log(str, num);
str = "欧元";
num = 2000;
this.$init(str, num);
}
});
}

Hook修改类的字段

非静态字段使用Java.choose来选择对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fuction Hooktest3(){
Java.perform(function(){
var money = Java.use('com.xiaojianbang.app.Money');
console.log('money.flag:',JSON.stringify(money.flag));
money.flag.value='xiaojianbang';
console.log(money.flag.value);//静态字段直接用use获取类

Java.choose('com.xiaojianbang.app.Money',{
onMatch: function(obj){
obj._name.value = 'ouyuan';//若存在字段名和函数名相同时须在前面加个下划线
obj.num.value = 150000;
},//choose的第二个参数,每遇到一个对象就调用
onComplete: function(){

}//
});//非静态字段用Java.choose
});
}

Hook内部类中的方法,匿名类

在类后面加’$’再加上内部类的类名即可,

匿名类没有名字因此加’$’在加1,即第几个匿名类,以此类推

主动调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function HookTest4(){
Java.perform(function(){
var Utils = Java.use("com.xiaojianbang.app.Utils");

var retval = Utils.test();
console.log(retval);

retval = Utils.test(666);
console.log(retval);

var Money = Java.use("comlxiaojianbang.app.Money");
retval = Utils.text(Money.$new("xiaojianbang",777));//对于对象类的参数用$new来实例化即可
console.log(retval);
});
}

枚举所有的类和所有的方法

使用enumerateLoadedClassesSync

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//同步方式,所有找完后统一处理

function HookTest5(){
Java.perform(function(){
var classes = Java.enumerateLoadedClassesSync();
for(var i = 0;i<classes.length;i++){
if(classes[i].indexof("com.xiaojianbang.app")!=-1){
console.log('classes[i]');
var clazz = Java.use(classes[i]);
var methods = clazz.class.getDeclaredMethods();//getDeclaredMethods可以获取所有方法,getMethods可以获取公开的方法
for(var j = 0;j<methods.length;j++){
console.log(methods[j]);
}
}
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//异步方式,每枚举到一个就进行处理

function HookTest6{
Java.perform(function(){
Java.enumerateLoadedClasses({
onMatch: function(name,handle){
if(name.indexof("com.xiaojianbang.app")!=-1){
console.log(name);
var calzz = Java.use(name);
console.log(clazz);
var methods = clazz.class.getDeclaredMethods();
for(var i = 0;i<methods.length;i++){
console.log(methods[i]);
}
}
},
onComplete: function(){}
});
});
}

Hook类的所有方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function HookTest7(){
function hookAll(md5, methodsName){
for(var k = 0;k<md5[methodsName].overloads.length;k++){
md5[methodsName].overloads[k].implenmentation=function(){
for(car i = 0;i<arguments.length;i++){
console.log(arguments[i]);
}
console.log(methodsName);
return this[methodsName].apple(this,arguments);
}
}
}

Java.perform(function(){
var md5 = Java.use('com.xiaojianbang.app.MD5');
var methods = md5.class.getDeclaredMethods();
for(var j = 0; j<methods.length;j++){
var methodsName = methods[j].getName();//获取方法的名字
console.log(methodsName);
hookAll(md5,methodsName);
}
});
}

Hook动态加载的dex

因为class loader不是默认的,因此直接hook会找不到,在frida中采用枚举class loader的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function HookTest8(){
Java.perform(function(){//安卓7.0以上才可以用
Java.enumerateClassLoaders({
onMatch: function(loader){
try {
if(loader.loadClass("com.xiaojianbang.app.Dynamic")){
Java.classFactory.loader = loader;
var Dynamic = Java.use("com.xiaojianbang.app.Dynamic");
console.log(Dynamic);
Dynamic.sayHello.implementation = function(){
return "jingyi";
}
}
} catch (error) {

}
},
onComplete: function(){}
});
});
}

Hook特殊类型

可以使用java的方法来遍历,但要符合js的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
function HookTest9(){
Java.perform(function(){
var ShufferMap = Java.use("com.xiaojianbang.app.ShufferMap");//console.log(ShufferMap);
ShufferMap.show.implementation = function(map){
console.log(JSON.stringify(map));
map.put('pass','jingyi');
map.put('guanwang','bbs');//put是Java中map类的方法
var retval = this.show(map);
console.log(retval);
return retval;
}
});
}

Hook so层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function hook_so(){
var soAddr = Moudle.findBaseAddress("libtre.so");//so文件的基址
var base64_encode_new = soAddr.add(0x152c+1);//加上想要hook的函数的偏移,但因为时thumb指令需要再加1,arm指令则不用,分辨thumb指令可以通过查看机器码(对于32位的so,option->general->将opcode bytes改为4后就能显示机器码),机器码有两个字节也有四个字节的是thumb指令,都是四个字节的是arm指令,也可以在plt表->got表后查看,有+1的就是thumb指令
Interceptor.attach(base64_encode_new,{//so层用此函数附加,第一个参数为函数地址,第二个则是一个对象,对象中有两个方法
onEnter: function(args){//进入函数时候执行此方法
console.log("args[o]: ",args[0]);
console.log("args[o]: ",hexdump(args[0]);//利用hexdump函数将地址中的值打印,注意传入的参数必须是地址
console.log("args[o]: ",ptr(args[0]).readCString());//对于cstr可以这样打印出来,可以先用dexdump,如果是C语言的字符串再用这个
this.args1 = args[1];
console.log("args[o]: ",args[1]);
console.log("args[o]: ",hexdump(args[1]);
console.log("args[o]: ",args[2]);
},
onLeave: function(retval){//离开时执行
console.log("retval: ",retval);
console.log("retval: ",hexdump(retval));
console.log("retval: ",ptr(this.args1).readCString());//hook so层时某个参数的地址和返回值的地址一样,这是c语言中常用的方法,可以通过这样的方法来打印,即先记录地址,离开再打印
}
});
}

算法转发

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# -*- coding: UTF-8 -*-

from fastapi import FastAPI
import uvicorn
import frida


jsCode = """

function hookTest(username, passward){
var result;
Java.perform(function(){

var time = new Date().getTime();
time = '1597582774344';

var string = Java.use('java.lang.String');
var signData = string.$new('equtype=ANDROID&loginImei=Android352689082129358&timeStamp=' +
time + '&userPwd=' + passward + '&username=' + username + '&key=sdlkjsdljf0j2fsjk');

var Utils = Java.use('com.dodonew.online.util.Utils');
var sign = Utils.md5(signData).toUpperCase();
console.log('sign: ', sign);

var encryptData = '{"equtype":"ANDROID","loginImei":"Android352689082129358","sign":"'+
sign +'","timeStamp":"'+ time +'","userPwd":"' + passward + '","username":"' + username + '"}';

var RequestUtil = Java.use('com.dodonew.online.http.RequestUtil');
var Encrypt = RequestUtil.encodeDesMap(encryptData, '65102933', '32028092');
console.log('Encrypt: ', Encrypt);
result = Encrypt;
});
return result;
}
rpc.exports = {
xiaojianbang: hookTest
};

""";

#调用frida脚本
process = frida.get_device_manager().add_remote_device('192.168.1.11:27042').attach("com.dodonew.online")
script = process.create_script(jsCode)
print('[*] Running 小肩膀')
script.load()

app = FastAPI()

@app.get("/get") #注意这里url上没有定义参数
async def getEchoApi(item_id, item_user, item_pass):
#fastapi会聪明的发现它不是URL参数,然后自动将他识别为param参数
#RPC远程调用
result = script.exports.xiaojianbang(item_user, item_pass)
return {"item_id": item_id, "item_retval": result}

if __name__ == '__main__':
uvicorn.run(app, port = 8080)

Objection的安装与使用

1
pip install objection -i https://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn

注入进程

1
objection -g com.xiaojianbang.app explore
1
objection -N -h 192.168.1.3 -p 9999 -g com.xiaojianbang.app explore//以网络的方式连接,启动前注入则在后面加 --start--command "android hooking watch class 'com.xiaojianbang.app.MD5'"

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
android hooking search  classes <name>//枚举类,name为类名

android hooking list class_methods com.xiaojianbang.app.MD5//列出类的所有方法

android hooking watch class com.xiaojianbang.app.MD5//hook类的所有方法

jobs list//查看hook的类
jobs kill xxjobid

android hooking watch class_methods com.xiaojianbang.app.MD5.md5_1 --dump--args --dump--return --dump--backtrack//hook方法的参数,返回,调用堆栈,会hook所有的重载,如果要hook其中的某一个函数则需要加上“参数类型”


android heap execute 地址 getinfo//主动调用方法

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 3049155267@qq.com

💰

×

Help us with donation