这篇文章上次修改于 1139 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
Frida启动方式
手机环境配置
建议先获取系统root权限,在开启远程adb服务
adb root
adb disable-verity
adb reboot
adb root
adb remount
# 第一种方式
adb shell 'echo "service.adb.tcp.port=5555" >>/system/build.prop'
# 第二种方式
adb tcpip 5555
开启frida server进程,指定启动的端口
adb shell
nohup /data/mobile-deploy/hluda-server-14.2.13-android-arm -l 0.0.0.0:27042 > /sdcard/frida-server.log 2>&1 &
nohup /data/mobile-deploy/hluda-server-14.2.13-android-arm64 -l 0.0.0.0:27042 > /sdcard/frida-server.log 2>&1 &
# 电脑上执行,不执行的话电脑连接不上frida server
adb forward tcp:27042 tcp:27042
Spawn方式启动
spawn
方式是frida重新打开要分析的应用.
# USB连接设备时的连接方式
frida -U -l LOAD_SCRIPT -f PACKAGE_NAME --no-pause
# 远程连接时方式
frida -H IP:PORT -l LOAD_SCRIPT -f PACKAGE_NAME --no-pause
Attach方式启动
attach
方式是frida注入已经打开的应用进程中
# USB连接设备时的连接方式
frida -U PACKAGE_NAME -l LOAD_SCRIPT --no-pause
# 远程连接时方式
frida -H IP:PORT PACKAGE_NAME -l LOAD_SCRIPT --no-pause
延迟启动
第一种方式、spawn
启动时不加 —-no-pause
参数
# USB连接设备时的连接方式
frida -U -l LOAD_SCRIPT -f spawn_FILE
# 远程连接时方式
frida -H IP:PORT -l LOAD_SCRIPT -f spawn_FILE
第二种方式、延迟启动
setTimeout(main, 5000)
Frida基本使用
Java层的Hook都是从Java.perform开始的。
function hook_Java() {
Java.perform(function () {
//code
});
}
Frida hook类
frida的启动命令
frida -H 192.168.0.100:27042 com.android.settings -l hook_Java.js --no-pause
枚举所有的类
function enumerate_LoadedClasses() {
Java.perform(function () {
// 方法一
let clazz_list = Java.enumerateLoadedClassesSync();
for (const clazz of clazz_list) {
console.log("[*] found instance of '" + clazz.toString() + "'");
}
// 方法二
Java.enumerateLoadedClasses({
onMatch: function (args) {
console.log("[*] found instance of '" + args.toString() + "'");
}, onComplete: function () {
console.log("found done");
}
});
});
}
枚举类所有方法
function enumerate_methods() {
Java.perform(function (){
let ViewCompat = Java.use("android.support.v4.view.ViewCompat");
let method_list = ViewCompat.class.getDeclaredMethods()
for (const clazz of method_list) {
console.log("[*] found method of '"+clazz.toString()+"'");
}
});
}
hook构造函数
$init
构造方法;$new
创建对象;$dispose()
手动销毁对象.
hook的目标:
package com.tlamb96.kgbmessenger.b;
/* loaded from: classes.dex */
public class a {
/* renamed from: a reason: collision with root package name */
private int f448a;
private String b;
private String c;
private boolean d;
// 构造方法
public a(int i, String str, String str2, boolean z) {
this.f448a = i;
this.b = str;
this.c = str2;
this.d = z;
}
public String a() {
return this.b;
}
public int b() {
return this.f448a;
}
public String c() {
return this.c;
}
public boolean d() {
return this.d;
}
}
如果目标类的构造方法就一个,hook目标类的构造方法如下:
//
function hook_constructor(clazz) {
// hook构造函数
Java.perform(function () {
let clazz_name = Java.use(clazz);
// let className = "com.tlamb96.kgbmessenger.b.a";
let a = Java.use(className);
a.$init.implementation = function (i, str, str2, z) {
console.log("a.$init: ", i, str, str2, z);
//print_stack();
}
});
}
如果构造函数有重载
构造方法
// 构造方法
public a(int i, String str, String str2, boolean z) {
this.f448a = i;
this.b = str;
this.c = str2;
this.d = z;
}
public a(int i, String str) {
this.f448a = i;
this.b = str;
}
hood代码
function hook_constructor(clazz) {
// hook构造函数
Java.perform(function () {
let clazz_name = Java.use(clazz);
// let className = "com.tlamb96.kgbmessenger.b.a";
let a = Java.use(className);
a.$init.overload("int","java.lang.String").implementation = function (i, str) {
console.log("a.$init: ", i, str);
}
});
}
修改方法参数和返回值
// 方法
public String a() {
return this.b;
}
public int b() {
return this.f448a;
}
public String c() {
return this.c;
}
public String c(String str) {
return str;
}
public boolean d() {
return this.d;
}
与hook构造方法类似,不同的是将$init
替换为具体的方法即可.比如我们hook c
方法。因为c
方法有重载,我们需要使用overload()
方法,在方法内部指定要hook方法具体参数。
function hook_method(clazz_name) {
Java.perform(function (){
let clazz = Java.use(clazz_name);
clazz_name.c.overload('java.lang.String').implementation = function (str) {
let result = this.c(str);
console.log('clazz_name.c', str);
return result;
};
});
}
修改参数
function hook_method(clazz_name) {
Java.perform(function (){
let clazz = Java.use(clazz_name);
clazz_name.c.overload('java.lang.String').implementation = function (str) {
console.log("before", str);
str = "hello";
console.log("after", str);
let result = this.c(str);
console.log('clazz_name.c', result);
return result;
};
});
}
修改返回值
function hook_method(clazz_name) {
Java.perform(function (){
let clazz = Java.use(clazz_name);
clazz_name.c.overload('java.lang.String').implementation = function (str) {
let result = this.c(str);
console.log('clazz_name.c', result);
result = "我自己改的值";
return result;
};
});
}
修改类成员变量值
静态成员变量直接使用Java.use
;
修改非静态类成员变量时使用Java.choose(className,callbacks)
.
设置和函数名相同的成员变量需要在属性前面 加一个 下划线_
用于查找堆中指定类的实例。获得实例后可以调用实例的函数。声明为:Java.choose(className,callbacks)
demo如下
Java.choose("com.android.bluetooth",{
onMatch:function(){
//onMatch回调会在找到类的实例后调用,也就是说内存中有多少实例,就会调用多少次
},
onComplete:function(){
//onComplete回调会在所有onMatch完成后调用
}
})
要hook的Java代码如下
package cool.find.testnative2;
import android.util.Log;
public class SecondActivy {
private static boolean static_bool_var = false;
private boolean bool_var = false;
private boolean same_name_bool_var = false;
private void same_name_bool_var() {
Log.d("Frida", static_bool_var + " " + this.bool_var + " " + this.same_name_bool_var);
}
}
function hook_variable_member() {
Java.perform(function () {
var SecondActivy = Java.use("cool.find.testnative2.SecondActivy");
console.log("SecondActivy.static_bool_var:", SecondActivy.static_bool_var.value);
SecondActivy.static_bool_var.value = true; //设置静态成员变量的值
console.log("SecondActivy.static_bool_var:", FridaActivity3.static_bool_var.value);
// 非静态成员变量则使用如下方法, 也可以修改静态成员变量
Java.choose("cool.find.testnative2.SecondActivy", {
onMatch: function (instance) {
console.log("SecondActivy.bool_var:", instance.bool_var.value);
instance.bool_var.value = true;
console.log("SecondActivy.bool_var:", instance.bool_var.value);
console.log("SecondActivy._same_name_bool_var:", instance._same_name_bool_var.value);
instance._same_name_bool_var.value = true;
console.log("SecondActivy._same_name_bool_var:", instance._same_name_bool_var.value);
}, onComplete: function () {
}
})
});
}
hook动态加载的类
具体的例子可以参考看雪的帖子
https://bbs.pediy.com/thread-258772.htm
https://bbs.pediy.com/thread-246767.htm
function hook_dyn_dex() {
Java.perform(function () {
Java.enumerateClassLoaders({
"onMatch": function (loader) {
if (loader.toString().startsWith("com.tencent.shadow.core.loader.classloaders.PluginClassLoader")) {
Java.classFactory.loader = loader; // 将当前class factory中的loader指定为我们需要的
}
},
"onComplete": function () {
console.log("success");
}
});
// 此处需要使用Java.classFactory.use
var videoController = Java.classFactory.use("com.iqiyi.plugin.widget.dkplayer.controller.VideoController");
videoController.setVip.implementation = function () {
console.log("hook setVip");
this.setVip(true);
};
});
}
加载外部dex文件
生成dex
//将class文件打包成jar包
jar -cvf result.jar *.class
// 将jar包转换成dex文件
~/Library/Android/sdk/build-tools/28.0.3/dx --dex --output=result.dex result.jar
function load_external_dex() {
// 打开 dex 文件
let dex = Java.openClassFile('/data/local/tmp/ddex.dex');
Java.perform(function () {
// 加载 dex
dex.load();
// 进行hook
Java.use('com.tlamb96.kgbmessenger.LoginActivity').j.implementation = function () {
return true;
};
})
}
打印Java层调用栈
function print_stack_trace() {
let Thread = Java.use("java.lang.Thread");
let th = Java.cast(Thread.currentThread(), Thread);
let stack = th.getStackTrace();
let e = null;
for (let i = 0; i < stack.length; i++) {
console.warn("\t" + stack[i].getClassName() + "." + stack[i].getMethodName() + "(" + stack[i].getFileName() + ")");
}
}
复杂Java类型的打印、转换
import exp from "constants";
export namespace Utils {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
// Use a lookup table to find the index.
const lookup = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256);
for (let i = 0; i < chars.length; i++) {
lookup[chars.charCodeAt(i)] = i;
}
export function strToBase64(str: string) {
return new Buffer(str).toString("base64");
}
export function base64ToStr(base64_str: string) {
let result = Buffer.from(base64_str, 'base64');
return result.toString('utf-8');
}
export function strToHexStr(str: string) {
let res = new Buffer(str);
return res.toString('hex');
}
export function hexStrToStr(str: string) {
let res = new Buffer(str, 'hex');
return res.toString('utf-8');
}
export function strToHexArrayBuffer(input: string) {
if (typeof input !== 'string') {
throw new TypeError('Expected input to be a string')
}
if ((input.length % 2) !== 0) {
throw new RangeError('Expected string to be an even number of characters')
}
const view = new Uint8Array(input.length / 2)
for (let i = 0; i < input.length; i += 2) {
view[i / 2] = parseInt(input.substring(i, i + 2), 16)
}
return view.buffer;
}
export function ArrayBufferToStr(arrayBuffer: ArrayBuffer) {
if (typeof arrayBuffer !== 'object' || arrayBuffer === null || typeof arrayBuffer.byteLength !== 'number') {
throw new TypeError('Expected input to be an ArrayBuffer')
}
var view = new Uint8Array(arrayBuffer)
var result = ''
var value
for (var i = 0; i < view.length; i++) {
value = view[i].toString(16)
result += (value.length === 1 ? '0' + value : value)
}
return result
}
export function stringToArrayBuffer(str: string) {
const buffer = new ArrayBuffer(str.length);
const bytes = new Uint8Array(buffer);
str.split('').forEach(function (str, i) {
bytes[i] = str.charCodeAt(0);
});
return buffer;
}
export function arrayBufferToString(buffer: ArrayBuffer) {
// let uint8 = new Uint8Array(buffer);
return Buffer.from(buffer).toString('utf8');
}
export function arrayBufferToBase64Str(arraybuffer: ArrayBuffer): string {
let bytes = new Uint8Array(arraybuffer),
i,
len = bytes.length,
base64 = '';
for (i = 0; i < len; i += 3) {
base64 += chars[bytes[i] >> 2];
base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
base64 += chars[bytes[i + 2] & 63];
}
if (len % 3 === 2) {
base64 = base64.substring(0, base64.length - 1) + '=';
} else if (len % 3 === 1) {
base64 = base64.substring(0, base64.length - 2) + '==';
}
return base64;
}
export function base64StrToArrayBuffer(base64: string): ArrayBuffer {
let bufferLength = base64.length * 0.75,
len = base64.length,
i,
p = 0,
encoded1,
encoded2,
encoded3,
encoded4;
if (base64[base64.length - 1] === '=') {
bufferLength--;
if (base64[base64.length - 2] === '=') {
bufferLength--;
}
}
const arraybuffer = new ArrayBuffer(bufferLength),
bytes = new Uint8Array(arraybuffer);
for (i = 0; i < len; i += 4) {
encoded1 = lookup[base64.charCodeAt(i)];
encoded2 = lookup[base64.charCodeAt(i + 1)];
encoded3 = lookup[base64.charCodeAt(i + 2)];
encoded4 = lookup[base64.charCodeAt(i + 3)];
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
}
return arraybuffer;
}
}
没有评论