【FridaHook整理】Frida安装及Hook安卓常用脚本

1 概述

在逆向过程中,Frida是非常常用的Hook工具,这个工具在日常使用的过程中,有很多通用方法又借鉴了部分网友的代码,我在这里整理记录一下,方便以后直接查阅及使用,部分函数使用的时候,可能需要稍微修改一下,本文记录是Android的方法,不涉及其他的,主要是搬迁的一下参考文章的片段:
Frida官方文档
Android逆向之旅—Hook神器家族的Frida工具使用详解
看雪 Frida官方手册 - JavaScript API(篇一)
看雪 Frida官方手册 - JavaScript API(篇二)
Js中几种String Byte转换方法

2 安装与启动方式

2.1 Frida安装

这个网上都已经有教程了,因为Frida大致原理是手机端安装一个server程序,然后把手机端的端口转到PC端,PC端写python脚本进行通信,而python脚本中需要hook的代码采用javascript语言。所以这么看来我们首先需要安装PC端的python环境,这个没难度直接安装python即可,然后开始安装frida了,直接运行命令:pip install frida 安装frida-tools:pip install frida-tools
【FridaHook整理】Frida安装及Hook安卓常用脚本_第1张图片
要提前配置好python环境变量,不然提示pip命令找不到。安装完成后,再去官网下载对应版本的手机端程序frida-server:https://github.com/frida/frida/releases
注意: 这里一定要把frida-server版本和上面PC端安装的frida版本一致,不然运行报错的。其实这里看到真的实现hook功能的是手机端的frida-server,这个也是开源的大家可以研究他的原理。我们也看到这个工具和IDA是不是很类似,也是把手机端的端口转发到PC端进行通信而已。有了frida-server之后就好办了,直接push到手机目录下,然后修改一下文件的属性即可:

安装firda:pip install frida
安装frida-tools:pip install frida-tools

查找模拟器或者手机的CPU类型:adb shell getprop ro.product.cpu.abi 
frida server根据手机CPU型号下载合适的推送到手机  frida server 下载地址
我下载的是:frida-server-14.0.5-android-x86(需要解压缩成单文件之后推送手机)

连接手机或者mumu模拟器:adb connect 127.0.0.1:75557555为mumu模拟器端口号,其它百度)
将文件push 进手机的指定目录下:adb push frida-server-14.0.5-android-x86 /data/local/tmp/

进入手机端命令:adb shell
多个设备进入某一指定设备:adb -s emulator-5554 shell
切换获取手机的root权限:su
查找文件是否在手机中:cd /data/local/tmp/
查看路径下的文件并看文件的权限:ls -l
拥有root权限更改文件的权限为777:chmod 777 frida-server-14.0.5-android-x86
在手机中启动运行该文件: ./frida-server-14.0.5-android-x86 &

电脑运行检查手机端服务是否开启成功: frida-ps -U
windows运行 端口转发到PC:adb forward tcp:27043 tcp:27043

若文件推送错误,删除办法:

进入手机端命令:adb shell 
切换获取手机的root权限:su
进入系统内指定文件夹:cd /data/local/tmp/
列表显示当前文件夹内容 :ls
删除名字为xxx的文件夹及其里面的所有文件:rm -r xxx
删除文件指定文件xxx :rm xxx

按照上面的命令直接一步步执行就行,因为我之前已经push过,在这里就不在演示了,直接演示开启frida-server的命令,如果在检查的时候,发现没有开启,就去做一次端口的转发。
【FridaHook整理】Frida安装及Hook安卓常用脚本_第2张图片
到这里frida 安装配置与手机互通的工作就做完了,接下来就开始在PC端开始编写hook程序进行操作了:

2.2 使用js脚本启动

 frida -U -l exploit.js -f com.package.name

其中js脚本写法方式如下:

setImmediate(function() {
    console.log("[*] Starting script");

    Java.perform(function() {
      myClass = Java.use("com.package.name.xxx某Activity");
      myClass.implementation = function(v) {
         // do sth.
      }
    })
})

2.3 使用python脚本启动

import frida, sys

jscode = """
Java.perform(function () {
    //这里写要Hook的软件的类名,建议配合jadx和JEB使用
    var Myclass= Java.use('这里填写要Hook的类名');
    //这里写要Hook的类下的方法名Mymethod
    Myclass.Mymethod.implementation = function (str) {
        //这里是输出打印相关的提示语结果
        send('Hook success');
        console.log('string is: ' + str));
    };
});
"""

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


//这里写要Hook的软件的包名
process = frida.get_usb_device().attach('这里写软件的包名')
#pid = device.spawn(["com.android.chrome"])
#session = device.attach(pid)
#device.resume(pid)
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Hook Start Running')
script.load()
sys.stdin.read()

3 Android 常用Hook方法汇总

Hook 一般函数—使用implementation

Hook一般函数使用案例1:
var MainActivity = Java.use('ese.xposedtest.MainActivity');
//外部类 修改返回值
MainActivity.OutClass.implementation = function (arg) {
    var ret = this.OutClass(arg);
    console.log('Done:' + arg);
    return ret;
}
Hook一般函数使用案例2:
import frida, sys

# Hook软件的类名下的方法实现Hook 传入需要的参数 看方法是否有返回值  
bscode = """
Java.perform(
    function () {
        //获取当前安卓设备的系统版本
        var v=Java.androidVersion;
        send('Version:'+v);

        //打印调用堆栈
        function printstack() {
            send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
        }

        //Hook软件包的类名为:com.myclass
        var myclass= Java.use('com.myclass');
        //调用Hook软件包类名下的方法:mymethod
        myclass.mymethod.implementation = function (bytearray) {
            send('com.myclass.mymethod.implementation');
            printstack();
            var ret = this.mymethod(bytearray);
            send("result:"+ret);
            return ret;
        }
    }
);
"""

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

process = frida.get_usb_device().attach('这里是包名') #Hook软件的包名
script = process.create_script(bscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()

Hook 重载函数—使用overload

//如果一个类的两个方法具有相同的名称,您需要使用"重载",若不知具体参数,出错会有提示的。
myClass.myMethod.overload().implementation = function(){
  // do sth
}

myClass.myMethod.overload("[B", "[B").implementation = function(param1, param2) {
  // do sth
}

myClass.myMethod.overload("android.context.Context", "boolean").implementation = function(param1, param2){
  // do sth
}
拦截方法的所有方法重载代码示例1:
function hook_overload_8() {
    if(Java.available) {
        Java.perform(function () {
            console.log("start hook");
            var targetMethod = 'add';
            var targetClass = 'com.roysue.roysueapplication.Ordinary_Class';
            var targetClassMethod = targetClass + '.' + targetMethod;
            //目标类
            var hook = Java.use(targetClass);
            //重载次数
            var overloadCount = hook[targetMethod].overloads.length;
            //打印日志:追踪的方法有多少个重载
            console.log("Tracing " + targetClassMethod + " [" + overloadCount + " overload(s)]");
            //每个重载都进入一次
            for (var i = 0; i < overloadCount; i++) {
                    //hook每一个重载
                    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);
                        });   
                        // 打印参数
                        if (arguments.length) console.log();
                        for (var j = 0; j < arguments.length; j++) {
                            console.log("arg[" + j + "]: " + arguments[j]);
                        }
                        //打印返回值
                        var retval = this[targetMethod].apply(this, arguments); // rare crash (Frida bug?)
                        console.log("nretval: " + retval);
                        console.warn("n*** exiting " + targetClassMethod);
                        return retval;
                    }
                }
            console.log("hook end");
        });
    }
}

Hook 构造函数—使用$init

//拦截java的初始化java.lang.Stringbuilder的重载构造函数,将部分参数写入控制台。
const StringBuilder = Java.use('java.lang.StringBuilder');
//我们需要重载.$init() 代替 .$new()。 .$new() = .alloc() + .init()
StringBuilder.$init.overload('java.lang.String').implementation = function (arg) {
    var partial = "";
    var result = this.$init(arg);
    if (arg !== null) {
         partial = arg.toString().replace('\n', '').slice(0,10);
    }
    // console.log('new StringBuilder(java.lang.String); => ' + result)
    console.log('new StringBuilder("' + partial + '");')
    return result;
}

console.log('[+] new StringBuilder(java.lang.String) hooked');

Hook 生成对象—使用$new

const JavaString = Java.use('java.lang.String');
var exampleString1 = JavaString.$new('Hello World, this is an example string in Java.');

console.log('[+] exampleString1: ' + exampleString1);

Hook 内部类—使用$

案例1:拦截内部类参数代码示例1
var inInnerClass = Java.use('ese.xposedtest.MainActivity$inInnerClass');

inInnerClass.methodInclass.implementation = function(){
    var arg0 = arguments[0];
    var arg1 = arguments[1];
    send("params1: "+ arg0 +" params2: " + arg1);
    return this.formInclass(1,"Frida");
}
案例2:拦截内部类函数代码示例2
function hook_overload_3() {
    if(Java.available) {
        Java.perform(function () {
            console.log("start hook");
            //注意此处类的路径填写更改所分析的路径
            var clz = Java.use('com.roysue.roysueapplication.User$clz');
            if(clz != undefined) {
                //这边也是像正常的函数来hook即可
                clz.toString.implementation = function (){
                    console.log("成功hook clz类");
                    return this.toString();
                }
            } else {
                console.log("clz: undefined");
            }
            console.log("start end");
        });
    }
}

Hook 枚举所有的类及方法

枚举所有的类并定位类代码示例:
setTimeout(function (){
  Java.perform(function (){
    console.log("n[*] enumerating classes...");
    //Java对象的API enumerateLoadedClasses
    Java.enumerateLoadedClasses({
      //该回调函数中的_className参数就是类的名称,每次回调时都会返回一个类的名称
      onMatch: function(_className){
        //在这里将其输出
        console.log("[*] found instance of '"+_className+"'");

        //如果只需要打印出com.roysue包下所有类把这段注释即可,想打印其他的替换掉indexOf中参数即可定位到~
        //if(_className.toString().indexOf("com.roysue")!=-1)
        //{
        //    console.log("[*] found instance of '"+_className+"'");
        //}
      },
      onComplete: function(){
        //会在枚举类结束之后回调一次此函数
        console.log("[*] class enuemration complete");
      }
    });
  });
});
枚举类的所有方法并定位方法代码示例:
function enumMethods(targetClass)
{
    var hook = Java.use(targetClass);
    var ownMethods = hook.class.getDeclaredMethods();
    hook.$dispose;
    return ownMethods;
}

function hook_overload_5() {
    if(Java.available) {
        Java.perform(function () {
           var a = enumMethods("com.roysue.roysueapplication.User$clz")
           a.forEach(function(s) {
                console.log(s);
           });
        });
    }
}

Hook 拦截类的所有方法

拦截类的所有方法代码示例:
function traceClass(targetClass)
{
    //Java.use是新建一个对象哈,大家还记得么?
    var hook = Java.use(targetClass);
    //利用反射的方式,拿到当前类的所有方法
    var methods = hook.class.getDeclaredMethods();
    //建完对象之后记得将对象释放掉哈
    hook.$dispose;
    //将方法名保存到数组中
    var parsedMethods = [];
    methods.forEach(function(method) {
        //通过getName()方法获取函数名称
        parsedMethods.push(method.getName());
    });
    //去掉一些重复的值
    var targets = uniqBy(parsedMethods, JSON.stringify);
    //对数组中所有的方法进行hook
    targets.forEach(function(targetMethod) {
        traceMethod(targetClass + "." + targetMethod);
    });
}
function hook_overload_9() {
    if(Java.available) {
        Java.perform(function () {
            console.log("start hook");
            traceClass("com.roysue.roysueapplication.Ordinary_Class");
            console.log("hook end");
        });
    }
}
s1etImmediate(hook_overload_9);

Hook 拦截Java层类的所有子类

//枚举所有已经加载的类
Java.enumerateLoadedClasses({
    onMatch: function(aClass) {
        //迭代和判断
        if (aClass.match(pattern)) {
            //做一些更多的判断,适配更多的pattern
            var className = aClass.match(/[L]?(.*);?/)[1].replace(///g, ".");
            //进入到traceClass里去
            traceClass(className);
        }
    },
    onComplete: function() {}
});

Hook native 函数

Interceptor.attach(Module.findExportByName("xxx.so" , "xxxx"), {
    onEnter: function(args) {
        send("open(" + Memory.readCString(args[0])+","+args[1]+")");
    },
    onLeave:function(retval){
    }
});

Hook 静态变量(属性)/参考文章中有反射的方法

var ah = Java.use("com.ah");
console.log("To Log: " + ah.a.value);
ah.a.value = true;

Hook 获取设备版本及打印堆栈

// 获取当前安卓设备的系统版本
var v = Java.androidVersion;
send('Version:' + v);

// 打印调用堆栈
function printstack()
{
 send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
}

RPC远程调用Java层函数(获上下文)

rpc导出Java层函数代码示例1:
import codecs
import frida
from time import sleep

# 附加进程名称为:com.roysue.roysueapplication
session = frida.get_remote_device().attach('com.roysue.roysueapplication')

# 这是需要执行的js脚本,rpc需要在js中定义
source = """
    //定义RPC
    rpc.exports = {
        //这里定义了一个给外部调用的方法:sms
        sms: function () {
            var result = "";
            //嵌入HOOK代码
            Java.perform(function () {
                //拿到class类
                var Ordinary_Class = Java.use("com.roysue.roysueapplication.Ordinary_Class");
                //最终rpc的sms方法会返回add(1,3)的结果!
                result = Ordinary_Class.add(1,3);
             });
            return result;
        },
    };
"""

# 创建js脚本
script = session.create_script(source)
script.load()

# 这里可以直接调用java中的函数
rpc = script.exports
# 在这里也就是python下直接通过rpc调用sms()方法
print(rpc.sms())
sleep(1)

session.detach()
rpc导出Java层函数 酷安代码示例2:
import frida, sys, codecs, os

# def adbforward():
#     os.system('adb forward tcp:27042 tcp:27042')
#     os.system('adb forward tcp:27043 tcp:27043')

# 酷安10.5.3版本 Frida_hook_rpc
jscode = """
rpc.exports = {
    //gethello 是自己定义的函数名,str是参数,根据实际传参,不要大写。
    gethello: function(str){
        send('hello');
        var sig = ''
        Java.perform(function(){
                //拿到软件里的context上下文信息  这是一个生命周期之后启动的类,启动后就可看到里面有上下文信息
                var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
                var context = currentApplication.getApplicationContext();
                
                //软件的类名
                var AuthUtils = Java.use('com.coolapk.market.util.AuthUtils');
                //类下面的方法,传入的参数信息string类型 可以模拟传入
                sig = AuthUtils.getAS(context, str);
                send(sig);
        });
        return sig;
    }
}
"""


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


# Hook的软件包名
process = frida.get_usb_device().attach('com.coolapk.market')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
# 回调执行函数,传入模拟字段参数的信息
script.exports.gethello('fd017bd5ed4e942da40e5673')
# sys.stdin.read()

byte转String

function byte2string(array){
    var result = "";
    for(var i = 0; i < array.length; ++i){
        result+= (String.fromCharCode(array[i]));
    }
   return result;
}

int转bytes

function intTobytes(n) {
  var bytes = [];
  for (var i = 0; i < 2; i++) {
    bytes[i] = n >> (8 - i * 8);
 
  }
  return bytes;
}

string转Uint8Array(模板)

function stringToUint8Array(str){
  var arr = [];
  for (var i = 0, j = str.length; i < j; ++i) {
    arr.push(str.charCodeAt(i));
  }
  var tmpUint8Array = new Uint8Array(arr);
  return tmpUint8Array
}

Uint8Array转string(模板)

function Uint8ArrayToString(fileData){
    console.log(fileData)
  var dataString = "";
  for (var i = 0; i < fileData.length; i++) {
    dataString += String.fromCharCode(fileData[i]);
  }
  return dataString
}

ArrayBuffer转String(模板)

ArrayBuffer转String:解决中文乱码(模板)

function ab2str(buf) {
    return new Uint16Array(buf)
    // encodedString = String.fromCodePoint.apply(null, new Uint16Array(buf));
    // // decodedString = encodeURI(encodedString);//没有这一步中文会乱码
    // // console.log(decodedString);
    // return encodedString
}

string转ArrayBuffer(模板)

function str2ab(str) {
    var buf = new ArrayBuffer(str.length * 2); // 每个字符占用2个字节
    var bufView = new Uint16Array(buf);
    for (var i = 0, strLen = str.length; i < strLen; i++) {
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}

ArrayBuffer转16进制字符串(模板)

function byteToHexString(uint8arr) {
    if (!uint8arr) {
        return '';
    }
    var hexStr = '';
    for (var i = 0; i < uint8arr.length; i++) {
        var hex = (uint8arr[i] & 0xff).toString(16);
        hex = (hex.length === 1) ? '0' + hex : hex;
        hexStr += hex;
    }
    return hexStr.toUpperCase();
}

添加时间戳

 console.log(new Date(new Date().getTime())

byte转Hex

这个非常常用,因为有些byte是没有办法转成string的,只能转成hex,用来查看
参考文章

function byteToHexString(uint8arr) {
  if (!uint8arr) {
    return '';
  }
  
  var hexStr = '';
  for (var i = 0; i < uint8arr.length; i++) {
    var hex = (uint8arr[i] & 0xff).toString(16);
    hex = (hex.length === 1) ? '0' + hex : hex;
    hexStr += hex;
  }
  return hexStr.toUpperCase();
}

Hex转byte

function hexStringToByte(str) {
  if (!str) {
    return new Uint8Array();
  }
  
  var a = [];
  for (var i = 0, len = str.length; i < len; i+=2) {
    a.push(parseInt(str.substr(i,2),16));
  }
  
  return new Uint8Array(a);
}

你可能感兴趣的:(Python,抓包逆向,java,android,python,jscript)