frida 实用代码片段

记录一些可复用的frida脚本

dump so

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
/*
如何使用
设置一个倒计时后dump指定的so文件,timeout单位是ms,默认为3000ms
对应的python段on_message回调
def handleDumpSo(message, data):
with open("dump_"+message['payload'][1], "ab") as f:
f.write(data)
*/
function dump(soName: string, timeout: number = 3000) {
setTimeout(() => {
let libSo = Process.getModuleByName(soName);
let base = libSo.base;
let size = libSo.size;
let sectionRanges = libSo.enumerateRanges("");
for (let i = 0; i < sectionRanges.length; i++) {
console.log(sectionRanges[i].base.sub(base), sectionRanges[i].size, sectionRanges[i].base.add(sectionRanges[i].size).sub(base), sectionRanges[i].protection);
Memory.protect(sectionRanges[i].base, sectionRanges[i].size, 'rwx');
let buffer = sectionRanges[i].base.readByteArray(sectionRanges[i].size);
console.log(`write ${sectionRanges[i].size} bytes sections`);
send(["dumpso", soName], buffer);
if (i + 1 < sectionRanges.length && sectionRanges[i].base.add(sectionRanges[i].size).compare(sectionRanges[i + 1].base) !== 0) {
let gap = Memory.alloc(sectionRanges[i + 1].base.sub(sectionRanges[i].base.add(sectionRanges[i].size)).toUInt32());
let buffer = gap.readByteArray(sectionRanges[i + 1].base.sub(sectionRanges[i].base.add(sectionRanges[i].size)).toUInt32());
console.log(`write ${sectionRanges[i + 1].base.sub(sectionRanges[i].base.add(sectionRanges[i].size)).toUInt32()} bytes gap`);
send(["dumpso", soName], buffer);
}
}
console.log("base: ", base);
console.log("size: ", size);
console.log("base + size: ", base.add(size));
}, timeout);
}
dump("libil2cpp.so");

把内存中完整的so dump下来,包括仅运行时分配空间的段

HookGetStaticMethodID

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
/*
如何使用
这个gadget通过hook libart.so中的getStaticMethodID函数来获取so层获取的dex函数信息
一般直接运行即可,注意本gadget依赖libart.so的加载
配套python解析message的函数
def handleGetStaticMethodID(message):
print("----------------------------------------")
print("JAVA method name: {}".format(message['payload'][1]))
print("JAVA method signature: {}".format(message['payload'][2]))
print("Which file get It: {}".format(message['payload'][3]))


*/
function hookJNIgetStaticMethodID() {
console.log("hook JNIgetStaticMethodID");
let symbols = Module.load("libart.so").enumerateSymbols();
for (let i = 0; i < symbols.length; i++) {
let symbol = symbols[i];
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("GetStaticMethodID") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
console.log(symbol.name);
Interceptor.attach(symbol.address, {
onEnter: function (args) {
var Name = args[2].readUtf8String();
var sig = args[3].readUtf8String();
let whoCallIt = DebugSymbol.fromAddress(this.returnAddress).toString();
send(["getStaticMethodID", Name, sig, whoCallIt]);
}
})
}
}
}
setTimeout(hookJNIgetStaticMethodID, 3000);

HookRegisterNative

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
52
53
54
55
/*
如何使用
这个gadget通过hook libart.so中的RegisterNatives函数来获取JNI注册的native方法信息
一般直接运行即可,注意本gadget依赖libart.so的加载
配套python解析message的函数
def handleResigerNatives(message):
print("----------------------------------------")
print("Native Method in class: {}".format(message['payload'][1]))
print("Native Method name: {}".format(message['payload'][2]))
print("Native Method signature: {}".format(message['payload'][3]))
print("Native Method address: {}".format(message['payload'][4]))
print("Which file register It: {}".format(message['payload'][5]))

*/
Java.perform(() => {
hookJNIregisterNatives();
})
function hookJNIregisterNatives() {
console.log("hook JNIregisterNatives");
let symbols = Process.getModuleByName("libart.so").enumerateSymbols();
for (let i = 0; i < symbols.length; i++) {
let symbol = symbols[i];
//_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0) {
Interceptor.attach(symbol.address, {
onEnter: function (args) {
var jclass = args[1];
let className = Java.vm.getEnv().getClassName(jclass);
var methods = args[2];
var nMethods = args[3].toInt32();
// console.log(nMethods);
// console.log("className:", className);
for (let i = 0; i < nMethods; i++) {
// console.log("i:", i);
let nativeMethodPtr = methods.add(i * Process.pointerSize * 3);
let namePtr = nativeMethodPtr.add(0).readPointer();
let signaturePtr = nativeMethodPtr.add(Process.pointerSize).readPointer();
let methodPtr = nativeMethodPtr.add(Process.pointerSize * 2).readPointer();
let methodName = namePtr.readUtf8String();
let methodSignature = signaturePtr.readUtf8String();
let whoCallIt = DebugSymbol.fromAddress(this.returnAddress).toString();
// console.log(Module.load(DebugSymbol.fromAddress(this.returnAddress).moduleName as string).base)
let methodPtrStr = methodPtr.sub(Module.load(DebugSymbol.fromAddress(this.returnAddress).moduleName as string).base).toString(16);
// console.log("RegisterNatives:", className, methodName, methodSignature, methodPtrStr, whoCallIt);
send(["registerNatives", className, methodName, methodSignature, methodPtrStr, whoCallIt]);
}
}
});
}
}
// console.log("RegisterNatives addr:", addrRegisterNatives);
}
// setImmediate(hookWhenArtLoaded);

traceStack

全量hook脚本,用于追踪执行流
脚本中遇到的函数表可以由这个脚本生成

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
'''
用bn导出函数表和函数地址,方便批量添加frida跟踪调用链
'''
from binaryninja import *


def solve(bv: BinaryView):
names = []
addresses = []
with open("functionDump.txt", "w") as f:
for func in bv.functions:
names.append(func.name)
addresses.append(func.start)
print("var functionNames = [", file=f)
for i in range(len(names)):
if i == len(names) - 1:
print(" \"{}\"".format(names[i]), file=f)
else:
print(" \"{}\",".format(names[i]), file=f)
print("];", file=f)
print("var functionAddresses = [", file=f)
for i in range(len(addresses)):
if i == len(addresses) - 1:
print(" 0x{:x}".format(addresses[i]), file=f)
else:
print(" 0x{:x},".format(addresses[i]), file=f)
print("];", file=f)
print("export finish")
print("total function count: {}".format(len(names)))


solve(bv)

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/*
根据给出的函数表跟踪调用链,函数表由bnExportFunctionInfo.py导出
顺带给出br的跳转目标地址方便分析bn未识别的函数
*/
var soName = "libsec2023.so"

function sendWrapper(msg) {
send(["traceStack", msg]);
}

function startTrace(soName) {
var tid = Process.getCurrentThreadId();
var lib = Process.getModuleByName(soName);
var cnt = 1;
sendWrapper("========================trace started========================");
Stalker.follow(tid, {
events: {
call: false,
ret: false,
exec: false,
block: false,
compile: false
},
transform: (iterator) => {
var insn;
while ((insn = iterator.next()) != null) {
const addr = ptr(insn.address);
// 只 instrument 属于目标 so 的指令
if (addr.compare(lib.base) < 0 || addr.compare(lib.base.add(lib.size)) >= 0) {
iterator.keep();
continue;
}
// if (insn.mnemonic == "br") {
// const regName = insn.operands[0].value as string;
// iterator.putCallout((ctx) => {
// sendWrapper(`[${addr.sub(lib.base)}]` + "[BR] target =" + ptr(ctx[regName]).sub(lib.base));
// });
// }
if (functionAddresses.indexOf(addr.sub(lib.base).toUInt32()) != -1) {
sendWrapper("at " + cnt + " " + functionNames[functionAddresses.indexOf(addr.sub(lib.base).toUInt32())]);
cnt++;
}
iterator.keep();
}
}
})
// console.log("========================trace ended========================");
}

Java.perform(() => {
var dlopen_addr = Module.getExportByName("libdl.so", "dlopen");
var dlopen_ext_addr = Module.getExportByName("libdl.so", "android_dlopen_ext");

Interceptor.attach(dlopen_addr, {
onEnter: function (args) {
var path = args[0].readUtf8String();
if (path?.indexOf(soName) != -1) {
this.getSo = 1;
}
},
onLeave: function (retval) {
if (this.getSo) {
sendWrapper("dlopen: " + soName);
startTrace(soName);
}
}
})
Interceptor.attach(dlopen_ext_addr, {
onEnter: function (args) {
var path = args[0].readUtf8String();
if (path?.indexOf(soName) != -1) {
this.getSo = 1;
}
},
onLeave: function (retval) {
if (this.getSo) {
sendWrapper("dlopen_ext: " + soName);
startTrace(soName);
}
}
})
})

python段解析数据的部分,因为trace的数据量实际上相当大,并且有大量重复,所以采用分批合并输出的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def handleTraceStack(message):
traceLimit = 1000000000
global TraceStacklogs
global startPrintLog
if len(TraceStacklogs) == 0:
TraceStacklogs.append([message['payload'][1], 1])
else:
if message['payload'][1] == TraceStacklogs[-1][0]:
TraceStacklogs[-1][1] += 1
else:
TraceStacklogs.append([message['payload'][1], 1])

def print_log():
global TraceStacklogs
for item, cnt in TraceStacklogs:
print(f"{item} x{cnt}")
TraceStacklogs.clear()
threading.Timer(3.0, print_log).start()
if not startPrintLog:
startPrintLog = True
print_log()

Author

SGSG

Posted on

2025-05-10

Updated on

2025-05-10

Licensed under