Python环境,直接打开命令行,执行一波
注意:保持frdia 与frida-server 版本一致
。 不同手机支持的frdia-server 版本不一致;
查看frida 与frida-tools 对应版本:前往
# 最新版
pip install frida-tools
# nexus 6P 手机:
# frida-tools==1.2.2 对应的frida == 12.11.18.
pip install frida-tools==1.2.2
安装完毕以后,因为这一页文档的下半部分用于测试刚装好的库是否可用的话过于麻烦,我们这里就直接使用
frida-ps
查看当前运行的进程, 有输出则说明成功。
端口转发:
将PC端的27042端口收到的数据,转发给到手机中27042端口,
注意: 使用手机必须开启端口转发
// 第一个tcp:电脑端口、第二个tcp:手机端口
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
打开GitHub之后你会发现,这里有很多个不同的版本,应该下载哪一个呢?
根据CPU架构型号选择。
查CPU架构的方法很多,这里介绍一个比较方便快捷的——使用一个名叫Device Info HW的APP。
命名行
adb shell getprop ro.product.cpu.abi
所以这里我需要下载的是x86_64
版本的 frida-server,下载后解压出来一个没后缀
的文件.
然后我们需要将这个文件放入手机中运行起来。先把这个文件的名字改成 frida-server
然后在这个文件所在的目录下打开命令行(Windows下为Shift+鼠标右键,选择“在此处打开CMD/PowerShell”),执行以下命令:
adb root
adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server &"
注意:
如果你的手机和我的一样,直接这么运行会提示权限不足的话,可以先进入
adb shell
,在执行
su
命令获取Root权限后(手机端可能会弹出Root授权提示),再运行
/data/local/tmp/frida-server &
启动 frida-server。
启动后,我们先照惯例来测试一下是否能正常使用了,和前面一样,使用
frida-ps -U
参数,这个参数的意思是让它对USB连接的设备操作,如果不出意外的话,你应该能看到与不加 -U
参数时截然不同的显示。
地址:前往
import frida, sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
print(f"data: {data}")
else:
print(message)
jscode = """
Java.perform(() => {
// Function to hook is defined here
const MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
// Whenever button is clicked
const onClick = MainActivity.onClick;
onClick.implementation = function (v) {
// Show a message to know that the function got called
send('onClick');
// Call the original onClick handler
onClick.call(this, v);
// Set our values after running the original onClick handler
this.m.value = 0;
this.n.value = 1;
// this.cnt.value = 999;
// Log to the console that it's done, and we should have the flag!
console.log('Done:' + JSON.stringify(this.cnt));
};
});
"""
process = frida.get_usb_device().attach('rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()
先看看
Java.perform
,在Frida官方文档的javascript-api页中可以看到,它的用途是确保当前线程已连接到VM用的,所以我们直接照着这么用就行了;
然后看看
Java.use
这个函数,它的用途是获取一个指向某个类的指针,参数中的
com.example.seccon2015.rock_paper_scissors.MainActivity
就是我们需要Hook的那个类;
接着就是真正执行Hook的部分了,这个代码中使用了一个
MainActivity.onClick.implementation
,意思就是Hook前面获取到的类中的onClick
方法,后面跟着的赋值函数的部分,函数的参数为对应要Hook方法的参数,内部执行的部分就是在对应方法被调用时所执行的代码,这里它是先打了一个
onClick
日志,然后调用了原始方法(如果不调用的话原始方法不会被执行),接着它将m、n、cnt(变量具体含义请自行反编译APP后查看代码)的值做了修改,最后,它又打了一个携带着cnt变量值的日志。
最后是一些常规操作,
frida.get_usb_device().attach()
是获取指定APP(参数为包名)当前运行着的进程,
process.create_script(jscode)
、script.load()
是将前面的JS代码注入进去,
script.on('message', on_message)
是设置消息传出时的回调,最后的
sys.stdin.read()
是输出日志用的。
总结:除了JS代码部分,其他的其实只是个壳子,核心的Hook操作逻辑全在JS代码中,我们在使用时一般只改JS代码部分和指定包名的部分就可以了。
python 执行frida 脚本就和普通py脚本一样, 需要特别注意的是Hook
的进程名称。
例:官网案例apk rock_paper_scissors
的Hook 进程为 com.example.seccon2015.rock_paper_scissors
# 官网python 原码
process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()
然而此时,我执行frida-ps -U
获取的进程名称为rock_paper_scissors
. 所以具体的进程名还是需要我们自己去查询确认一下。
import frida, sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
print(f"data: {data}")
else:
print(message)
jscode = """
Java.perform(() => {
const MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
const onClick = MainActivity.onClick;
onClick.implementation = function (v) {
// so something
};
// 多个hook 点
const MainActivity_n = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
const onClick = MainActivity_n.onClick;
onClick.implementation = function (v) {
// so something
};
});
"""
# 获取进程
process = frida.get_usb_device().attach('rock_paper_scissors')
# 创建js脚本(于内存中)
script = process.create_script(jscode)
# 绑定回调函数
script.on('message', on_message)
print('[*] Running CTF')
# 注入脚本
script.load()
# 输出log
sys.stdin.read()
Frida 常用的Hook 方法: 包括Hook 一般函数、重载函数、构造函数、打印堆栈信息、类方法名等。
这里只编写了js代码, 因为上面说的很清楚了, hook的主要逻辑是在js代码中,其他的python代码只是外壳。
// Hook xx.xxx.MainActivity 下的myMethod方法, 打印该方法的执行结果
var MainActivity = Java.use('xx.xxx.MainActivity');
MainActivity.myMethod.implementation = function (args) {
var res = this.myMethod(args);
// console.log('Done:' + JSON.stringify(res));
console.log('Done: ' + res)
return res;
};
重载函数: 一个类中有两个或多个相同的方法名, 但是方法的参数类型或个数不同。在调用时需要根据参数类型及个数来匹配不同的方法。在Hook 时我们通过
overload
关键字来指定具体参数类型
。如果通过implementation
会报错:Error:xx(): has more than one overload
// 案列一
myClass.myMethod.overload("java.lang.String").implementation = function (params1) {
console.log(params1);
};
// 案例二
myClass.myMethod.overload("[B","[B").implementation = function (params1, params2) {
// byte 转string
const p1 = Java.use("java.lang.String").$new(params1);
const p2 = Java.use("java.lang.String").$new(params2);
};
// 案列三
myClass.myMethod.overload("android.contexe.Context","boolean").implementation = function (params1, params2) {
// do someting 做点什么......
};
要hook构造函数,和普通的函数是有区别的,要用
$init
这种形式,并且要return this.$init(arg1,arg2)
调用原始的函数实现
无参构造
//获取要hook的那个类的实例
var userObj=Java.use("com.alex.javahooktarget.MyUser");
userObj.$init.overload().implementation=function(){
console.log("Hook 到 MyUser无参构造函数");
this.$init();//注意这里使用的是当前这个实例化的对象this
}
有参构造
const StringBuilder = Java.use("java/lang.StringBuilder");
StringBuilder.$init.overload('java.lang.String').implementation = function (args) {
var partial = "";
// 返回对象
var result = this.$init(args);
if(args !== null){
partial = args.toString().slice(0, 10);
}
console.log('new StringBuilder("' + partial +'");');
return result;
}
StringBuilder.$init.overload('[B', 'int').implementation = function (args1, args2){
// do something
}
成员方法指没有前缀
static
的类修饰符的方法, Hook 时需要对类实例化$new()
.
Java.perform(
function (){
// 实列化对象
var obj = Java.use("com.qianfanyun.base.entity.InitIndexEntity").$new();
if (obj != undefined){
obj.setDefault_national_prefix.implementation = function (param){
console.log("-------> str: " + param);
obj.setDefault_national_prefix(param)
}
}
});
内部类是指一个类定义在里一个类里面或方法里面,
注意: 对于内部类,通过 类名$
内部类名
如: Class$
inner$
inner 原理上是可以有无限嵌套的内部类的。
匿名类一般是$
1或者$2
、$3
…
public class MyClass extends BaseMyClass {
public String getText() {
return "这是当前类属性";
}
private static class InnerClasses {
public static boolean check1() {
return false;
}
public static boolean check2() {
return false;
}
}
}
Hook 代码:
var myClass = Java.use("com.xx.xxx.MyClass");
myClass.getText = function(){
return "这是Hook后返回的数据";
}
var InnerClasses Java.use("com.xx.xxx.MyClass$InnerClasses");
InnerClasses.check1.implementation = function (){
return true
}
InnerClasses.check2.implementation = function () {
return true
}
通过
so名
和方法名
查找方法位置,创建NativePointer
指针对象,然后通过Interceptor.attach()
拦截器函数。
注意: 一般不需要添加setTimeout
定时器,不过有时添加等待时间可能会避免掉一些异常。
function hookNativeFun(callback, funName, moduleName) {
var time = 1000;
var address = Module.findExportByName(moduleName, funName);
if (address == null) {
setTimeout(hookNativeFun, time, callback, funName, moduleName);
}
else {
console.log(funName + "hook result")
var nativePointer = new NativePointer(address);
Interceptor.attach(nativePointer, callback);
}
}
Java.perform(
function(){
Java.choose("com.xx.xx.xx", {
onMatch: function (x) {
ba.signInternal.implementation = function(a1,a2) {
result= x.method(a1,a2);
},
onComplete: function () {
}
})
}
)
Java.perform(function(){
Java.enumerateLoadedClasses({
onMatch: function(className) {
send(className);},
onComplete:function(){
send("done");
}
});
});
function getMethodName() {
var ret;
Java.perform(function() {
var Thread = Java.use("java.lang.Thread")
ret = Thread.currentThread().getStackTrace()[2].getMethodName();
});
return ret;
}
function showStacks() {
Java.perform(function() {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
});
}
function enumMethods(targetClass) {
var ret;
Java.perform(function() {
var hook = Java.use(targetClass);
var ret = hook.class.getDeclaredMethods();
ret.forEach(function(s) {
console.log(s);
})
})
return ret;
}
var current_application = Java.use('android.app.ActivityThread').currentApplication();
var context = current_application.getApplicationContext();
Java.perform(function(){
console.log("1 start hook");
try {
var URL = Java.use('java.net.URL')
URL.openConnection.overload('java.net.Proxy').implementation = function (arg1){
return this.openConnection()
}
} catch (e) {
console.log('' + e)
}
try {
var Builder = Java.use('okhttp3.OkHttpClient$Builder')
var mybuilder = Builder.$new()
Builder.proxy.overload('java.net.Proxy').implementation = function (arg1) {
return mybuilder
}
} catch (e) {
console.log('' + e)
}
var hashMap = Java.use("java.util.HashMap");
hashMap.put.implementation = function (a, b) {
// 不行可换 a.equals("username")
if (a=="username") {
console.log("hashMap.put: ", a, b);
printStacks();
}
return this.put(a, b);
}
var arrayList = Java.use("java.util.ArrayList");
arrayList.add.overload('java.lang.Object').implementation = function (a) {
if (a == "username") {
console.log("arrayList.add: ", a);
showStacks(); // 打印堆栈信息
}
//console.log("arrayList.add: ", a);
return this.add(a);
}
arrayList.add.overload('int', 'java.lang.Object').implementation = function (a, b) {
console.log("arrayList.add: ", a, b);
return this.add(a, b);
}
// 判断输入框是否为空
var textUtils = Java.use("android.text.TextUtils");
textUtils.isEmpty.implementation = function (a) {
if (a == "TURJNk1EQTZNREE2TURBNk1EQTZNREE9") {
console.log("textUtils.isEmpty: ", a);
printStacks();
}
//console.log("textUtils.isEmpty: ", a);
return this.isEmpty(a);
}
var base64 = Java.use("android.util.Base64");
base64.encodeToString.overload('[B', 'int').implementation = function (a, b) {
console.log("base64.encodeToString: ", JSON.stringify(a));
var result = this.encodeToString(a, b);
console.log("base64.encodeToString result: ", result)
printStacks();
return result;
}
rpc.exports = {
request_url: function (url) {
return new Promise(function(resolve, reject) {
Java.perform(function () {
var OkHttpClient=Java.use("okhttp3.OkHttpClient");
var Builder=Java.use("okhttp3.Request$Builder");
var client = OkHttpClient.$new()
var request = Builder.$new().get().url(url).build()
var response = client.newCall(request).execute()
resolve(response.body().string())
});
});
}
};
function okhttp3_get(url, headers_str){
let body = null;
Java.perform(() => {
const BuilderClazz = Java.use('okhttp3.Request$Builder');
const OkHttpClientClazz = Java.use('okhttp3.OkHttpClient');
try{
let builder = BuilderClazz.$new();
builder = builder.url(url);
if(headers_str != null && headers_str != ''){
let headers = JSON.parse(headers_str);
for(let key in headers){
builder = builder.addHeader(key, headers[key]);
}
}
let request = builder.build();
if(!okhttp3_client){
okhttp3_client = OkHttpClientClazz.$new();
}
let call = okhttp3_client.newCall(request);
let response = call.execute();
body = response.body().string();
}catch(e){
console.error(e);
}
});
return body;
}
function okhttp3_post(url, headers_str, media_type, body){
let ret = null;
Java.perform(() => {
const BuilderClazz = Java.use('okhttp3.Request$Builder');
const OkHttpClientClazz = Java.use('okhttp3.OkHttpClient');
const MediaTypeClazz = Java.use('okhttp3.MediaType');
const RequestBodyClazz = Java.use('okhttp3.RequestBody');
try{
let builder = BuilderClazz.$new();
builder = builder.url(url);
if(headers_str != null && headers_str != ''){
let headers = JSON.parse(headers_str);
for(let key in headers){
builder = builder.addHeader(key, headers[key]);
}
}
let m_type = MediaTypeClazz.parse(media_type);
let request_body = RequestBodyClazz.create(m_type, body);
builder = builder.post(request_body);
let request = builder.build();
if(!okhttp3_client){
okhttp3_client = OkHttpClientClazz.$new();
}
let call = okhttp3_client.newCall(request);
let response = call.execute();
ret = response.body().string();
}catch(e){
console.error(e);
}
});
return ret;
}