在逆向过程中,Frida是非常常用的Hook工具,这个工具在日常使用的过程中,有很多通用方法又借鉴了部分网友的代码,我在这里整理记录一下,方便以后直接查阅及使用,部分函数使用的时候,可能需要稍微修改一下,本文记录是Android的方法,不涉及其他的,主要是搬迁的一下参考文章的片段:
Frida官方文档
Android逆向之旅—Hook神器家族的Frida工具使用详解
看雪 Frida官方手册 - JavaScript API(篇一)
看雪 Frida官方手册 - JavaScript API(篇二)
Js中几种String Byte转换方法
这个网上都已经有教程了,因为Frida大致原理是手机端安装一个server程序,然后把手机端的端口转到PC端,PC端写python脚本进行通信,而python脚本中需要hook的代码采用javascript语言。所以这么看来我们首先需要安装PC端的python环境,这个没难度直接安装python即可,然后开始安装frida了,直接运行命令:pip install frida
安装frida-tools:pip install frida-tools
要提前配置好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:7555 (7555为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的命令,如果在检查的时候,发现没有开启,就去做一次端口的转发。
到这里frida 安装配置与手机互通的工作就做完了,接下来就开始在PC端开始编写hook程序进行操作了:
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.
}
})
})
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()
var MainActivity = Java.use('ese.xposedtest.MainActivity');
//外部类 修改返回值
MainActivity.OutClass.implementation = function (arg) {
var ret = this.OutClass(arg);
console.log('Done:' + arg);
return ret;
}
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()
//如果一个类的两个方法具有相同的名称,您需要使用"重载",若不知具体参数,出错会有提示的。
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
}
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");
});
}
}
//拦截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');
const JavaString = Java.use('java.lang.String');
var exampleString1 = JavaString.$new('Hello World, this is an example string in Java.');
console.log('[+] exampleString1: ' + exampleString1);
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");
}
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");
});
}
}
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);
});
});
}
}
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);
//枚举所有已经加载的类
Java.enumerateLoadedClasses({
onMatch: function(aClass) {
//迭代和判断
if (aClass.match(pattern)) {
//做一些更多的判断,适配更多的pattern
var className = aClass.match(/[L]?(.*);?/)[1].replace(///g, ".");
//进入到traceClass里去
traceClass(className);
}
},
onComplete: function() {}
});
Interceptor.attach(Module.findExportByName("xxx.so" , "xxxx"), {
onEnter: function(args) {
send("open(" + Memory.readCString(args[0])+","+args[1]+")");
},
onLeave:function(retval){
}
});
var ah = Java.use("com.ah");
console.log("To Log: " + ah.a.value);
ah.a.value = true;
// 获取当前安卓设备的系统版本
var v = Java.androidVersion;
send('Version:' + v);
// 打印调用堆栈
function printstack()
{
send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
}
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()
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()
function byte2string(array){
var result = "";
for(var i = 0; i < array.length; ++i){
result+= (String.fromCharCode(array[i]));
}
return result;
}
function intTobytes(n) {
var bytes = [];
for (var i = 0; i < 2; i++) {
bytes[i] = n >> (8 - i * 8);
}
return bytes;
}
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
}
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:解决中文乱码(模板)
function ab2str(buf) {
return new Uint16Array(buf)
// encodedString = String.fromCodePoint.apply(null, new Uint16Array(buf));
// // decodedString = encodeURI(encodedString);//没有这一步中文会乱码
// // console.log(decodedString);
// return encodedString
}
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;
}
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是没有办法转成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();
}
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);
}