文章目录
Frida简介
Frida是一款基于Python + JavaScript 的hook框架,本质是一种动态插桩技术。可以用于Android、Windows、iOS等各大平台,其执行脚本基于Python或者Node.js写成,而注入代码用JavaScript写成,所以有必要了解一些这些语言的语法。本文主要讲述了Android上Frida框架的使用。
环境搭建
python安装与虚拟环境配置
使用Frida需要Python 3环境,首先需要安装python3.7版本,安装完成之后再安装虚拟环境。
安装virtualenvwrapper
pip install virtualenvwrapper-win -i https://pypi.doubanio.com/simple
配置虚拟环境变量->WORKON_HOME
将WORKON_HOME添加到用户变量,可以修改虚拟环境的存储路径
创建虚拟环境
mkvirtualenv --python=C:\Python37\python.exe FridaHook
进入虚拟环境
workon FridaHook
建立好虚拟环境以后,所有的修改都是针对虚拟环境
Frida安装
pip install frida -i https://pypi.mirrors.ustc.edu.cn/simple/
pip install frida-tools -i https://pypi.mirrors.ustc.edu.cn/simple/
使用pip命令可以直接安装frida,但是速度会比较慢。坐下来喝杯茶慢慢等就行了。
(FridaHook) C:\Users\87321\Desktop>frida --version
15.1.4
(FridaHook) C:\Users\87321\Desktop>
安装完成之后可以查看版本表示安装成功
配置代码提示
安装vscode,配置vscode语言中文简体。打开"vscode" , 按快捷键"Ctrl+Shift+P",在顶部搜索框中输入"configure language"
安装nodejs,新建一个文件夹,在目录下执行下面这条命令
npm i @types/frida-gum
执行完成之后在文件夹中新建js脚本
此时就会出现相关的代码提示
Server环境配置
https://github.com/frida/frida/releases
下载Frida的Server端->frida-server-15.1.4-android-arm64.xz
,下载完成之后解压。然后push到手机的data/local/tmp
目录下,和IDA的动态调试有点类似
adb push .\frida-server-15.1.4-android-arm64 /data/local/tmp/frida
然后修改权限
# chmod 777 frida
直接运行frida服务
./frida
开启端口转发
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
PC端输入
frida-ps -U
如果能显示进程列表说明环境搭建完成
注意这条命令需要在python虚拟环境中执行
安装低版本Frida
由于Frida的稳定性较差,我用Android6.0的系统安装Frida最新版本出现了问题。解决方案是安装低版本的Frida,用下面的命令即可。
pip install frida==12.8.0
pip install frida-tools==5.3.0
pip install objection==1.8.4
Frida Hook
执行HOOK
首先来说一下如何执行hook代码
- 启动时hook,CMD输入
frida -U -f com.test.apk -l test.js --no-pause
将 com.test.apk 换成你手机里安装好的任意apk包名
- 启动apk后 hook
frida -U -f com.test.apk --no-pause
%load test.js
看到这个提示说明hook成功
Java层hook
Frida的Java层重要函数
- 使用java平台—>
Java.perform(function () {}
- 获取Java类 —>
Java.use(className)
- 当我们获取到Java类之后,我们直接通过
<wrapper>.<method>.implementations =function() {}
的方式来hook wrapper类的method方法,不管是实例方法还是静态方法都可以
Hook普通方法
被hook的代码如下;
package com.example.hookdemo01;
public class Student {
static public int Add(int a,int b)
{
return a+b;
}
}
接着编写Hook代码
function main()
{
//使用java平台
Java.perform(
function() {
//获取java类
var student=Java.use("com.example.hookdemo01.Student");
//hook Add方法(重写Add方法)
student.Add.implementation=function(a,b)
{
//修改参数
a=123;
b=456;
//调用原来的函数
var res = this.Add(a,b);
//输出结果
console.log(a,b,res);
return res;
}
}
);
}
setImmediate(main)
实际效果:
接着我们执行hook,可以看到Frida成功打印出了我们修改后的参数
重载方法
修改一下目标代码,新增三个重载函数,接着编写hook代码
//hook重载方法
function hookTest1()
{
//获取java类
var student=Java.use("com.example.hookdemo01.Student");
//hook test
student.test.overload('int').implementation=function(a)
{
//修改参数
a=123;
//调用原来的函数
var res = this.test(a);
//输出结果
console.log(a,res);
return res;
}
}
hook重载函数和hook普通方法有所区别,需要在方法名后调用overload()
函数,并在括号内传入需要hook的重载函数的参数。
实际效果:
执行hook,同样可以对函数参数进行修改。
//hook所有重载函数
function hookTest2()
{
//获取java类
var student=Java.use("com.example.hookdemo01.Student");
//重载方法的个数
var overlength=student.test.overloads.length;
//循环hook所有重载方法
for(var i=0;i<overlength;i++)
{
student.test.overloads[i].implementation=function()
{
//打印参数个数
console.log(arguments.length);
return this.test.apply(this,arguments);
}
}
}
同时,我们也可以用overloads
来对所有的重载函数进行hook,每一个重载函数只需要用arguments.length
来进行区分即可。
构造方法
再次修改代码,添加一个构造函数,接着编写hook代码
//hook构造函数
function hookTest3()
{
//获取java类
var student=Java.use("com.example.hookdemo01.Student");
student.$init.implementation=function(name,age)
{
//输出参数
console.log(name,age);
//调用原函数
this.$init(name,age);
//调用构造函数
//student.$new("guishou",888);
}
}
hook构造函数需要用$init
来代替函数名,另外,可以用$new
方法来调用构造函数,并且获取一个新的对象
修改类字段
修改代码,添加一个静态字段和一个私有字段,接着编写Hook代码
//修改类字段
function hookTest4()
{
//获取java类
var student=Java.use("com.example.hookdemo01.Student");
//修改静态字段
student.nickname.value="GuiShouFlags";
console.log(student.nickname.value);
//修改非静态字段
Java.choose("com.example.hookdemo01.Student",{
//每遍历一个对象都会调用onMatch
onMatch:function(obj)
{
//修改每个对象的字段
obj.number.value=999;
console.log(obj.number.value);
//字段名和函数名相同需要加下划线
//obj._number.value=999;
},
//遍历完成后调用onComplete
onComplete:function()
{
}
});
}
修改静态字段的方法较为简单,直接用类名点出字段名再加上value
值即可直接修改,修改非静态字段需要调用choose函数,传入类名和回调函数进行修改
修改后效果如图
hook内部类和匿名类
修改目标代码
添加一个内部类,然后hook内部类中的innerPrint
//hook内部类
function hookTest5()
{
//获取java类
var inner=Java.use("com.example.hookdemo01.Student$innerClass");
student.innerPrint.implementation=function()
{
//其他操作相同
}
}
hook内部类需要在获取java类时,在类名后加上$
符号连接内部类,这个完整的类名可以在smali代码中看到。匿名类的hook方法和内部类一样,可以通过查看smali代码的类名来hook。
枚举所有类和方法
代码如下:
function hookTest6()
{
//枚举已经加载的类 异步方式
Java.enumerateLoadedClasses({
//每枚举一个类调用一次
onMatch:function(name,handler)
{
//对类名进行过滤
if(name.indexOf("com.example.hookdemo01")!=-1)
{
//输出类名
console.log(name);
//根据类名获取java类
var clz=Java.use(name);
//获取类的所有方法
var methods=clz.class.getDeclaredMethods();
//循环输出所有方法
for(var i=0;i<methods.length();i++)
{
console.log(methods[i]);
}
}
},
//枚举完成以后调用
onComplete:function()
{
}
});
//枚举已经加载的类 同步方式
var classes=Java.enumerateClassLoadersSync();
for(var i=0;i<methods.classes();i++)
{
if(classes[i].indexOf("com.example.hookdemo01")!=-1)
{
console.log(classes[i]);
//枚举方法同上...
}
}
}
在Frida中枚举所有已加载的需要用到enumerateLoadedClasses
函数,在获取到所有类之后,再通过Java.use
获取类,接着通过Java中的getDeclaredMethods
函数,获取所有的函数名称,接着循环输出即可
hook动态加载的DexClass
对于一些使用DexClassLoader动态加载dex文件的apk来说,直接使用Java.user
必然是找不到当前的类,因为当前apk的ClassLoader并不存在这个类。想要hook动态加载的类需要先解决ClassLoader的问题。
function hookTest7()
{
//枚举ClassLoder
Java.enumerateClassLoaders({
onMatch:function(loader)
{
try
{
//如果能找到需要hook的类
if(loader.loadClass("com.example.hookdemo01.Student"))
{
//替换当前的ClassLoader
Java.ClassFactory.loader=loader;
//实现hook代码
var inner=Java.use("com.example.hookdemo01.Student$innerClass");
}
} catch (error)
{
}
},
onComplete:function()
{
}
});
}
对于动态加载的类,Frida提供了enumerateClassLoaders
函数来枚举ClassLoder,当枚举的ClassLoder中有需要Hook的类时,可以修改loader为当前的ClassLoader,然后再进行hook即可。
Native层hook
hook有导出函数
//hook有导出函数
function hookTest8()
{
//so名称
var so_name="libnative-lib.so";
//要Hook的函数名
var fun_name="test";
//通过名称找到导出函数
var add_fun=Module.findExportByName(so_name,fun_name);
Interceptor.attach(add_fun,{
//在hook函数之前执行
onEnter:function(args)
{
//args[0],arg[1]
console.log("hook enter");
},
//在hook函数之后执行
onLeave:function(retval)
{
console.log("hook leaver");
}
});
}
hook有导出函数直接通过导出函数名称找到函数地址即可进行hook
hook无导出函数
//hook无导出函数
function hookTest9()
{
//so名称
var so_name="libnative-lib.so";
//要Hook的函数偏移
var fun_off=0x7078;
//加载到内存后,函数地址=so地址+函数偏移
var so_add=Module.findBaseAddress(so_name);
var add_func=parseInt(so_add,16)+fun_off;
var ptr_fun=new NativePointer(add_func);
Interceptor.attach(ptr_fun,{
//在hook函数之前执行
onEnter:function(args)
{
console.log("hook enter");
},
//在hook函数之后执行
onLeave:function(retval)
{
console.log("hook leaver");
}
});
}
未导出的函数我们需要手动的计算出函数地址,然后将其转化成一个NativePointer的对象然后进行hook操作