Frida开发手册

前言

以下是在使用python开发frida脚本过程中收集到的一些用法, 这里将学习的知识点做个归纳总结

Hook函数

Java普通函数

1
2
3
4
5
6
const Context = Java.use('android.content.Context');
Context.checkCallingOrSelfPermission.implementation = function (permission: string) {
// 以下两种写法都可以
// return Context.checkCallingOrSelfPermission.call(this, permission)
return this.checkCallingOrSelfPermission(permission);
}

如果没有同名的重载函数则直接调用函数的implementation来重写即可

Java重载函数

1
2
3
4
const Context = Java.use('android.content.Context');
Context.checkPermission.overload('java.lang.String', 'int', 'int').implementation = function (permission: string, pid: number, uid: number) {
return this.checkPermission(permission, pid, uid);
}

如果需要 hook特定的重载函数则必须要在函数名后通过overload函数来指定参数列表

函数调用

Java构造器重载函数

1
2
let Person = Java.use("com.example.Person");
let person = Person.$new.overload("java.lang.String", "int").call(Person, "Alice", 25);

Java类重载函数

1
2
3
var EncryptUtil = Java.use("xxx.utils.EncryptUtil");
var encryptHMAC = EncryptUtil.encryptHMAC.overload('java.lang.String', 'java.lang.String');
var new_signature = encryptHMAC.call(EncryptUtil,msg_body, tran_id);

数组集合操作

创建Java数组

1
2
3
4
5
6
7
const buffer = Java.array('byte', [1, 2, 3]);
console.log(buffer.length);
for(let i = 0; i < buffer.length; ++i){
let b = buffer[i];
//todo something
}
console.log(result);

创建固定长度的Java数组

1
const buffer = Java.array('byte', new Array(1024).fill(0)); // size of 1024 

修改Java数组

1
2
3
4
const String = Java.use("java.lang.String");
let stringInstance = String.$new("Hello World");
const Arrays = Java.use("java.util.Arrays");
Arrays.fill(array, 0, 1, stringInstance); // equivalent to var_0[0] = stringInstance
1
2
3
const Demo = Java.use("com.test.Demo");
const intarr = Demo.getIntArray();
const newIntArr = Array.from(intarr).concat(888) // append element to array

遍历Java集合

1
2
3
4
5
6
7
const ArrayList = Java.use('java.util.ArrayList');
let list = ArrayList.$new();
let iter = list.iterator();
while(iter.hasNext()) {
let item = iter.next();
//todo something
}

异常处理

捕获Java异常

1
2
3
4
5
6
7
8
9
10
11
const Test = Java.use("com.exception.example.Test");
try {
Test.throwException();
} catch (ex) {
//ex is a handle, you have to cast it.
const Exception = Java.use("java.lang.Trowable");
let item = Java.cast(ex,Exception);
let SpecificException = Java.use(item.$className);
item = Java.cast(ex,SpecificException);
console.log(item.attribute.value);
}

抛出Java异常

1
2
3
4
5
const Activity = Java.use('android.app.Activity');
const Exception = Java.use('java.lang.Exception');
Activity.onResume.implementation = function () {
throw Exception.$new('Oops!');
};

成员变量访问修改

访问修改Java对象成员变量

1
2
3
Class Test {
double f = 200.0d;
}

注意如果要访问或者修改对象的f成员变量则不能直接通过对象.f访问,需要调用.fvalue属性

1
2
3
4
5
const Test = Java.use('com.example.Test');
let test = Test.$new();
console.log(`before=${test.f.value}`);
example.f.value = 100;
console.log(`after=${test.f.value}`);

访问修改具有同名函数的Java对象成员变量

1
2
3
4
Class Test {
double f = 200.0d;
public void f() {...}
}

如果出现同名的函数会导致frida无法区分是要访问成员变量还是函数遇到这种情况需要使用_加以区分

1
2
3
4
5
const Test = Java.use('com.example.Test');
let test = Test.$new();
console.log(`before=${test._f.value}`);
example.f.value = 100;
console.log(`after=${test._f.value}`);

检查Java对象类型

暂时没有查到最佳实践,不过可以通过检查对象的$className实现

1
2
3
4
5
6
const className = event.$className;
if (className === "android.view.KeyEvent") {

} else if (className === "android.view.MotionEvent") {

}

访问c++类或结构体成员变量

不存在虚函数的类或结构体

不包含虚函数可以直接计算成员变量的偏移量

1
2
3
4
5
6
7
8
9
10
11
12
class MotionEvent : public InputEvent {
public:
// Fields
int32_t mAction;
int32_t mFlags;
// Other fields and methods...

protected:
// Protected fields
int32_t mEdgeFlags;
// Other protected fields...
};
1
2
3
4
const NativePointer event = new NativePointer(0x9081723891);
const mAction = event.readS32();
const mFlags = event.add(4).readS32();
const mEdgeFlags = event.add(8).readS32();

存在虚函数的类或结构体

包含虚函数的话类成员的第一个是虚函数表指针vptr因此需要将指针的偏移量考虑进去

1
2
3
4
5
6
7
8
9
10
11
12
class InputEvent {
public:
virtual ~InputEvent() { }
virtual int32_t getType() const = 0;
int32_t mId;
};

class MotionEvent : public InputEvent {
public:
int32_t mAction;
int32_t mFlags;
};
1
2
3
4
const NativePointer event = new NativePointer(0x9081723891);
const mId = event.add(Process.pointerSize).readS32();
const mAction = event.add(Process.pointerSize).add(4).readS32();
const mFlags = event.add(Process.pointerSize).add(8).readS32();

处理std::vector std::string std::deque

https://github.com/thestr4ng3r/frida-cpp

引用:

  1. C++:虚函数内存布局解析(以 clang 编译器为例)https://www.less-bug.com/posts/cpp-vtable-and-object-memory-layout-clang-example/
  2. Frida: How to read a struct or a struct pointer or a pointer of a struct pointer? https://gist.github.com/schirrmacher/05ad9f1a0ba428e0cc6aeed46036ccd6

实现Java继承类

两种方式推荐第一种更符合书写方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const SomeBaseClass = Java.use('com.example.SomeBaseClass');
const X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
const MyTrustManager = Java.registerClass({
name: 'com.example.MyTrustManager',
implements: [X509TrustManager],
methods: {
checkClientTrusted(chain, authType) {
},
checkServerTrusted(chain, authType) {
},
getAcceptedIssuers() {
return [];
},
}
});

const MyWeirdTrustManager = Java.registerClass({
name: 'com.example.MyWeirdTrustManager',
superClass: SomeBaseClass,
implements: [X509TrustManager],
fields: {
description: 'java.lang.String',
limit: 'int',
},
methods: {
$init() {
console.log('Constructor called');
},
checkClientTrusted(chain, authType) {
console.log('checkClientTrusted');
},
checkServerTrusted: [{
returnType: 'void',
argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String'],
implementation(chain, authType) {
console.log('checkServerTrusted A');
}
}, {
returnType: 'java.util.List',
argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.lang.String'],
implementation(chain, authType, host) {
console.log('checkServerTrusted B');
return null;
}
}],
getAcceptedIssuers() {
console.log('getAcceptedIssuers');
return [];
},
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const myOutputClass = Java.registerClass({
name: 'com.example.MyOutputStream',
superClass: "java.io.ByteArrayOutputStream",
implements: [],
methods: {
write: [{
returnType: 'void',
argumentTypes: ['[B', 'int', 'int'],
implementation: function (b, off, len) {
console.log("MyOutputStream.write(" + JSON.stringify(b) + "," + off + "," + len + ")");
this.$super.write(b, off, len);
}
}, {
returnType: 'void',
argumentTypes: ['int'],
implementation: function (b) {
console.log('MyOutputStream.write(' + b + ")");
this.$super.write(b);
}
}]
}
});
var myInstance = myOutputClass.$new();

获取Android Context

Application

1
2
const ActivityThread = Java.use("android.app.ActivityThread");
let application = ActivityThread.currentApplication();

Activity

1
2
3
4
5
6
const ActivityThread = Java.use("android.app.ActivityThread");
let activityThread = ActivityThread.currentActivityThread();
let mActivities = activityThread.mActivities.value;
const ActivityThread$ActivityClientRecord = Java.use("android.app.ActivityThread$ActivityClientRecord");
let activityClientRecord = Java.cast(mActivities.mArray.value[1], ActivityThread$ActivityClientRecord).activity.value;
let activity = activityClientRecord.activity.value;

逆向工程进阶

Objection 手术刀级别的神器

具体用法参考:https://github.com/sensepost/objection

打印方法调用堆栈

1
2
3
4
5
6
const Log = Java.use('android.util.Log');
const Exception = Java.use('java.lang.Exception');
console.log(Log.getStackTraceString(Exception.$new()));

// 一行解决
console.log(Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Exception').$new()));

查找类加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const searchClass = "com.example.Main";
const classLoaders = Java.enumerateClassLoadersSync();
for (let i = 0; i < classLoaders.length; i++) {
const loader = classLoaders[i]
try {
loader.findClass(searchClass);
Java.classFactory.loader = loader;
console.log("found class loader:\n" + loader)
break;
} catch (error) {
if (error.message.includes("ClassNotFoundException")) {
console.log("\n You are trying to load encrypted class, trying next loader");
}
}
}

解决方案

Hook不生效

尝试禁止优化,相关信息:https://github.com/frida/frida/issues/1395

1
Java.deoptimizeEverything();

无法调用Java对象函数

先确认javascript的映射对象是否存在这个函数,以下几种方法都可以

1
Object.keys(obj).forEach(key => console.log(key));
1
2
3
for (let key in obj) {
console.log(key);
}
1
Object.getOwnPropertyNames(obj.__proto__).join('\n\t')

这里存在两种情况

  • 不存在函数名

    一般对象来自于泛型返回值比如ArrayList这些被自动转型成Object类型强制转换一下实际类型即可

    1
    2
    3
    let networkInterfaces = ...
    const NetworkInterface = Java.use('java.net.NetworkInterface');
    networkInterfaces = Java.cast(networkInterfaces, NetworkInterface);
  • 存在函数名

    这种情况一般出现在对象有继承但又未重写该函数的情况,frida调用函数跟java调用函数实际上还是有区别的不能直接调用子类未重写的函数必须强制转换成父类类型才可以调用

    1
    2
    3
    4
    const Key = Java.use("java.security.Key");
    const Certificate = Java.use("java.security.cert.Certificate");
    const publicKey = Java.cast(leafCert, Certificate).getPublicKey();
    const algorithm = Java.cast(publicKey, Key).getAlgorithm();

    还有种奇怪的写法之前用过先记录一下,也可以试试

    1
    2
    const Enumeration = Java.use('java.util.Enumeration');
    Enumeration.nextElement.call(networkInterfaces);

如果还不行的话别着急有绝招,借助Java反射调用

1
2
const getPublicKey = leaf.getClass().getMethod("getPublicKey", []);
let publicKey = getPublicKey.invoke(leaf, null);

操作Java long类型数据存在精度问题

应将从Java层获取的long类型数据转成string然后放如BigInt类型中

1
2
3
const Long = Java.use('java.lang.Long');
let l = Long.parseLong(-5476376641664023184);
let newL = BigInt(l.toString())

更多的用法

https://github.com/iddoeldor/frida-snippets/blob/master/README.md

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2015-2024 Kaisar
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信