frida使用总结(持续更新)

最近在学习frida的使用,写个文档记录下…

1 常用命令

1.1 启动app

1.1.1 setImmediate

立即执行

setImmediate(function () { 
    console.log("[*] Starting script");
    Java.perform(function () {
        hook()
    })
})

1.1.2 setTimeout

延迟执行,因为有一些dex文件还来不及load进来,需要过一两秒,比如这段代码延迟2秒执行

setImmediate(function () { 
    console.log("[*] Starting script");
    Java.perform(function () {
        hook()
    })
}, 2000)

1.1.3 frida命令启动app

当然要进入adb shell把frida server启动了先,这里不写了

立即启动

frida -U -f cn.demo.login -l .\hookMutilpleFunction.js --no-pause

-f 表示需要启动的app包名
-l 表示frida脚本


有时候某些dex文件还没被加载,需要延迟启动,可以把--no-pause去掉
frida -U -f cn.demo.login -l .\hookMutilpleFunction.js

然后用%resume启动app
在这里插入图片描述

1.1.4 python脚本启动

方法一:直接读取js文件

import time
import frida

apk_name = 'cn.demo.login'

device = frida.get_usb_device()
pid = device.spawn([apk_name])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
with open("frida.js",'r',encoding='UTF-8') as f:
    script = session.create_script(f.read())
script.load()
input()

方法二:js以文本形式

import time
import frida

apk_name = 'cn.demo.login'

jscode = """
    Java.perform(function(){  
        console.log('do something')
    });
"""

device = frida.get_usb_device()
pid = device.spawn([apk_name])
device.resume(pid)
time.sleep(1)
session = device.attach(pid)
script = session.create_script(jscode)
script.load()
input()

1.2 查看模拟器所有进程

注意:这个是基于window平台的,linux平台把findstr改成grep即可

1.2.1 查看当前进程

frida-ps -Ua

1.2.2 搜索指定进程

frida-ps -Ua | findstr login

1.3 查看当前的top activitivy

通常用于查看当前正在运行的app

adb shell dumpsys activity top | findstr ACTIVITY

2 frida基础

2.1 frida的main函数

Java层的Hook都是从Java.perform开始的,相当于main()方法,下面的js语法都是需要写在这个里面的。
所有关于frida的操作必须在这个函数里面,否则会报错

Java.perform(function(){
    //在这个编写frida逻辑
})

2.2 查找指定类的实例

用于查找运行堆中指定类的实例,获得实例后可以调用实例的函数。声明为:Java.choose(className,callbacks)

Java.choose("cn.demo.login.ui.login.LoginActivity",{
    onMatch:function(){
        //onMatch回调会在找到类的实例后调用,也就是说内存中有多少实例,就会调用多少次
    },
    onComplete:function(){
        //onComplete回调会在所有onMatch完成后调用
    }
})

2.3 查看虚拟机是否启动

一般用于检查当前进程的java虚拟机是否启动,防止报错

if (!Java.available) {
	console.log('java虚拟机未加载!')
	return
}

2.4 注册一个自定义的类

用于注册一个自定义的类到内存里

var obj = Java.registerClass({
    name:"top.zyzling.User", //类的全限定名.必传
    superClass:'xxx', //父类的全限定类名,可选
    implements:"xxx", //该类实现的接口全限定名,可选
    fields:{属性名:"类型"}, //该类的属性集,可选
    methods:{} //该类的方法集,可选
})

官方的例子

//获取目标进程的SomeBaseClass类
var SomeBaseClass = Java.use('com.example.SomeBaseClass');
//获取目标进程的X509TrustManager类
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');

var MyWeirdTrustManager = Java.registerClass({
  //注册一个类是进程中的MyWeirdTrustManager类
  name: 'com.example.MyWeirdTrustManager',
  //父类是SomeBaseClass类
  superClass: SomeBaseClass,
  //实现了MyWeirdTrustManager接口类
  implements: [X509TrustManager],
  //类中的属性
  fields: {
    description: 'java.lang.String',
    limit: 'int',
  },
  //定义的方法
  methods: {
    //类的构造函数
    $init: function () {
      console.log('Constructor called');
    },
    //X509TrustManager接口中方法之一,该方法作用是检查客户端的证书
    checkClientTrusted: function (chain, authType) {
      console.log('checkClientTrusted');
    },
    //该方法检查服务器的证书,不信任时。在这里通过自己实现该方法,可以使之信任我们指定的任何证书。在实现该方法时,也可以简单的不做任何处理,即一个空的函数体,由于不会抛出异常,它就会信任任何证书。
    checkServerTrusted: [{
      //返回值类型
      returnType: 'void',
      //参数列表
      argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String'],
      //实现方法
      implementation: function (chain, authType) {
         //输出
        console.log('checkServerTrusted A');
      }
    }, {
      returnType: 'java.util.List',
      argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.lang.String'],
      implementation: function (chain, authType, host) {
        console.log('checkServerTrusted B');
        //返回null会信任所有证书
        return null;
      }
    }],
    // 返回受信任的X509证书数组。
    getAcceptedIssuers: function () {
      console.log('getAcceptedIssuers');
      return [];
    },
  }
});

2.5 类操作

比如业务里有这一段java类

package cn.demo.login.data.model;

public class Student {
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String Say(){
        return "My name is" + name + ", age " + Integer.toString(age);
    }
    // 静态方法
    public static String Info(){
        return "This is a student class!!!";
    }
}

2.5.1 获取类

注意:是获取类,相当于class.forName()不是获取类实例!
比如,获取类

Java.perform(function () {
	// 获取类
    var stu_cls = Java.use('cn.demo.login.data.model.Student');
})

2.5.2 初始化类

Java.perform(function () {
	// 获取类
    var stu_cls = Java.use('cn.demo.login.data.model.Student');
    // 初始化类
    var stu = stu_cls.$new("世界第一打怪物", 22);
    console.log(stu)
})

2.5.3 调用类的方法

2.5.3.1 静态方法

非静态方法直接use,然后调用就可以了

Java.perform(function () {
    // 获取类
    var stu_cls = Java.use('cn.demo.login.data.model.Student');
    // 调用类的方法
    var str = stu_cls .Info()
    console.log(str)
})
2.5.3.2 非静态方法

非静态的方法调用需要先初始化类实例

Java.perform(function () {
    // 获取类
    var stu_cls = Java.use('cn.demo.login.data.model.Student');
    // 初始化类
    var stu = stu_cls.$new("世界第一打怪物", 22);
    // 调用类的方法
    var str = stu.Say()
    console.log(str)
})

2.5.4 获取类变量的值

静态变量和非静态变量类似,这里只展示非静态变量的获取
不管变量范围是啥,public or private, 都可以获取

Java.perform(function () {
    // 获取类
    var stu_cls = Java.use('cn.demo.login.data.model.Student');
    // 初始化类
    var stu = stu_cls.$new("世界第一打怪物", 22);
    // 获取变量的值
    var name = stu.name.value
    console.log(name)
})

2.5.5 更改变量的值

Java.perform(function () {
    // 获取类
    var stu_cls = Java.use('cn.demo.login.data.model.Student');
    // 初始化类
    var stu = stu_cls.$new("世界第一打怪物", 22);
    // 调用类的方法
    console.log('原值:', stu.name.value)
    stu.name.value = '你爸爸'
    console.log('现值:', stu.name.value)
})

3 hook

下面还是以Student类为例

3.1 hook静态/非静态方法

3.1.1 hook没重载

Java.perform(function () {
    console.log('ok! hook好了')
    Java.use('cn.demo.login.data.model.Student').Say.implementation = function(){
        var new_str = '我有一只小毛驴,我从来也不骑'
        // 调用原方法的结果
        var origina_str = this.Say()
        console.log(origina_str)
        // 更改原有的逻辑
        return new_str
    }
})

3.1.2 hook重载

我们改造下Student类,给Say方法增加个重载,如下

package cn.demo.login.data.model;

public class Student {
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String Say(){
        return "My name is" + name + ", age " + Integer.toString(age);
    }
    public String Say(String str){
        return str + ", My name is" + name + ", age " + Integer.toString(age);
    }
    public static String Info(){
        return "This is a student class!!!";
    }
}

这时hook就要小心了,要加上overload(参数类型)

Java.perform(function () {
    console.log('ok! hook好了')
    Java.use('cn.demo.login.data.model.Student').Say.overload('java.lang.String').implementation = function(str){
        var new_str = '这是新的字符串'
        // 调用原方法的结果
        var origina_str = this.Say(str)
        console.log(origina_str)
        // 更改原有的逻辑
        return new_str
    }
})

常用java参数类型如下,注意对比java和js类型的不同

类型全名类型缩写
booleanZ
byteB
charC
doubleD
floatF
intI
longJ
shortS
Stringjava.lang.String
数组表示一般是“[类型缩写”,比如“[]int”, 缩写为“[I”

3.1.3 通用hook方法

有时候想通过更改配置文件的方式来hook,而不是改代码,可以使用如下通用的hook代码,通过指定类名,方法名以及参数即可(为什么不需要指定返回值,因为方法的重载是不需要靠他的!)

// 根据overload获取参数字符串
function getTargetArgumentsByOverload(overload) {
    var curArguments = "(";
    overload.argumentTypes.forEach(function (type) {
        curArguments += type.className + ",";
    });
    if (curArguments.length > 1) {
        curArguments = curArguments.substr(0, curArguments.length - 1);
    }
    curArguments += ")";
    return curArguments
}
// hook函数
function hook(targetClass, targetMethod, targetArguments) {
    // 替换掉所有的空格, 防止写配置的时候不小心
    targetClass = targetClass.replace(/\s+/g, "")
    targetMethod = targetMethod.replace(/\s+/g, "")
    targetArguments = targetArguments.replace(/\s+/g, "")
    // 获取参数数组
    var targetArgumentsArr = targetArguments.substr(1, targetArguments.length - 2).split(',')
    // targetClassMethod
    var targetClassMethod = targetClass + ' ' + targetMethod + targetArguments
    //目标类
    var hook = Java.use(targetClass);
    //获取所有重载
    var overloads = hook[targetMethod].overloads
    //重载次数
    var overloadCount = overloads.length;
    //打印日志:追踪的方法有多少个重载
    console.log("Tracing " + targetClass + '.' + targetMethod + " [" + overloadCount + " overload(s)]");
    //每个重载都进入一次
    for (var i = 0; i < overloadCount; i++) {
        //获取参数类型字符串
        var overload = overloads[i]
        var curArguments = getTargetArgumentsByOverload(overload)
        //参数不符合预期, continue!
        if (curArguments != targetArguments) continue
        hook[targetMethod].overloads[i].implementation = function () {
            console.warn("\n*** entered " + targetClassMethod);

            //打印调用栈,帮助调试,当然可能会信息量很大,不需要是注释掉它
            // Java.perform(function () {
            //     var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());
            //     console.log("\nBacktrace:\n" + bt);
            // });

            // 特定参数处理
            for (var j = 0; j < arguments.length; j++) {
                // 处理StringString
                if (targetArgumentsArr[j] == 'java.lang.String') {
                    // handle Stirng
                }
                else if(targetArgumentsArr[j] == '[B'){
                    // handler []byte
                }
                // other....
            }

            // 获取并打印原来函数的返回值
            var retval = this[targetMethod].apply(this, arguments); 
            console.log(retval)
            return retval;
        }
    }
}

然后想hook什么方法,只需要在main函数里指定hook函数的三个参数即可!

Java.perform(function () {
    console.log('go hooking!')
    hook('cn.demo.login.data.model.Student', 'Say', '(java.lang.String)')
})

3.1.4 模糊hook方法

模糊hook方法,根据方法名hook掉所有重载

// 模糊hook方法,根据方法名hook掉所有重载
function blurry_hook(targetClassMethod) {
    //替换掉所有的空格, 防止写配置的时候不小心
    targetClassMethod = targetClassMethod.replace(/\s+/g, "");
    var r_point = targetClassMethod.length
    for (; r_point >= 0 && targetClassMethod[r_point] != '.'; r_point--) { }
    var targetClass = targetClassMethod.substring(0, r_point)
    var targetMethod = targetClassMethod.substring(r_point + 1)
    console.log(targetClass, targetMethod)
    // 静态构造函数在frida层hook不到,暂时不处理
    if (targetMethod.indexOf("<clinit>") != -1) {
        // "静态构造函数在frida层hook不到,暂时不处理";
        return
    }
    // targetClassMethod
    var found = false
    var targetClassMethod = targetClass + " " + targetMethod;
    try {
        //目标类
        var hook = Java.use(targetClass);
        //获取所有重载
        var overloads = hook[targetMethod].overloads;
        //重载次数
        var overloadCount = overloads.length;
        // console.log('overloadCount: ', overloadCount)
        //每个重载都进入一次
        for (var i = 0; i < overloadCount; i++) {
            //获取参数类型字符串
            var overload = overloads[i]
            overload.implementation = function () {
                var output = ''
                try {
                    output += "\n\n*** entered " + targetClassMethod + '\n';
                    var stack_info = printStack(false)
                    output += stack_info
                    output += '=============================Val=======================\n'
                    for (var j = 0; j < overload.argumentTypes.length; j++) {
                        //处理参数
                        // 包括了分享内容 || 判断hashcode是否在set里面
                        var argumentsType = overload.argumentTypes[j].className
                        var argumentVal = transform(argumentsType, arguments[j])
                        argumentVal = tuomin(argumentsType, argumentVal)
                        output += 'argumentsType: ' + argumentsType + '  argumentVal: ' + argumentVal + '\n'
                        // argumentVal = tuomin(argumentsType, argumentVal)
                        arguments[j] = argumentVal
                    }
                    output += 'targetReturn: ' + overload.returnType.className + ' '
                    var returnValStr = transform(overload.returnType.className, returnVal)
                    output += 'returnVal: ' + returnValStr + '\n'
                    output += '===========================================================\n'
                    console.log(output)
                    found = true
                    // 处理好参数后,在传给方法,并返回返回值
                    var returnVal = this[targetMethod].apply(this, arguments)
                    return returnVal
                } catch (err) {
                    console.log(targetMethod, ' -> err: ', err, ' output: ', output)
                }
            }
        }
    } catch (err) {
        console.log(targetMethod, ' -> err: ', err)
    }
}

3.2 hook构造方法

hook构造方法其实就是调用$init(注意构造参数),hook重载的构造方法跟hook普通重载方法一样,这里只展示hook普通构造方法。还是那个Student类!

Java.perform(function () {
    console.log('go hooking!')
    Java.use('cn.demo.login.data.model.Student').$init.overload('java.lang.String', 'int').implementation = function(name, age){
        console.log('整你!')
        var new_name = '世界超级打怪物'
        var new_age = 100
        this.$init(new_name, new_age)
    }
})

3.3 hook内部类

下面以hook内部类的方法为例,hook其他东西类似

3.3.1 hook有名内部类的方法

给Student类增加一个常规内部类Pencil

package cn.demo.login.data.model;

public class Student {
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String Say(){
        return "My name is " + name + ", age " + Integer.toString(age);
    }
    public String Say(String str){
        return str + ", My name is" + name + ", age " + Integer.toString(age);
    }
    public static String Info(){
        return "This is a student class!!!";
    }
    // 常规内部
    public class Pencil{
        public String GetPencil(){
            return "I have a red pencil";
        }
    }
}

那么怎么hook它呢?

Java.perform(function () {
    console.log('go hooking!')
    Java.use('cn.demo.login.data.model.Student$Pencil').GetPencil.implementation = function(){
        var new_str = "I don't have pencil!"
        return new_str
    }
})

3.3.2 hook匿名内部类的方法

匿名内部类一般都是“类名$数字”的形式,数字一般按照在代码中定义的顺序依次递增,比如我在代码不同位置定位三个Skill匿名内部类

3.3.2.1 通过定义的顺序
package cn.demo.login.data.model;

abstract class Skill {
    public abstract String Sing();
}

public class Student {
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String Say(){
        //匿名内部类 1
        Skill skill = new Skill() {
            @Override
            public String Sing() {
                return "I can fly";
            }
        };
        return "My name is " + name + ", age " + Integer.toString(age) + "";
    }
    public String Say(String str){
        //匿名内部类 2
        Skill skill = new Skill() {
            @Override
            public String Sing() {
                return "I can swim";
            }
        };
        return str + ", My name is" + name + ", age " + Integer.toString(age);
    }
    public String Sing(){
        //匿名内部类 3
        Skill skill = new Skill() {
            @Override
            public String Sing() {
                return "I can sing";
            }
        };
        return skill.Sing();
    }
}

这也是hook的时候要往下数,比如我要hook第三个,那我就类名$3,具体代码如下

Java.use('cn.demo.login.data.model.Student$3').Sing.implementation = function(){
    var new_str = "I can't sing!!!"
    return new_str
}
3.3.2.2 通过smali代码

可以如果代码比较多,很难数下去,可以尝试从smali代码中找到答案

  • 1 AS点击File->Setting->Plugins, 然后搜索下载java2smali插件
    在这里插入图片描述
    · 2 然后选择Build->Compile to Smali,就可以得到smali代码
    在这里插入图片描述
    · 然后找到找hook的匿名函数所在的方法,比如我要找sing方法里面的类
    在这里插入图片描述
    很明显,这个匿名的skill类叫做Student$3,然后hook它就好了。

4 其他常用方法

4.1 打印调用栈

打印调用栈其实就是故意制造一个Exception,然后打印Exception的调用栈

// 打印调用栈
function printStack() {
    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, "\r\n");
            console.log("=============================Stack strat=======================");
            console.log(replaceStr);
            console.log("=============================Stack end=======================\r\n");
            Exception.$dispose();
        }
    });
}

4.2 python与js通信

有时候一些操作在js下比较难完成(比如加密,甚至需要机器学习的操作),那我们可以发送到电脑的python脚本中处理,具体看代码注释。

python启动脚本,功能包括启动app和处理脚本

import frida
import base64

# app包名
apk_name = 'cn.demo.login'

# 消息处理函数
def message_handler(message, payload):
    # 输出参数
    print('message: ', message)
    print('payload: ', payload)
    print('加密前的data:', message['payload']['data'])
    # 加密处理
    encode_str = str(base64.b64encode(message['payload']['data'].encode()))
    script.post({"data": encode_str})

# 获取启动app的必要信息
device = frida.get_usb_device()
pid = device.spawn([apk_name])
device.resume(pid)
session = device.attach(pid)
# 读取js文件
with open("rpc.js",'r',encoding='UTF-8') as f:
    script = session.create_script(f.read())
# 注册消息处理函数
script.on("message", message_handler)  
# 启动app
script.load()

#####################
input() 

js的frida逻辑

function fun() {
    // 发送加密前的数据
    send({'data': '我有一只小毛驴,我从来也不骑' }); // 发送加密前的数据
    // 接受数据
    // 这里wait()就是阻塞,等待python返回结果
    // 如果不加wait(),就相当于异步处理
    recv(function (received_data) {
        console.log('加密后的数据: ', received_data['data'])
    }).wait(); 
    //收到数据之后的操作
}

// setTimeout setImmediate
setTimeout(function () { //prevent timeout
    console.log("[*] Starting script");
    Java.perform(function () {
        fun()
    })
}, 2000)

4.3 RPC通信

如果想写个工具,能随时随地地方便的调用js脚本,可以考虑使用js的RPC通信,使用很简单,js写好函数然后RPC暴露出来即可!
js

// 加
function add(a, b){
    return a+b
}
// 减
function sub(a, b){
    return a-b
}
// 乘
function multi(a, b){
    return a*b
}
// 除
function divide(a, b){
    return a/b
}

// 定义RPC
// 格式: 外部接口名:内部函数名
// 注意: 导出名不可以有大写字母或者下划线
rpc.exports = {
    rpcadd: add,
    rpcsub: sub,
    rpcmulti: multi,
    rpcdivide: divide
};

python

import frida
import base64

# app包名
apk_name = 'cn.demo.login'

# 获取启动app的必要信息
device = frida.get_usb_device()
pid = device.spawn([apk_name])
device.resume(pid)
session = device.attach(pid)
# 读取js文件
with open("rpc.js", 'r', encoding='UTF-8') as f:
    script = session.create_script(f.read())
# 启动app
script.load()
# 调用API
while True:
    choice = input('输入你的选择,1:加法, 2:减法,3:乘法,4:除法,5:退出,然后输入两个数组,最后输出结果')

    def input_num():
        a = int(input('a='))
        b = int(input('b='))
        return a, b
    if choice == '1':
        a, b = input_num()
        print(script.exports.rpcadd(a, b))
    elif choice == '2':
        a, b = input_num()
        print(script.exports.rpcsub(a, b))
    elif choice == '3':
        a, b = input_num()
        print(script.exports.rpcmulti(a, b))
    elif choice == '4':
        a, b = input_num()
        print(script.exports.rpcdivide(a, b))
    elif choice == '5':
        break
    print('-----------------------------------------------------')

#####################
input()

5 Natice层的hook(TODO)

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值