感谢

image-20210809134336251

frida env

https://github.com/frida/frida-java-bridge/blob/master/lib/env.js


IDA 判断 Thumb 指令集和 Arm 指令集

  • IDA - Options - General - number of opcode bytes - 设置为 4
  • 此时查看 IDA VIew 中 opcode 的长度, 如果出现 2 个字节和 4 个字节的, 说明为 thumb 指令集
  • 如果都是 4 个字节的, 说明是 arm 指令集;
  • 在 Thumb 指令集下, inline hook 的偏移地址需要进行 +1 操作;

获取所有 JNI 函数函数地址

通过 hook ArtMethod 的 RegisterNative 函数, 可以监控所有的静态注册和动态注册的 JNI 函数的地址;

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
84
85
86
87
88
89
90
91
/*
frida14
仅在Android 8.1下测试成功,其他版本可能需要重新修改适配
原作者: Simp1er
*/
const STD_STRING_SIZE = 3 * Process.pointerSize;

class StdString {
constructor() {
this.handle = Memory.alloc(STD_STRING_SIZE);
}

dispose() {
const [data, isTiny] = this._getData();
if (!isTiny) {
Java.api.$delete(data);
}
}

disposeToString() {
const result = this.toString();
this.dispose();
return result;
}

toString() {
const [data] = this._getData();
return data.readUtf8String();
}

_getData() {
const str = this.handle;
const isTiny = (str.readU8() & 1) === 0;
const data = isTiny ? str.add(1) : str.add(2 * Process.pointerSize).readPointer();
return [data, isTiny];
}
}


function prettyMethod(method_id, withSignature) {
const result = new StdString();
Java.api['art::ArtMethod::PrettyMethod'](result, method_id, withSignature ? 1 : 0);
return result.disposeToString();
}

function readStdString(str) {
if ((str.readU8() & 1) === 1) { // size LSB (=1) indicates if it's a long string
return str.add(2 * Process.pointerSize).readPointer().readUtf8String();
}
return str.add(1).readUtf8String();
}

function attach(addr) {
Interceptor.attach(addr, {
onEnter: function (args) {
this.arg0 = args[0]; // this
},
onLeave: function (retval) {
var modulemap = new ModuleMap()
modulemap.update()
var module = modulemap.find(retval)
// var string = Memory.alloc(0x100)
// ArtMethod_PrettyMethod(string, this.arg0, 1)
if (module != null) {
console.log('<' + module.name + '> method_name =>',
prettyMethod(this.arg0, 1),
',offset=>', ptr(retval).sub(module.base), ',module_name=>', module.name)
} else {
console.log('<anonymous> method_name =>', readStdString(string), ', addr =>', ptr(retval))
}
}
});
}

function hook_RegisterNative() {
var libart = Process.findModuleByName('libart.so')
var symbols = libart.enumerateSymbols()
for (var i = 0; i < symbols.length; i++) {
if (symbols[i].name.indexOf('RegisterNative') > -1 && symbols[i].name.indexOf('ArtMethod') > -1 && symbols[i].name.indexOf('RuntimeCallbacks') < 0) {
//art::RuntimeCallbacks::RegisterNativeMethod(art::ArtMethod*, void const*, void**)
attach(symbols[i].address)
}
}

}

function main() {
hook_RegisterNative()
}

setImmediate(main)

枚举内存中的 so 文件

用于查看目标 module 是否被正常加载, 使用 Process.enumerateModules() 将当前加载的所有 so 文件打印出来

1
2
3
4
5
6
7
8
9
10
function hook_native(){
var modules = Process.enumerateModules();
for (var i in modules){
var module = modules[i];
console.log(module.name);
if (module.name.indexOf("target.so") > -1 ){
console.log(module.base);
}
}
}

获取指定 so 文件的基地址

1
2
3
4
function hook_module() {
var baseAddr = Module.findBaseAddress("libnative-lib.so");
console.log("baseAddr", baseAddr);
}

获取指定 so 文件的函数

通过导出函数名定位 native 方法

1
2
3
4
function hook_func_from_exports(){
var add_c_addr = Module.findExportByName("libnative-lib.so", "add_c");
console.log("add_c_addr is :",add_c_addr);
}

通过 symbols 符号定位 native 方法

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
function find_func_from_symbols() {
var NewStringUTF_addr = null;
var symbols = Process.findModuleByName("libart.so").enumerateSymbols();
for (var i in symbols) {
var symbol = symbols[i];
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0
){
if (symbol.name.indexOf("NewStringUTF") >= 0) {
console.log("find target symbols", symbol.name, "address is ", symbol.address);
NewStringUTF_addr = symbol.address;
}
}
}

console.log("NewStringUTF_addr is ", NewStringUTF_addr);

Interceptor.attach(NewStringUTF_addr, {
onEnter: function (args) {
console.log("args0",args[0])
console.log("args0", args[0], hexdump(args[0]));
console.log("args1", args[1], hexdump(args[1]));
var env = Java.vm.tryGetEnv();
if (env != null) {
// 直接读取 c 里面的 char
console.log("Memory readCstring is :", Memory.readCString(args[1]));
}else{
console.log("get env error");
}
},
onLeave: function (returnResult) {
console.log("result: ", Java.cast(returnResult, Java.use("java.lang.String")));
var env = Java.vm.tryGetEnv();
if (env != null) {
var jstring = env.newStringUtf("修改返回值");
returnResult.replace(ptr(jstring));
}
}
})
}

通过地址偏移 inline-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
function main(){
// get base address of target so;
var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so");
console.log("base module addr ->", libnative_lib_addr);
if (libnative_lib_addr){
var add_addr1 = Module.findExportByName("libnative-lib.so", "_Z5r0addii");
var add_addr2 = libnative_lib_addr.add(0x94B2 + 1); // 32位需要加1
console.log(add_addr1);
console.log(add_addr2);
}

// 主动调用
var add1 = new NativeFunction(add_addr1, "int", ["int", "int"]);
var add2 = new NativeFunction(add_addr2, "int", ["int", "int"]);

console.log("add1 result is ->" + add1(10, 20));
console.log("add2 result is ->" + add2(10, 20));

}

setImmediate(main);

/*
base module addr -> 0xd430b000
0xd43144b3
0xd43144b3
add1 result is ->30
add2 result is ->30
*/

通过 Intercept 拦截器打印 native 方法参数和返回值, 并修改返回值

  • onEnter: 函数(args) : 回调函数, 给定一个参数 args, 用于读取或者写入参数作为 NativePointer 对象的指针;
  • onLeave: 函数(retval) : 回调函数给定一个参数 retval, 该参数是包含原始返回值的 NativePointer 派生对象; 可以调用 retval.replace(1234) 以整数 1234 替换返回值, 或者调用retval.replace(ptr("0x1234")) 以替换为指针;
  • 注意: retval 对象会在 onLeave 调用中回收, 因此不要将其存储在回调之外使用, 如果需要存储包含的值, 需要制作深拷贝, 如 ptr(retval.toString())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function find_func_from_exports() {
var add_c_addr = Module.findExportByName("libnative-lib.so", "add_c");
console.log("add_c_addr is :",add_c_addr);
// 添加拦截器
Interceptor.attach(add_c_addr,{
// 打印入参
onEnter: function (args) {
console.log("add_c called");
console.log("arg1:",args[0].toInt32());
console.log("arg2", args[1].toInt32());
},
// 打印返回值
onLeave: function (returnValue) {
console.log("add_c result is :", returnValue.toInt32());
// 修改返回值
returnValue.replace(100);
}
})
}

通过 Intercept 拦截器替换原方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function frida_Interceptor() {
Java.perform(function () {
//这个c_getSum方法有两个int参数、返回结果为两个参数相加
//这里用NativeFunction函数自己定义了一个c_getSum函数
var add_method = new NativeFunction(Module.findExportByName('libhello.so', 'c_getSum'),
'int',['int','int']);
//输出结果 那结果肯定就是 3
console.log("result:",add_method(1,2));
//这里对原函数的功能进行替换实现
Interceptor.replace(add_method, new NativeCallback(function (a, b) {
//h不论是什么参数都返回123
return 123;
}, 'int', ['int', 'int']));
//再次调用 则返回123
console.log("result:",add_method(1,2));
});
}

inline hook

通俗点说, inline hook就是通过内存地址, 进行 hook;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function inline_hook() {
var libnative_lib_addr = Module.findBaseAddress("libnative-lib.so");
if (libnative_lib_addr) {
console.log("libnative_lib_addr:", libnative_lib_addr);
var addr_101F4 = libnative_lib_addr.add(0x102BC);
console.log("addr_101F4:", addr_101F4);

Java.perform(function () {
Interceptor.attach(addr_101F4, {
onEnter: function (args) {
console.log("addr_101F4 OnEnter :", this.context.PC,
this.context.x1, this.context.x5,
this.context.x10);
},
onLeave: function (retval) {
console.log("retval is :", retval)
}
}
)
})
}
}

so 层方法注册到 js 中, 主动调用

1
new NativeFunction(address, returnType, argTypes[, options])
  • address : 函数地址
  • returnType : 指定返回类型
  • argTypes : 数组指定参数类型
  • 类型可选: void, pointer, int, uint, long, ulong, char, uchar, float, double, int8, uint8, int16, int32, uint32, int64, uint64; 参照函数所需的 type 来定义即可;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function invoke_native_func() {
var baseAddr = Module.findBaseAddress("libnative-lib.so");
console.log("baseAddr", baseAddr);
var offset = 0x0000A28C + 1;
var add_c_addr = baseAddr.add(offset);
var add_c_func = new NativeFunction(add_c_addr, "int", ["int","int"]);
var result = add_c_func(1, 2);
console.log(result);
}
Java.perform(function () {
// 获取 so 文件基地址
var base = Module.findBaseAddress("libnative-lib.so");
// 获取目标函数偏移
var sub_834_addr = base.add(0x835) // thumb 需要 +1
// 使用 new NativeFunction 将函数注册到 js
var sub_834 = new NativeFunction(sub_834_addr, 'pointer', ['pointer']);
// 开辟内存, 创建入参
var arg0 = Memory.alloc(10);
ptr(arg0).writeUtf8String("123");
var result = sub_834(arg0);
console.log("result is :", hexdump(result));
})

hook libart 中的 jni 方法

jni 全部定在在 /system/lib(64)/libart.so 文件中, 通过枚举 symbols 筛选出指定的方法

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
function hook_libart() {
var GetStringUTFChars_addr = null;

// jni 系统函数都在 libart.so 中
var module_libart = Process.findModuleByName("libart.so");
var symbols = module_libart.enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
var name = symbols[i].name;
if ((name.indexOf("JNI") >= 0)
&& (name.indexOf("CheckJNI") == -1)
&& (name.indexOf("art") >= 0)) {
if (name.indexOf("GetStringUTFChars") >= 0) {
console.log(name);
// 获取到指定 jni 方法地址
GetStringUTFChars_addr = symbols[i].address;
}
}
}

Java.perform(function(){
Interceptor.attach(GetStringUTFChars_addr, {
onEnter: function(args){
// console.log("args[0] is : ", args[0]);
// console.log("args[1] is : ", args[1]);
console.log("native args[1] is :",Java.vm.getEnv().getStringUtfChars(args[1],null).readCString());
console.log('GetStringUTFChars onEnter called from:\n' +
Thread.backtrace(this.context, Backtracer.FUZZY)
.map(DebugSymbol.fromAddress).join('\n') + '\n');
// console.log("native args[1] is :", Java.cast(args[1], Java.use("java.lang.String")));
// console.log("native args[1] is :", Memory.readCString(Java.vm.getEnv().getStringUtfChars(args[1],null)));
}, onLeave: function(retval){
// retval const char*
console.log("GetStringUTFChars onLeave : ", ptr(retval).readCString());
}
})
})
}

hook libc 中的系统方法

/system/lib(64)/libc.so 导出的符号没有进行 namemanline , 直接过滤筛选即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// hook libc.so
var pthread_create_addr = null;

// console.log(JSON.stringify(Process.enumerateModules()));
// Process.enumerateModules() 枚举加载的so文件
var symbols = Process.findModuleByName("libc.so").enumerateSymbols();
for (var i = 0; i < symbols.length; i++){
if (symbols[i].name === "pthread_create"){
// console.log("symbols name is -> " + symbols[i].name);
// console.log("symbols address is -> " + symbols[i].address);
pthread_create_addr = symbols[i].address;
}
}

Interceptor.attach(pthread_create_addr,{
onEnter: function(args){
console.log("args is ->" + args[0], args[1], args[2],args[3]);
},
onLeave: function(retval){
console.log(retval);
}
});

libc.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
34
35
// hook 检测frida 的方法
function main() {
// var exports = Process.findModuleByName("libnative-lib.so").enumerateExports(); 导出
// var imports = Process.findModuleByName("libnative-lib.so").enumerateImports(); 导入
// var symbols = Process.findModuleByName("libnative-lib.so").enumerateSymbols(); 符号

var pthread_create_addr = null;
var symbols = Process.getModuleByName("libc.so").enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
if (symbol.name === "pthread_create") {
pthread_create_addr = symbol.address;
console.log("pthread_create name is ->", symbol.name);
console.log("pthread_create address is ->", pthread_create_addr);
}
}

Java.perform(function(){
// 定义方法 之后主动调用的时候使用
var pthread_create = new NativeFunction(pthread_create_addr, 'int', ['pointer', 'pointer','pointer','pointer'])
Interceptor.replace(pthread_create_addr,new NativeCallback(function (a0, a1, a2, a3) {
var result = null;
var detect_frida_loop = Module.findExportByName("libnative-lib.so", "_Z17detect_frida_loopPv");
console.log("a0,a1,a2,a3 ->",a0,a1,a2,a3);
if (String(a2) === String(detect_frida_loop)) {
result = 0;
console.log("阻止frida反调试启动");
} else {
result = pthread_create(a0,a1,a2,a3);
console.log("正常启动");
}
return result;
}, 'int', ['pointer', 'pointer','pointer','pointer']));
})
}

hook native 调用栈

1
2
3
4
5
6
7
Interceptor.attach(f, {
onEnter: function (args) {
console.log('RegisterNatives called from:\n' +
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n') + '\n');
}
});

jnitrace

安装

jnitrace: https://github.com/chame1eon/jnitrace

1
python` : `pip install jnitrace

基础用法

ndk 开发是没有办法脱离 [libc.so](http://libc.so)[libart.so](http://libart.so) 进行开发, 所以只要降维打击, 通过 trace 的方式就可以监控到 so

https://kevinspider-1258012111.cos.ap-shanghai.myqcloud.com/2020-09-07-032605.jpg

启动命令

1
jnitrace [options] -l libname packagename

例如: jnitrace -l [libnative-lib.so](http://libnative-lib.so) com.example.myapplication

必要参数

  • -l libname : 指定要trace.so文件, 可以同时trace多个.so文件, 直接使用 *trace所有的.so文件; 如: -l libnative-lib.so -l libanother-lib.so or -l *
  • packagename : 指定要tracepackage name

可选参数

  • -m: 指定是spawn还是attach
  • -b: 指定是fuzzy还是accurate
  • -i <regex>: 指定一个正则表达式来过滤出方法名, 例如: -i Get -i RegisterNatives 就只会打印出名字里包含Get或者RegisterNativesJNI methods
  • -e <regex>i相反,同样通过正则表达式来过滤,但这次会将指定的内容忽略掉
  • -I <string>trace导出的方法,jnitrace认为导出的函数应该是从Java端能够直接调用的函数,所以可以包括使用RegisterNatives来注册的函数,例如I stringFromJNI -I nativeMethod([B)V,就包括导出名里有stringFromJNI,以及使用RegisterNames来注册,并带有nativeMethod([B)V签名的函数。
  • -o path/output.json,导出输出到文件里。
  • -p path/to/script.js,用于在加载jnitrace脚本之前将指定路径的Frida脚本加载到目标进程中,这可以用于在jnitrace启动之前对抗反调试。
  • -a path/to/script.js,用于在加载jnitrace脚本之后将指定路径的Frida脚本加载到目标进程中
  • -ignore-env,不打印所有的JNIEnv函数
  • -ignore-vm,不打印所有的JavaVM函数

启动方式

默认使用 spawn 启动, 可以通过 -m attach 设置通过 attach 启动

1
jnitrace -m attach -l[libnative-lib.so](http://libnative-lib.so) com.kevin.demoso1

设置回溯器

默认情况下使用 accurate的精确模式来进行回溯, 可以通过 -b fuzzy 修改为模糊模式

1
jnitrace -l [libnative-lib.so](http://libnative-lib.so) -b fuzzy com.kevin.demoso1

监控指定规则的方法

用于指定应该跟踪的方法名, 该选项可以多次提供;

1
jnitrace -l libnative-lib.so -i RegisterNatives com.kevin.demoso1

只过滤出RegisterNatives相关的内容

忽略指定规则的方法

用于指定在跟踪中应被忽略的方法名, 这个选项可以被多次提供;

忽略以Find开头的所有方法;

1
jnitrace -l libnative-lib.so -e ^Find com.kevin.demoso

jnitace 计算偏移地址

https://kevinspider-1258012111.cos.ap-shanghai.myqcloud.com/2020-09-07-062032.png

0x8e4f3b1 是方法 initSN 方法的绝对地址

0xd8e4e000[libmyjni.so](http://libmyjni.so) 基地址

使用使用 initSN()V的绝对地址 0xd8e4f3b1 减去 [libmyjni.so](http://libmyjni.so) 的基地址 0xd8e4e000 , 得到偏移 0x13B1

https://kevinspider-1258012111.cos.ap-shanghai.myqcloud.com/2020-09-07-070337.png

g 进行跳转到 0x13B1 即可进入方法

frida trace

文档地址: https://frida.re/docs/frida-trace/

options

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
Usage: frida-trace [options] target

Options:
--version show program's version number and exit
-h, --help show this help message and exit
-D ID, --device=ID connect to device with the given ID
-U, --usb connect to USB device
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
-f FILE, --file=FILE spawn FILE
-F, --attach-frontmost attach to frontmost application
-n NAME, --attach-name=NAME attach to NAME
-p PID, --attach-pid=PID attach to PID
--stdio=inherit|pipe stdio behavior when spawning (defaults to “inherit”)
--runtime=duk|v8 script runtime to use (defaults to “duk”)
--debug enable the Node.js compatible script debugger
-I MODULE, --include-module=MODULE include MODULE
-X MODULE, --exclude-module=MODULE exclude MODULE
-i FUNCTION, --include=FUNCTION include FUNCTION
-x FUNCTION, --exclude=FUNCTION exclude FUNCTION
-a MODULE!OFFSET, --add=MODULE!OFFSET add MODULE!OFFSET
-T, --include-imports include program's imports
-t MODULE, --include-module-imports=MODULE include MODULE imports
-m OBJC_METHOD, --include-objc-method=OBJC_METHOD include OBJC_METHOD
-M OBJC_METHOD, --exclude-objc-method=OBJC_METHOD exclude OBJC_METHOD
-s DEBUG_SYMBOL, --include-debug-symbol=DEBUG_SYMBOL include DEBUG_SYMBOL
-q, --quiet do not format output messages
-d, --decorate Add module name to generated onEnter log statement
-o OUTPUT, --output=OUTPUT dump messages to file

基础使用

1
frida-trace [options] packagename

启动模式

默认使用 attach 模式, 可以指定 -f packageName 使用 spawn 模式启动

1
frida-trace -U -i strcmp -f com.gdufs.xman

文件输出

1
frida-trace -U -i "strcmp" -f com.gdufs.xman -o xman.json

-o filepath 指定输出的文件路径, 方便内容过多时进行查看

trace 任意 function

1
frida-trace -U -i "strcmp" com.example.demoso1

trace 任意 module

1
frida-trace -U -I "libnative-lib.so" com.example.demoso1

根据地址进行 trace

1
frida-trace -U -a "libnative-lib.so!0x9281" com.example.demoso1

批量 trace

源码地址: https://github.com/Pr0214/trace_natives

ps: 需要切换到 frida14 版本

1.将traceNatives.py丢进IDA plugins目录中

  • 在ida 的python console中运行如下命令即可找到plugins目录:os.path.join(idaapi.get_user_idadir(), "plugins")

2.IDA中,Edit-Plugins-traceNatives –> IDA输出窗口就会显示如下字眼:使用方法如下: frida-trace -UF -O C:\Users\Lenovo\Desktop\2021\mt\libmtguard.txt


frida-hook-libart

下载地址: https://github.com/lasting-yang/frida_hook_libart

hook art

1
frida -U --no-pause -f package_name -l hook_art.js

hook_RegisterNatives

1
frida -U --no-pause -f package_name -l hook_RegisterNatives.js

hook_artmethod

init libext first time

1
2
3
4
5
6
7
adb push lib/libext64.so /data/local/tmp/libext64.so
adb push lib/libext.so /data/local/tmp/libext.so
adb shell su -c "cp /data/local/tmp/libext64.so /data/app/libext64.so"
adb shell su -c "cp /data/local/tmp/libext.so /data/app/libext.so"
adb shell su -c "chown 1000.1000 /data/app/libext*.so"
adb shell su -c "chmod 777 /data/app/libext*.so"
adb shell su -c "ls -al /data/app/libext*"

use hook_artmethod.js

1
2
3
frida -U --no-pause -f package_name -l hook_artmethod.js
# or
frida -U --no-pause -f package_name -l hook_artmethod.js > hook_artmethod.log

frida-fart-hook

首先拷贝fart.so和fart64.so到/data/app目录下,并使用chmod 777 设置好权限,然后就可以使用了。

如果目标 app 没有 sdcard 权限则需要手动添加; 或者可以修改 frida_fart_hook.js 中的源码, 将 savepath 改为 /data/data/应用包名/;

该frida版fart是使用hook的方式实现的函数粒度的脱壳,仅仅是对类中的所有函数进行了加载,但依然可以解决绝大多数的抽取保护

需要以spawn方式启动app,等待app进入Activity界面后,执行fart()函数即可。如app包名为com.example.test,则

frida -U -f com.example.test -l frida_fart_hook.js --no-pause ,然后等待app进入主界面,执行fart()

高级用法:如果发现某个类中的函数的CodeItem没有dump下来,可以调用dump(classname),传入要处理的类名,完成对该类下的所有函数体的dump,dump下来的函数体会追加到bin文件当中。


frida 文件写入(frida/hook libc)

frida api 写入文件

1
2
3
4
5
6
function writeFile(){
var file = new File("/sdcard/reg.dat", "w");
file.write("content from frida");
file.flush();
file.close();
}

frida 定义 NativeFunction 写入文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function writeFileNative(){
var addr_fopen = Module.findExportByName("libc.so", "fopen");
var addr_fputs = Module.findExportByName("libc.so", "fputs");
var addr_fclose = Module.findExportByName("libc.so", "fclose");

// 将 libc 的系统方法注册到 js 层
var fopen = new NativeFunction(addr_fopen, "pointer", ["pointer", "pointer"]);
var fputs = new NativeFunction(addr_fputs, "int", ["pointer", "pointer"]);
var fclose = new NativeFunction(addr_fclose, "int", ["pointer"]);

// 在 js 层主动调用 libc 的方法
// 不能直接将 js 的字符串传给 libc中的方法, 需要进行转换
var filename = Memory.allocUtf8String("/sdcard/reg.dat");
var open_mode = Memory.allocUtf8String("w");
var file = fopen(filename, open_mode);

var buffer = Memory.allocUtf8String("content from frida");
var result = fputs(buffer, file);
console.log("fputs ret: ", result);

// 关闭文件
fclose(file);

}

hook 读写 std::string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function readStdString(str){
var isTiny = (str.readU8 & 1) === 0;
if (isTiny){
return str.add(1).readUtf8String();
}
return str.add(2 * Process.pointerSize).readPointer().readUtf8String();
}



function writeStdString(str, content){
var isTiny = (str.readU8() & 1) === 0;
if (isTiny){
str.add(1).writeUtf8String(content);
}else{
str.add(2 * Process.pointerSize).readPointer().writeUtf8String(content);
}
}

hook so 文件加载后马上 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
34
35
36
37
38
39
40
41
42
43
//第一种方式(针对较老的系统版本)<=android6
var dlopen = Module.findExportByName(null, "dlopen");
console.log(dlopen);
if(dlopen != null){
Interceptor.attach(dlopen,{
onEnter: function(args){
var soName = args[0].readCString();
console.log(soName);
if(soName.indexOf("libc.so") != -1){
this.hook = true;
}
},
onLeave: function(retval){
if(this.hook) {
dlopentodo();
};
}
});
}

//第二种方式(针对新系统版本) android 8.1 使用该方法
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
console.log(android_dlopen_ext);
if(android_dlopen_ext != null){
Interceptor.attach(android_dlopen_ext,{
onEnter: function(args){
var soName = args[0].readCString();
console.log(soName);
if(soName.indexOf("libc.so") != -1){
this.hook = true;
}
},
onLeave: function(retval){
if(this.hook) {
dlopentodo();
};
}
});
}

function dlopentodo(){
//todo ...
}

hook libc kill

1
2
3
4
5
6
7
8
9
10
function replaceKILL(){
var kill_addr = Module.findExportByName("libc.so","kill");
Interceptor.replace(kill_addr, new NativeCallback(function(arg0, arg1){
console.log("arg0=> ", arg0);
console.log("arg1=> ", arg1);
console.log('libc.so!kill called from:\n' +
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n') + '\n');
},"int",["int","int"]))
}

hook init_array

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
84
85
86
87
88
89
90
91
92
93
94
95
//应用以32位在64位终端环境下运行
//adb install --abi armeabi-v7a <path to apk>

function get_call_function() {
var call_function_addr = null;
var symbols = Process.getModuleByName("linker").enumerateSymbols();
for (var m = 0; m < symbols.length; m++) {
if (symbols[m].name == "__dl__ZL13call_functionPKcPFviPPcS2_ES0_") {
call_function_addr = symbols[m].address;
console.log("found call_function_addr => ", call_function_addr)
hook_call_function(call_function_addr)
}
}
}

function hook_call_function(_call_function_addr){
console.log("hook call function begin!hooking address :=>",_call_function_addr)
Interceptor.attach(_call_function_addr,{
onEnter:function(args){
if(args[2].readCString().indexOf("base.odex")<0){
console.log("============================")
console.log("function_name =>",args[0].readCString())
var soPath = args[2].readCString()
console.log("so path : =>",soPath)
var soName = soPath.split("/").pop();
console.log("function offset =>","0x"+(args[1]-Module.findBaseAddress(soName)).toString(16))
console.log("============================")
}
},onLeave:function(retval){
}
})
}

setImmediate(get_call_function)

function hook_constructor() {
if (Process.pointerSize == 4) {
var linker = Process.findModuleByName("linker");
} else {
var linker = Process.findModuleByName("linker64");
}

var addr_call_function =null;
var addr_g_ld_debug_verbosity = null;
var addr_async_safe_format_log = null;
if (linker) {
var symbols = linker.enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
var name = symbols[i].name;
if (name.indexOf("call_function") >= 0){
addr_call_function = symbols[i].address;
}
else if(name.indexOf("g_ld_debug_verbosity") >=0){
addr_g_ld_debug_verbosity = symbols[i].address;

ptr(addr_g_ld_debug_verbosity).writeInt(2);

} else if(name.indexOf("async_safe_format_log") >=0 && name.indexOf('va_list') < 0){

addr_async_safe_format_log = symbols[i].address;

}

}
}
if(addr_async_safe_format_log){
Interceptor.attach(addr_async_safe_format_log,{
onEnter: function(args){
this.log_level = args[0];
this.tag = ptr(args[1]).readCString()
this.fmt = ptr(args[2]).readCString()
if(this.fmt.indexOf("c-tor") >= 0 && this.fmt.indexOf('Done') < 0){
this.function_type = ptr(args[3]).readCString(), // func_type
this.so_path = ptr(args[5]).readCString();
var strs = new Array(); //定义一数组
strs = this.so_path.split("/"); //字符分割
this.so_name = strs.pop();
this.func_offset = ptr(args[4]).sub(Module.findBaseAddress(this.so_name))
console.log("func_type:", this.function_type,
'\nso_name:',this.so_name,
'\nso_path:',this.so_path,
'\nfunc_offset:',this.func_offset
);
}
},
onLeave: function(retval){

}
})
}
}
function main() {
hook_constructor();
}
setImmediate(main);

frida dump

document: https://github.com/lasting-yang/frida_dump

frida dump so

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function dump_so(so_name) {
Java.perform(function () {
var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
var dir = currentApplication.getApplicationContext().getFilesDir().getPath();
var libso = Process.getModuleByName(so_name);
console.log("[name]:", libso.name);
console.log("[base]:", libso.base);
console.log("[size]:", ptr(libso.size));
console.log("[path]:", libso.path);
var file_path = dir + "/" + libso.name + "_" + libso.base + "_" + ptr(libso.size) + ".so";
var file_handle = new File(file_path, "wb");
if (file_handle && file_handle != null) {
Memory.protect(ptr(libso.base), libso.size, 'rwx');
var libso_buffer = ptr(libso.base).readByteArray(libso.size);
file_handle.write(libso_buffer);
file_handle.flush();
file_handle.close();
console.log("[dump]:", file_path);
}
});
}

frida dump dex

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
function get_self_process_name() {
var openPtr = Module.getExportByName('libc.so', 'open');
var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);

var readPtr = Module.getExportByName("libc.so", "read");
var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]);

var closePtr = Module.getExportByName('libc.so', 'close');
var close = new NativeFunction(closePtr, 'int', ['int']);

var path = Memory.allocUtf8String("/proc/self/cmdline");
var fd = open(path, 0);
if (fd != -1) {
var buffer = Memory.alloc(0x1000);

var result = read(fd, buffer, 0x1000);
close(fd);
result = ptr(buffer).readCString();
return result;
}

return "-1";
}


function mkdir(path) {
var mkdirPtr = Module.getExportByName('libc.so', 'mkdir');
var mkdir = new NativeFunction(mkdirPtr, 'int', ['pointer', 'int']);



var opendirPtr = Module.getExportByName('libc.so', 'opendir');
var opendir = new NativeFunction(opendirPtr, 'pointer', ['pointer']);

var closedirPtr = Module.getExportByName('libc.so', 'closedir');
var closedir = new NativeFunction(closedirPtr, 'int', ['pointer']);

var cPath = Memory.allocUtf8String(path);
var dir = opendir(cPath);
if (dir != 0) {
closedir(dir);
return 0;
}
mkdir(cPath, 755);
chmod(path);
}

function chmod(path) {
var chmodPtr = Module.getExportByName('libc.so', 'chmod');
var chmod = new NativeFunction(chmodPtr, 'int', ['pointer', 'int']);
var cPath = Memory.allocUtf8String(path);
chmod(cPath, 755);
}

function dump_dex() {
var libart = Process.findModuleByName("libart.so");
var addr_DefineClass = null;
var symbols = libart.enumerateSymbols();
for (var index = 0; index < symbols.length; index++) {
var symbol = symbols[index];
var symbol_name = symbol.name;
//这个DefineClass的函数签名是Android9的
//_ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE
if (symbol_name.indexOf("ClassLinker") >= 0 &&
symbol_name.indexOf("DefineClass") >= 0 &&
symbol_name.indexOf("Thread") >= 0 &&
symbol_name.indexOf("DexFile") >= 0) {
console.log(symbol_name, symbol.address);
addr_DefineClass = symbol.address;
}
}
var dex_maps = {};
var dex_count = 1;

console.log("[DefineClass:]", addr_DefineClass);
if (addr_DefineClass) {
Interceptor.attach(addr_DefineClass, {
onEnter: function(args) {
var dex_file = args[5];
//ptr(dex_file).add(Process.pointerSize) is "const uint8_t* const begin_;"
//ptr(dex_file).add(Process.pointerSize + Process.pointerSize) is "const size_t size_;"
var base = ptr(dex_file).add(Process.pointerSize).readPointer();
var size = ptr(dex_file).add(Process.pointerSize + Process.pointerSize).readUInt();

if (dex_maps[base] == undefined) {
dex_maps[base] = size;
var magic = ptr(base).readCString();
if (magic.indexOf("dex") == 0) {

var process_name = get_self_process_name();
if (process_name != "-1") {
var dex_dir_path = "/data/data/" + process_name + "/files/dump_dex_" + process_name;
mkdir(dex_dir_path);
var dex_path = dex_dir_path + "/class" + (dex_count == 1 ? "" : dex_count) + ".dex";
console.log("[find dex]:", dex_path);
var fd = new File(dex_path, "wb");
if (fd && fd != null) {
dex_count++;
var dex_buffer = ptr(base).readByteArray(size);
fd.write(dex_buffer);
fd.flush();
fd.close();
console.log("[dump dex]:", dex_path);

}
}
}
}
},
onLeave: function(retval) {}
});
}
}

var is_hook_libart = false;

function hook_dlopen() {
Interceptor.attach(Module.findExportByName(null, "dlopen"), {
onEnter: function(args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
//console.log("dlopen:", path);
if (path.indexOf("libart.so") >= 0) {
this.can_hook_libart = true;
console.log("[dlopen:]", path);
}
}
},
onLeave: function(retval) {
if (this.can_hook_libart && !is_hook_libart) {
dump_dex();
is_hook_libart = true;
}
}
})

Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
onEnter: function(args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
//console.log("android_dlopen_ext:", path);
if (path.indexOf("libart.so") >= 0) {
this.can_hook_libart = true;
console.log("[android_dlopen_ext:]", path);
}
}
},
onLeave: function(retval) {
if (this.can_hook_libart && !is_hook_libart) {
dump_dex();
is_hook_libart = true;
}
}
});
}


setImmediate(dump_dex);

frida dump dex class

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
function get_self_process_name() {
var openPtr = Module.getExportByName('libc.so', 'open');
var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);

var readPtr = Module.getExportByName("libc.so", "read");
var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]);

var closePtr = Module.getExportByName('libc.so', 'close');
var close = new NativeFunction(closePtr, 'int', ['int']);

var path = Memory.allocUtf8String("/proc/self/cmdline");
var fd = open(path, 0);
if (fd != -1) {
var buffer = Memory.alloc(0x1000);

var result = read(fd, buffer, 0x1000);
close(fd);
result = ptr(buffer).readCString();
return result;
}

return "-1";
}

function load_all_class() {
if (Java.available) {
Java.perform(function () {

var DexFileclass = Java.use("dalvik.system.DexFile");
var BaseDexClassLoaderclass = Java.use("dalvik.system.BaseDexClassLoader");
var DexPathListclass = Java.use("dalvik.system.DexPathList");

Java.enumerateClassLoaders({
onMatch: function (loader) {
try {
var basedexclassloaderobj = Java.cast(loader, BaseDexClassLoaderclass);
var pathList = basedexclassloaderobj.pathList.value;
var pathListobj = Java.cast(pathList, DexPathListclass)
var dexElements = pathListobj.dexElements.value;
for (var index in dexElements) {
var element = dexElements[index];
try {
var dexfile = element.dexFile.value;
var dexfileobj = Java.cast(dexfile, DexFileclass);
console.log("dexFile:", dexfileobj);
const classNames = [];
const enumeratorClassNames = dexfileobj.entries();
while (enumeratorClassNames.hasMoreElements()) {
var className = enumeratorClassNames.nextElement().toString();
classNames.push(className);
try {
loader.loadClass(className);
} catch (error) {
console.log("loadClass error:", error);
}
}
} catch (error) {
console.log("dexfile error:", error);
}
}
} catch (error) {
console.log("loader error:", error);
}
},
onComplete: function () {

}
})
console.log("load_all_class end.");
});
}
}
var dex_maps = {};

function print_dex_maps() {
for (var dex in dex_maps) {
console.log(dex, dex_maps[dex]);
}
}

function dump_dex() {
load_all_class();

for (var base in dex_maps) {
var size = dex_maps[base];
console.log(base);

var magic = ptr(base).readCString();
if (magic.indexOf("dex") == 0) {
var process_name = get_self_process_name();
if (process_name != "-1") {
var dex_path = "/data/data/" + process_name + "/files/" + base.toString(16) + "_" + size.toString(16) + ".dex";
console.log("[find dex]:", dex_path);
var fd = new File(dex_path, "wb");
if (fd && fd != null) {
var dex_buffer = ptr(base).readByteArray(size);
fd.write(dex_buffer);
fd.flush();
fd.close();
console.log("[dump dex]:", dex_path);

}
}
}
}
}

function hook_dex() {
var libart = Process.findModuleByName("libart.so");
var addr_DefineClass = null;
var symbols = libart.enumerateSymbols();
for (var index = 0; index < symbols.length; index++) {
var symbol = symbols[index];
var symbol_name = symbol.name;
//这个DefineClass的函数签名是Android9的
//_ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE
if (symbol_name.indexOf("ClassLinker") >= 0 &&
symbol_name.indexOf("DefineClass") >= 0 &&
symbol_name.indexOf("Thread") >= 0 &&
symbol_name.indexOf("DexFile") >= 0) {
console.log(symbol_name, symbol.address);
addr_DefineClass = symbol.address;
}
}

console.log("[DefineClass:]", addr_DefineClass);
if (addr_DefineClass) {
Interceptor.attach(addr_DefineClass, {
onEnter: function (args) {
var dex_file = args[5];
//ptr(dex_file).add(Process.pointerSize) is "const uint8_t* const begin_;"
//ptr(dex_file).add(Process.pointerSize + Process.pointerSize) is "const size_t size_;"
var base = ptr(dex_file).add(Process.pointerSize).readPointer();
var size = ptr(dex_file).add(Process.pointerSize + Process.pointerSize).readUInt();

if (dex_maps[base] == undefined) {
dex_maps[base] = size;
console.log("hook_dex:", base, size);
}
},
onLeave: function (retval) {}
});
}

}

var is_hook_libart = false;

function hook_dlopen() {
Interceptor.attach(Module.findExportByName(null, "dlopen"), {
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
//console.log("dlopen:", path);
if (path.indexOf("libart.so") >= 0) {
this.can_hook_libart = true;
console.log("[dlopen:]", path);
}
}
},
onLeave: function (retval) {
if (this.can_hook_libart && !is_hook_libart) {
hook_dex();
is_hook_libart = true;
}
}
})

Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
//console.log("android_dlopen_ext:", path);
if (path.indexOf("libart.so") >= 0) {
this.can_hook_libart = true;
console.log("[android_dlopen_ext:]", path);
}
}
},
onLeave: function (retval) {
if (this.can_hook_libart && !is_hook_libart) {
hook_dex();
is_hook_libart = true;
}
}
});
}


setImmediate(hook_dex);

指针运算符和读写 API

img

img

hook so readPointer()

1
2
3
4
5
6
7
8
9
10
Java.perform(function () {
var libc_addr = Process.findModuleByName("libc.so").base;
console.log("libc address is " + libc_addr);
// 0x10 转为十进制为 16, 读取
console.log(libc_addr.readByteArray(0x10));
// readPointer(), 从此内存位置读取 NativePointer
console.log("pointer size", Process.pointerSize);
console.log("readPointer() is " + libc_addr.readPointer());
console.log("Memory.readPointer()" + Memory.readPointer(libc_addr.add(Process.pointerSize)));
})

hook so writePointer()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java.perform(function () {
var libc_addr = Process.findModuleByName("libc.so").base;
console.log("libc_addr : " + libc_addr);
// 分配四个字节的空间地址
const r = Memory.alloc(4);
// 将 libc_addr 指针写入刚申请的 r 中
r.writePointer(libc_addr);
// 读取 r 指针的数据
var buffer = Memory.readByteArray(r, 4);
console.log(buffer);
})

//libc_addr : 0x7da7fdf000
// 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
// 00000000 00 f0 fd a7 ....

hook so readS32(), readU32()

从指定内存地址读取有符号或者无符号 8/16/21/etc 或浮点数/双精度值, 并将其作为数字返回;

1
2
3
4
5
6
Java.perform(function () {
var libc_addr = Process.findModuleByName("libc.so").base;
console.log(hexdump(libc_addr));
console.log(libc_addr.readS32(), (libc_addr.readS32()).toString(16));
console.log(libc_addr.readU32(), (libc_addr.readU32()).toString(16));
})

img

hook so writeS32(), writeU32()

将有符号或无符号8/16/32/等或浮点数/双精度值写入此内存位置

1
2
3
4
5
6
7
8
9
10
Java.perform(function () {
// 开辟四个字节的内存空间
const r = Memory.alloc(4);
r.writeS32(0x12345678);
console.log(r.readByteArray(0x10));
})

<!-- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 78 56 34 12 7d 00 00 00 98 c0 bb a8 7d 00 00 00 xV4.}.......}...
-->

hook so readByteArray(), writeByteArray()

1
2
3
4
5
6
7
8
9
10
11
12
Java.perform(function () {
// 定义一个需要写入的字节数组
var arr = [ 0x72, 0x6F, 0x79, 0x73, 0x75, 0x65];
//这里申请以arr大小的内存空间
var r = Memory.alloc(arr.length);
// 将 arr 写入 r 中
r.writeByteArray(arr);
// Memory.writeByteArray(r, arr); 同样可以写入
console.log("memory readbyteArray: ")
console.log(r.readByteArray(arr.length));
console.log(Memory.readByteArray(r, arr.length));
})

hook so readCString(), writeUtf8String()

1
2
3
4
5
6
7
8
9
10
11
Java.perform(function () {
// 开辟内存空间 存有字符串
var r = Memory.allocUtf8String("你好,世界");
// 读取内存中的字符串
console.log(hexdump(r));
console.log(r.readCString());
// 往内存中写入新的字符串
r.writeUtf8String("Hello,World");
console.log(hexdump(r));
console.log(r.readCString())
})

hook 获取 jni array

1
2
3
4
5
6
// 获取 jbytesArray 的指针
var arg1Ptr = Java.vm.getEnv().getByteArrayElements(this.arg1, null)
// 获取到指针后可以直接 hexdump 打印
console.log("arg1Ptr",hexdump(arg1Ptr));
// 如果是字符串可以直接转
console.log("arg1Ptr",arg1Ptr.readCString());

ida 动态调试

  1. 将 ida 中的 android-server 推入到手机中 adb push /Applications/IDA\ Pro\ 7.0/ida.app/Contents/MacOS/dbgsrv/android_server /data/local/tmp/as android_server 负责调试 32 位的app, android_server64 负责调试 64 位的app, 改名为 as 可以防止部分 android_server 名称检测
  2. 给 android_server 增加权限
1
2
3
adb shell
su
chmod +x /data/local/tmp/as
  1. 进行端口转发 adb forward tcp:11678 tcp:11678
  2. 启动 android_server 并指定端口为 11678
1
2
3
adb shell
su
/data/local/tmp/as -p11678
  1. 调试之前先注入 frida
  2. 新建一个 ida 界面
  3. Debugger - Remote ARMLinux/Android debugger
  4. hostname: localhost; Port: 11678; 勾选 Save network settings as default
  5. frida 打印出目标 function 最终的地址, ida 中 g 到目标 function 地址, 查看是否是 thumb 指令集, 如果是 thumb 则 option + g, 将 T 修改为1, 再按 c;
  6. File - Script file - 读取 ida trace 脚本, 需要更改目标 so 文件和 function 的起始地址和结束地址; 读取之后会出现断点; 快捷键: option F7;
  7. frida 主动调用脚本, 检测断点是否触发
  8. 断点检测正常后, ida 中执行 starthook()命令, 进行 hook 操作; 执行suspend_other_thread()挂起其他线程(可选择)
  9. ida 中 Debugger - tracing - tracing options - 设置 Trace file 路径 和 取消 Trace over debugger segments 的勾选
  10. ida 中 Debugger - tracing - 勾选 instruction tracing
  11. frida 主动调用触发断点
  12. ida 点击运行按钮进行执行, 此时 ida 中黄色部分为已经执行完的指令, 点击 Debugger -tracing - tracing window 可以看到当前执行进度;

ida 添加自有 python 路径

修改路径下的文件 : /Applications/IDA Pro 7.0/ida.app/Contents/MacOS/python/init.py

1
2
3
4
5
6
7
8
# Prepare sys.path so loading of the shared objects works
lib_dynload = os.path.join(
sys.executable,
IDAPYTHON_DYNLOAD_BASE,
"python", "lib", "python2.7", "lib-dynload")

# added by kevin
sys.path.insert(0, "/Users/zhangyang/anaconda3/envs/py2/lib/python2.7/site-packages")

idapython 脚本调试

在 pycharm 中开启调试

img

在需要调试的脚本中添加断点

1
2
3
import pydevd_pycharm

pydevd_pycharm.settrace('localhost', port=12345, stdoutToServer=True, stderrToServer=True)

在 idapython 的__init__.py文件中添加自有的 python 路径;

先在 pycharm 中打开调试监听, 在 ida 中运行要调试的脚本即可;

jni_helper

  1. 进入目录 ~/androidFxxk/idaTools/jni_helper
  2. java -jar JadxFindJNI/JadxFindJNI.jar <apk.path> <output.json>
  3. ida 中Script File运行 jni_help脚本, 路径 ~/androidFxxk/idaTools/jni_helper/ida/jni_helper.py
  4. 导入刚才生成的 output.json 文件即可自动识别

frida dump 内存中的 so 文件

给应用提供 sdcard 权限, 方便将 dump 下来的内容直接写入 sdcard 中;

  1. adb shell dumpsys window | grep mCurrentFocus 查看当前 app 的包名
  2. 根据包名查看进程 pid
1
2
3
4
5
 ✔  adb shell
sailfish:/ $ su
sailfish:/ # ps -ef | grep com.kevin.sotest
u0_a136 29251 641 0 16:10:14 ? 00:00:00 com.kevin.sotest
root 29845 29827 0 16:17:46 pts/3 00:00:00 grep com.kevin.sotest
  1. 通过对应进程id 和 so 名称, 查看其在虚拟地址空间中的映射
1
2
3
4
sailfish:/ # cat /proc/29251/maps | grep libnative-lib.so
7593f4a000-7593f7a000 r-xp 00000000 103:13 1180186 /data/app/com.kevin.sotest-Wmu_QuZYJCLa0CPho2HTHw==/lib/arm64/libnative-lib.so
7593f7b000-7593f7f000 r--p 00030000 103:13 1180186 /data/app/com.kevin.sotest-Wmu_QuZYJCLa0CPho2HTHw==/lib/arm64/libnative-lib.so
7593f7f000-7593f80000 rw-p 00034000 103:13 1180186 /data/app/com.kevin.sotest-Wmu_QuZYJCLa0CPho2HTHw==/lib/arm64/libnative-lib.so
  1. frida 脚本将该内存中的内容全部 dump 下来
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
///<reference path='./index.d.ts'/>

// 应用需要申请 sdcard 权限, dump下来的内容直接写入 sdcard 中
var global_so_name = "libnative-lib.so";

function catMaps() {
Java.perform(function () {
console.log("执行 -> ", "hook starting dump");
var ProcessClass = Java.use("android.os.Process");
var pid = ProcessClass.myPid();
console.log("pid", ProcessClass.myPid());
console.log("cat /proc/" + pid + "/maps | grep " + global_so_name);
})
}


function dump() {
var inputArgs = arguments;
Java.perform(function () {
for (var i in inputArgs) {
var fileHandle = null;
if (i == 0) {
fileHandle = new File("/sdcard/" + global_so_name, "wb");
} else {
fileHandle = new File("/sdcard/" + global_so_name, "ab");
}
if (fileHandle && fileHandle != null) {
var base = "0x" + inputArgs[i].split("-")[0];
var end = "0x" + inputArgs[i].split("-")[1];
var size = parseInt(parseInt(end) - parseInt(base));
console.log("size", size, typeof size);
// Memory.protect(ptr(base), size, 'rwx');
console.log("base", base, typeof base);
var libso_buffer = ptr(base).readByteArray(size);
console.log("buffer", libso_buffer);
fileHandle.write(libso_buffer);
fileHandle.flush();
fileHandle.close();
}
}
})
}


// dump('7593f4a000-7593f7a000', '7593f7b000-7593f7f000', '7593f7f000-7593f80000')

hook system_properties_get

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
///<reference path='./index.d.ts'/>

// hook libc.so
var targetAddr = null;

// console.log(JSON.stringify(Process.enumerateModules()));
// Process.enumerateModules() 枚举加载的so文件
var symbols = Process.findModuleByName("libc.so").enumerateSymbols();
for (var i = 0; i < symbols.length; i++){
if (symbols[i].name === "__system_property_get"){
targetAddr = symbols[i].address;
}
}

//
Interceptor.attach(targetAddr, {
onEnter: function (args) {
this._name = args[0].readCString();
this._value = args[1];
},
onLeave: function (retval) {
console.log(JSON.stringify({
result_length: retval,
name: this._name,
val: this._value.readCString()
}))
}
});

Process 对象API

Process.id

返回附加目标进程的 PID

1
2
3
4
function Process_pid() {
console.log(Process.id);
}
// 10190

调用的结果和在 android 中通过 ps -ef | grep hellojni_sign2的pid 相同

1
u0_a139      10190   639 0 15:03:40 ?     00:00:10 com.example.hellojni_sign2

Process.isDebuggerAttached()

检测当前是否对目标程序已经附加

Process.enumerateModules()

枚举当前加载的模块,返回模块对象的数组。 Process.enumerateModules()会枚举当前所有已加载的so模块,并且返回了数组Module对象; 可以通过 JSON.stringify()打印module 对象;

1
2
3
4
5
6
7
8
9
10
11
12
function ProcessTest() {
var modules = Process.enumerateModules();
for (var i = 0; i < modules.length; i++) {
console.log(JSON.stringify(modules[i]));

var module = modules[i];
console.log("module.name", module.name);
console.log("module.base", module.base);
console.log("module.size", module.size);
console.log("module.path", module.path);
}
}

其中 module 对象中含有的属性为:

  • name: so 的名称
  • base: so 加载的基础地址
  • size: so 文件的大小
  • path: so 文件的路径

Process.enumerateThreads()

Process.enumerateThreads():枚举当前所有的线程,返回包含以下属性的对象数组:

索引 属性 含义
1 id 线程id
2 state 当前运行状态有running, stopped, waiting, uninterruptible or halted
3 context 带有键pc和sp的对象,它们是分别为ia32/x64/arm指定EIP/RIP/PC和ESP/RSP/SP的NativePointer对象。也可以使用其他处理器特定的密钥,例如eax、rax、r0、x0等

Module 对象 API 和属性

Module 对象的属性

索引 属性 含义 1 name 模块名称 2 base 模块地址,其变量类型为NativePointer 3 size 大小 4 path 完整文件系统路径

Module.load()

主要用于加载指定 so 文件, 返回一个 Module 对象;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function frida_Module() {
Java.perform(function () {
//参数为so的名称 返回一个Module对象
const hooks = Module.load('libhello.so');
//输出
console.log("模块名称:",hooks.name);
console.log("模块地址:",hooks.base);
console.log("大小:",hooks.size);
console.log("文件系统路径",hooks.path);
});
}
setImmediate(frida_Module,0);

输出如下:
模块名称: libhello.so
模块地址: 0xdf2d3000
大小: 24576
文件系统路径 /data/app/com.roysue.roysueapplication-7adQZoYIyp5t3G5Ef5wevQ==/lib/arm/libhello.so

Module.enumerateImports()

枚举模块中的所有 import 函数;

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
function frida_Module() {
Java.perform(function () {
const hooks = Module.load('libhello.so');
var Imports = hooks.enumerateImports();
for(var i = 0; i < Imports.length; i++) {
//函数类型
console.log("type:",Imports[i].type);
//函数名称
console.log("name:",Imports[i].name);
//属于的模块
console.log("module:",Imports[i].module);
//函数地址
console.log("address:",Imports[i].address);
}
});
}
setImmediate(frida_Module,0);

输出如下:
[Google Pixel::com.roysue.roysueapplication]-> type: function
name: __cxa_atexit
module: /system/lib/libc.so
address: 0xf58f4521
type: function
name: __cxa_finalize
module: /system/lib/libc.so
address: 0xf58f462d
type: function
name: __stack_chk_fail
module: /system/lib/libc.so
address: 0xf58e2681
...

Module.enumerateExports()

获取指定 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
function frida_Module() {
Java.perform(function () {
const hooks = Module.load('libhello.so');
var Exports = hooks.enumerateExports();
for(var i = 0; i < Exports.length; i++) {
//函数类型
console.log("type:",Exports[i].type);
//函数名称
console.log("name:",Exports[i].name);
//函数地址
console.log("address:",Exports[i].address);
}
});
}
setImmediate(frida_Module,0);

输出如下:
[Google Pixel::com.roysue.roysueapplication]-> type: function
name: Java_com_roysue_roysueapplication_hellojni_getSum
address: 0xdf2d411b
type: function
name: unw_save_vfp_as_X
address: 0xdf2d4c43
type: function
address: 0xdf2d4209
type: function
...

Module.enumerateSymbols()

获取指定 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
function frida_Module() {
Java.perform(function () {
const hooks = Module.load('libc.so');
var Symbol = hooks.enumerateSymbols();
for(var i = 0; i < Symbol.length; i++) {
console.log("isGlobal:",Symbol[i].isGlobal);
console.log("type:",Symbol[i].type);
console.log("section:",JSON.stringify(Symbol[i].section));
console.log("name:",Symbol[i].name);
console.log("address:",Symbol[i].address);
}
});
}
setImmediate(frida_Module,0);

输出如下:
isGlobal: true
type: function
section: {"id":"13.text","protection":"r-x"}
name: _Unwind_GetRegionStart
address: 0xf591c798
isGlobal: true
type: function
section: {"id":"13.text","protection":"r-x"}
name: _Unwind_GetTextRelBase
address: 0xf591c7cc
...

Module.findExportByName(exportName), Module.getExportByName(exportName)

1
2
3
4
5
6
7
8
9
10
11
12
function frida_Module() {
Java.perform(function () {
Module.getExportByName('libhello.so', 'c_getStr')
console.log("Java_com_roysue_roysueapplication_hellojni_getStr address:",Module.findExportByName('libhello.so', 'Java_com_roysue_roysueapplication_hellojni_getStr'));
console.log("Java_com_roysue_roysueapplication_hellojni_getStr address:",Module.getExportByName('libhello.so', 'Java_com_roysue_roysueapplication_hellojni_getStr'));
});
}
setImmediate(frida_Module,0);

输出如下:
Java_com_roysue_roysueapplication_hellojni_getStr address: 0xdf2d413d
Java_com_roysue_roysueapplication_hellojni_getStr address: 0xdf2d413d

Module.findBaseAddress(name)、Module.getBaseAddress(name)

返回 name 模块的绝对基地址

1
2
3
4
5
6
7
8
9
10
11
12
function frida_Module() {
Java.perform(function () {
var name = "libhello.so";
console.log("so address:",Module.findBaseAddress(name));
console.log("so address:",Module.getBaseAddress(name));
});
}
setImmediate(frida_Module,0);

输出如下:
so address: 0xdf2d3000
so address: 0xdf2d3000

Memory 对象

Memory 的一些 API 通常是对内存处理, 譬如 Memroy.copy()用来复制内存; 又或者Memory.writeByteArray写入字节到指定内存中;

Memory.scan搜索内存数据

其主要功能是搜索内存中以address地址开始,搜索长度为size,需要搜是条件是pattern,callbacks搜索之后的回调函数;此函数相当于搜索内存的功能。

img

如果我想搜索在内存中112A地址的起始数据要怎么做,代码示例如下。

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
function frida_Memory() {
Java.perform(function () {
//先获取so的module对象
var module = Process.findModuleByName("libhello.so");
//??是通配符
var pattern = "03 49 ?? 50 20 44";
//基址
console.log("base:"+module.base)
//从so的基址开始搜索,搜索大小为so文件的大小,搜指定条件03 49 ?? 50 20 44的数据
var res = Memory.scan(module.base, module.size, pattern, {
onMatch: function(address, size){
//搜索成功
console.log('搜索到 ' +pattern +" 地址是:"+ address.toString());
},
onError: function(reason){
//搜索失败
console.log('搜索失败');
},
onComplete: function()
{
//搜索完毕
console.log("搜索完毕")
}
});
});
}
setImmediate(frida_Memory,0);

先来看看回调函数的含义,onMatch:function(address,size):使用包含作为NativePointer的实例地址的address和指定大小为数字的size调用,此函数可能会返回字符串STOP以提前取消内存扫描。onError:Function(Reason):当扫描时出现内存访问错误时使用原因调用。onComplete:function():当内存范围已完全扫描时调用。

我们来来说上面这段代码做了什么事情:搜索libhello.so文件在内存中的数据,搜索以pattern条件的在内存中能匹配的数据。搜索到之后根据回调函数返回数据。

搜索内存数据Memory.scanSync

功能与Memory.scan一样,只不过它是返回多个匹配到条件的数据。代码示例如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function frida_Memory() {

Java.perform(function () {

var module = Process.findModuleByName("[libhello.so](http://libhello.so)");

var pattern = "03 49 ?? 50 20 44";

var scanSync = Memory.scanSync(module.base, module.size, pattern);

console.log("scanSync:"+JSON.stringify(scanSync));

});

}

setImmediate(frida_Memory,0);

// 输出如下,可以看到地址搜索出来是一样的

scanSync:[{"address":"0xdf2d412a","size":6}]

Memory.alloc()

在目标进程中的堆上申请size大小的内存,并且会按照Process.pageSize对齐,返回一个NativePointer,并且申请的内存如果在JavaScript里面没有对这个内存的使用的时候会自动释放的。也就是说,如果你不想要这个内存被释放,你需要自己保存一份对这个内存块的引用。

1
2
3
4
5
6
7
8
9
10
11
12
function frida_Memory() {
Java.perform(function () {
const r = Memory.alloc(10);
console.log(hexdump(r, {
offset: 0,
length: 10,
header: true,
ansi: false
}));
});
}
setImmediate(frida_Memory,0);

Memory.allocUtf8String(),Memory.allocUtf16String(),Memory.allocAnsiString()

Memory.allocUtf8String(str) 分配utf字符串

Memory.allocUtf16String 分配utf16字符串

Memory.allocAnsiString 分配ansi字符串

内存复制Memory.copy

如同c api memcp一样调用,使用案例如下。

module.base中复制10个字节的内存到新年申请的r

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function frida_Memory() {
Java.perform(function () {
//获取so模块的Module对象
var module = Process.findModuleByName("libhello.so");
//条件
var pattern = "03 49 ?? 50 20 44";
//搜字符串 只是为了将so的内存数据复制出来 方便演示~
var scanSync = Memory.scanSync(module.base, module.size, pattern);
//申请一个内存空间大小为10个字节
const r = Memory.alloc(10);
//复制以module.base地址开始的10个字节 那肯定会是7F 45 4C 46...因为一个ELF文件的Magic属性如此。
Memory.copy(r,module.base,10);
console.log(hexdump(r, {
offset: 0,
length: 10,
header: true,
ansi: false
}));
});
}
setImmediate(frida_Memory,0);

写入内存Memory.writeByteArray

将字节数组写入一个指定内存,代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function frida_Memory() {     
Java.perform(function () {
//定义需要写入的字节数组 这个字节数组是字符串"roysue"的十六进制
var arr = [ 0x72, 0x6F, 0x79, 0x73, 0x75, 0x65];
//申请一个新的内存空间 返回指针 大小是arr.length
const r = Memory.alloc(arr.length);
//将arr数组写入R地址中
Memory.writeByteArray(r,arr);
//输出
console.log(hexdump(r, {
offset: 0,
length: arr.length,
header: true,
ansi: false
}));
});
}
setImmediate(frida_Memory,0);

输出如下。
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 72 6f 79 73 75 65 roysue

读取内存Memory.readByteArray

将一个指定地址的数据按照 c 中bytearray进行读取

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
function frida_Memory() {     
Java.perform(function () {
//定义需要写入的字节数组 这个字节数组是字符串"roysue"的十六进制
var arr = [ 0x72, 0x6F, 0x79, 0x73, 0x75, 0x65];
//申请一个新的内存空间 返回指针 大小是arr.length
const r = Memory.alloc(arr.length);
//将arr数组写入R地址中
Memory.writeByteArray(r,arr);
//读取r指针,长度是arr.length 也就是会打印上面一样的值
var buffer = Memory.readByteArray(r, arr.length);
//输出
console.log("Memory.readByteArray:");
console.log(hexdump(buffer, {
offset: 0,
length: arr.length,
header: true,
ansi: false
}));
});
});
}
setImmediate(frida_Memory,0);

//输出如下。
[Google Pixel::com.roysue.roysueapplication]-> Memory.readByteArray:
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 72 6f 79 73 75 65 roysue

hook fgets 让 TracerPid 为 0

1
2
3
4
5
6
7
8
9
10
11
12
13
var anti_fgets = function () {
var fgetsPtr = Module.findExportByName("libc.so", "fgets");
var fgets = new NativeFunction(fgetsPtr, 'pointer', ['pointer', 'int', 'pointer']);
Interceptor.replace(fgetsPtr, new NativeCallback(function (buffer, size, fp) {
var retval = fgets(buffer, size, fp);
var bufstr = Memory.readUtf8String(buffer);
if (bufstr.indexOf("TracerPid:") > -1) {
console.log(bufstr); //此代码由我添加,打印TracerPid的值
Memory.writeUtf8String(buffer, "TracerPid:\t0");
}
return retval;
}, 'pointer', ['pointer', 'int', 'pointer']));
};

frida 批量 trace

  1. 使用 native_trace 对关键函数进行批量 trace, 获取 trace.log
  2. 使用 python 脚本提取其中的地址, 编辑了模板快速使用 trace
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import re

native_trace = """
16071 ms | sub_1494c4() ;debug 最终结果出现位置 (md5resultPtr, len, resultPtr)
16071 ms | sub_2546c()
16071 ms | sub_28d18()
16071 ms | | sub_bc028()
16071 ms | | | sub_cf970()
16071 ms | | | | sub_d4680()
"""

addr_list = re.findall(r"sub_(.*)\(\)", native_trace)
addr_list = list(map(lambda x:"0x" + x, addr_list))
print(addr_list)
  1. 使用 trace 出来的地址使用 frida 批量进行 hook; 编辑了模板快速使用 print_argtrace
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
// native
var base = Process.findModuleByName("libhello-jni.so").base;

function print_arg(addr) {
var range = Process.findRangeByAddress(addr);
if (range != null) {
return hexdump(addr) + '\r\n';
}else{
return ptr(addr) + "\r\n";
}
}

function native_trace(addr) {
var offset = addr;
addr = base.add(addr);
Interceptor.attach(addr, {
onEnter: function (args) {
// frida 设计的时候就无法获知 args 的长度
console.log("======================onenter sub_" + offset + "==========================");
for (var i = 0; i < 5; i++) {
eval("this.arg" + i + "=args[" + i + "]");
console.log("this.arg" + i, print_arg(eval("this.arg" + i)));
}

},
onLeave: function (returnValue) {
console.log("======================onleave sub_" + offset + "==========================");
for (var i = 0; i < 5; i++) {
console.log("this.arg" + i, print_arg(eval("this.arg" + i)))
}
console.log("returnVlaue", print_arg(returnValue));
}
})
}

function main() {
var trace_list = ['0x1494c4'];
for (var i = 0; i < trace_list.length; i++) {
native_trace(trace_list[i]);
}
}

setImmediate(main);

致谢

  • r0ysue
  • yang