frida Android 的简单使用

4,732 阅读2分钟

环境准备

  • 已root的Android环境, 真机虚拟机

配置frida

  1. pip install frida: fridapython 接口, 可以省略, 因为安装 frida-tools 时会自动安装, 但是当 frida 升级时, 为统一 frida-serverfrida 的版本, 需要手动升级 pip install -U frida

  2. pip install frida-tools: frida cli 工具

  3. 下载对应Android系统的 frida-server github.com/frida/frida…

    image.png

    • 使用 adb 连接 Android 设备
      • adb devices / adb connect 192.168.1.12:5555
    • frida-server 文件解压出来并 push 到 Android 设备中:
      • adb push frida-server /data/local/tmp
    • adb shell 下获取 root 权限
      • su
    • 修改 frida-server 权限, 并转移至 /system/bin 路径下
      • chmod a+x frida-server
      • mv /data/local/tmp/frida-server /system/bin, 不转移可能会出现奇怪问题
    • 运行 frida-server
      • /system/bin/frida-server &
    • 端口转发
      • adb forward tcp:27042 tcp:27042
      • 网上也有看到同时转发另一个端口的 adb forward tcp:27043 tcp:27043, 暂时不知道默认含义
  4. 如果出现 无法 connect frida server 相关错误

    • adb forward tcp:27042 tcp:27042
    • 关闭 MagiskHide
  5. 修改默认端口举例

    # adb 
    frida-server -l 0.0.0.0:9999 -D
    
    # pc cmd
    # adb forward tcp:7777 tcp:9999
    adb forward tcp:27042 tcp:9999
    
    # frida-ps -H localhost:7777
    # 如果本地使用默认端口 27042, 但是由于frida的自身机制, 依然需要进行指定, 因为 frida-server 用的不是默认端口
    frida-ps -H localhost:27042    
    

frida cli

  1. 获取远端(Android设备)进程
    • frida-ps -U
  2. 管理远端进程
    • frida-kill -U <process>

adb shell

  1. 获取全部进程
    • ps -A
    • ps -A | grep frida
  2. 杀死进程
    • kill -9 <process>
  3. 杀死frida
    • ps -A | grep frida | awk '{ print "kill -9 " $2} ' | sh
  4. 重启 adb
    • adb kill-server: 重启之后需重新转发端口 adb forward tcp:27042 tcp:27042

直接使用 frida 操作

代码示例

Python API

"""frida"""
import sys

import frida


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


device = frida.get_usb_device()

pid = device.spawn('com.tencent.mm')

device.resume(pid)

session = device.attach(pid)

jscode = open('java.js').read()

# 开启调试
# script = session.create_script(jscode, runtime='v8')
# session.enable_debugger()
script = session.create_script(jscode)

script.on('message', on_message)

print('[*] Running Frida')

script.load()

sys.stdin.read()

JavsScript API

frida 代码提示: github.com/DefinitelyT…

示例1 - hook 静态函数 或 没有重载的方法

Java.perform(function () {
    var CommUrlApi = Java.use('com.tencent.mm.ui.CommUrlApi')
    CommUrlApi.MD5.implementation = function (s) {
        console.log(s)
        let rt = this.MD5(s)
        console.log(rt)
        return rt
    }
})

示例2 - hook StringBuilder

const jStringBuilder = Java.use('java.lang.StringBuilder');
jStringBuilder.toString.implementation = function () {
    let rt = this.toString();
    console.log(rt);
    return rt;
}

示例3 - hook 重载方法

当一个方法有重载, 那么不管你想hook的方法有没有参数, 你都必须指定 override, 否则会报错

{
  'type': 'error',
  'description': "Error: a(): has more than one overload, use .overload(<signature>) to choose from:\n\t.overload()\n\t.overload('java.lang.String')\n\t.overload('java.lang.String', 'java.lang.String')\n\t.overload('java.util.regex.Pattern', 'java.lang.String')\n\t.overload('int', 'java.lang.String')",
  'stack': "Error: a(): has more than one overload, use .overload(<signature>) to choose from:\n\t.overload()\n\t.overload('java.lang.String')\n\t.overload('java.lang.String', 'java.lang.String')\n\t.overload('java.util.regex.Pattern', 'java.lang.String')\n\t.overload('int', 'java.lang.String')\n    at X (frida/node_modules/frida-java-bridge/lib/class-factory.js:563)\n    at K (frida/node_modules/frida-java-bridge/lib/class-factory.js:558)\n    at set (frida/node_modules/frida-java-bridge/lib/class-factory.js:925)\n    at <anonymous> (/script1.js:32)\n    at <anonymous> (frida/node_modules/frida-java-bridge/lib/vm.js:11)\n    at _performPendingVmOps (frida/node_modules/frida-java-bridge/index.js:238)\n    at <anonymous> (frida/node_modules/frida-java-bridge/index.js:213)\n    at <anonymous> (frida/node_modules/frida-java-bridge/lib/vm.js:11)\n    at _performPendingVmOpsWhenReady (frida/node_modules/frida-java-bridge/index.js:232)\n    at perform (frida/node_modules/frida-java-bridge/index.js:192)\n    at <eval> (/script1.js:36)",
  'fileName': 'frida/node_modules/frida-java-bridge/lib/class-factory.js',
  'lineNumber': 563,
  'columnNumber': 1
}

在报错信息中, 可以找到我们 override 所需的参数

Java.use('com.iflytek.ys.common.k.d.a.b').a.overload().implementation = function () {
    let ret = this.a();
    console.log(ret);
    return ret
}

示例4 - hook 静态方法

const jAntiHacker = Java.use('com.iflytek.yousheng.AntiHacker')
jAntiHacker.a.implementation = function (b) {
    let ret = jAntiHacker.a(b);
    console.log(12345);
    console.log(ret);
    return ret;
}

示例5 - apply arguments

const encrypt = Java.use('com.iflytek.readassistant.biz.b.d');
// encrypt.a.overload('com.iflytek.ys.core.g.c.d', 'java.lang.String', '[B', 'boolean', 'java.lang.String', 'com.iflytek.readassistant.biz.b.d$b').implementation = function () {
encrypt.a.overload('java.lang.String', '[B', '[B', 'boolean').implementation = function (str, b1, b2) {
    console.log(str);
    console.log(jString.$new(b1))
    if (b2 == null) {
        console.log('========-=-=-=-=');
    }
    return this.a.apply(this, arguments);
}

示例6 - 打印堆栈

Java.perform(function() {
    console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()))
});

一些有用信息

Java数据类型 对应 Frida

JavaFridaJavaFrida
booleanbooleanboolean[][Z
bytebytebyte[][B
shortshortshort[][S
intintint[][I
longlonglong[][J
floatfloatfloat[][F
doubledoubledouble[][D
java.lang.Stringjava.lang.Stringjava.lang.String[][Ljava.lang.String;

JavaScript脚本判断数据类型

Java.perform(function () {

    const jFrida = Java.use('com.example.file01.Frida');
    const jString = Java.use('java.lang.String');
    const jArrays = Java.use('java.util.Arrays');

    function viewFrida(funName, args) {
        console.log(funName, args.length);
        let ret = eval(`jFrida.${funName}`).apply(jFrida, args);
        args[args.length] = ret;
        for (let i = 0; i <= args.length; i++) {
            let value = args[i];
            if (value === undefined) {
                console.log(i, value);
                continue;
            }
            let type = value.constructor.name;
            switch (type) {
                case 'x':
                    // java object
                    console.log(i, value.$className, value.toString());
                    break;
                case 'Array':
                    // java object array
                    // todo
                    console.log(i, value.$w.$className, jArrays.toString(value));
                    break;
                case 'Boolean':
                    // boolean
                    console.log(i, type, value);
                    break;
                case 'g':
                    // base data type array
                    type = value.$s.typeName;
                    switch (type) {
                        case 'byte':
                            // byte[]
                            console.log(i, `${type}[]`, jString.$new(value), value);
                            break;
                        case 'char':
                            // char[]
                            console.log(i, `${type}[]`, jString.$new(value), value);
                            break;
                        case 'boolean':
                            // boolean[]
                            console.log(i, `${type}[]`, value);
                            break;
                        case 'short':
                            // short[]
                            console.log(i, `${type}[]`, value);
                            break;
                        case 'int':
                            // int[]
                            console.log(i, `${type}[]`, value);
                            break;
                        case 'long':
                            // long[]
                            console.log(i, `${type}[]`, value);
                            break;
                        case 'float':
                            // float[]
                            console.log(i, `${type}[]`, value);
                            break;
                        case 'double':
                            // double[]
                            console.log(i, `${type}[]`, value);
                            break;
                        default:
                            console.log(i, type, value, 'unknown');
                            break;
                    }
                    break;
                case 'Number':
                    // byte, short, int, float, double
                    console.log(i, type, value);
                    break;
                case 'String':
                    // char, String,
                    console.log(i, type, value);
                    break;
                case 'Int64':
                    // char, String,
                    console.log(i, 'long', value);
                    break;
                case 'Object':
                    // char, String,
                    console.log(i, type, value);
                    break;
                default:
                    console.log(i, type, value, 'unknown');
                    break;
            }
        }
        console.log();
        return ret;
    }

    jFrida.Test.overload('com.example.file01.MainActivity', '[Lcom.example.file01.MainActivity;', 'boolean', '[Z', 'byte', '[B', 'char', '[C', 'short', '[S', 'int', '[I', 'long', '[J', 'float', '[F', 'double', '[D', 'java.lang.String', '[Ljava.lang.String;').implementation = function () {
        viewFrida('Test', arguments);
    };
})

hook 技巧

Java.perform(function () {
    const jClass = Java.use('com.iflytek.msc.MSC')
    const jString = Java.use('java.lang.String');
    const jArrays = Java.use('java.util.Arrays');

    function viewFrida(funName, javaClass, args) {
        console.log(funName, args.length);
        let ret = eval(`javaClass.${funName}`).apply(javaClass, args);
        args[args.length] = ret;
        for (let i = 0; i <= args.length; i++) {
            let value = args[i];
            if (value === undefined || value === null) {
                console.log(i, value);
                continue;
            }
            let type = value.constructor.name;
            switch (type) {
                case 'x':
                    // java object
                    console.log(i, value.$className, value.toString());
                    break;
                case 'Array':
                    // java object array
                    // todo
                    console.log(i, value.$w.$className, jArrays.toString(value));
                    break;
                case 'Boolean':
                    // boolean
                    console.log(i, type, value);
                    break;
                case 'g':
                    // base data type array
                    type = value.$s.typeName;
                    switch (type) {
                        case 'byte':
                            // byte[]
                            // console.log(i, `${type}[]`, jString.$new(value), value);
                            console.log(i, `${type}[]`, jString.$new(value, 'GB2312'), value);
                            break;
                        case 'char':
                            // char[]
                            console.log(i, `${type}[]`, jString.$new(value), value);
                            break;
                        case 'boolean':
                            // boolean[]
                            console.log(i, `${type}[]`, value);
                            break;
                        case 'short':
                            // short[]
                            console.log(i, `${type}[]`, value);
                            break;
                        case 'int':
                            // int[]
                            console.log(i, `${type}[]`, value);
                            break;
                        case 'long':
                            // long[]
                            console.log(i, `${type}[]`, value);
                            break;
                        case 'float':
                            // float[]
                            console.log(i, `${type}[]`, value);
                            break;
                        case 'double':
                            // double[]
                            console.log(i, `${type}[]`, value);
                            break;
                        default:
                            console.log(i, type, value, 'unknown');
                            break;
                    }
                    break;
                case 'Number':
                    // byte, short, int, float, double
                    console.log(i, type, value);
                    break;
                case 'String':
                    // char, String,
                    console.log(i, type, value);
                    break;
                case 'Int64':
                    // char, String,
                    console.log(i, 'long', value);
                    break;
                case 'Object':
                    // char, String,
                    console.log(i, type, value);
                    break;
                default:
                    console.log(i, type, value, 'unknown');
                    break;
            }
        }
        console.log();
        return ret;
    }

    function hookMethod(funName, javaClass) {
        console.log('hook method', funName);
        eval(`javaClass.${funName}`).implementation = function () {
            return viewFrida(funName, this, arguments);
        };
    }

    // hook method QTTSSessionEnd
    // hook method QTTSGetParam
    // hook method QTTSAudioGet
    // hook method QTTSTextPut
    // hook method QTTSFini
    // hook method QTTSInit
    // hook method QTTSAudioInfo
    // hook method QTTSSessionBegin
    // jClass.$ownMembers.forEach(function (value) {
    //     if (value.startsWith('QTTS')) {
    //         hookMethod(value, jClass);
    //     }
    // });

    hookMethod('QTTSSessionBegin', jClass);
}    

相关链接

frida.re/

github.com/frida/frida