frida使用与常见api(一)

零、基本使用

  • frida -U -l hello_world.js com.android.settings 挂载脚本

  • ./fs1287amd64 -l 0.0.0.0:6666 自定义端口

  • 脚本智能补全

    1
    2
    3
    4
    git clone <https://github.com/oleavr/frida-agent-example.git>
    cd frida-agent-example/
    npm install
    ## 使用vscode打开此工程,在agent文件夹下编写ts,会有智能提示。

一、输出

1. console

1.1console之log

在官方API有两种打印的方式,分别是console、send。

1
2
3
4
5
6
7
8
function printf() {
Java.perform(function () {
console.log("hello-log");
console.warn("hello-warn");
console.error("hello-error");
});
}
setImmediate(printf,0);

1.2console之hexdump

打印内存中的地址,target参数可以是ArrayBuffer或者NativePointer,而options参数则是自定义输出格式可以填这几个参数offset、length、header、ansi。

1
2
3
4
5
6
7
var libc =Module.findBaseAddress('libqservice.so');
console.log(hexdump(libc,{
offset:0,
length:64,
header:true,
ansi:true
}))

2. send

send是在python层定义的on_message回调函数,jscode内所有的信息都被监控script.on('message', on_message),当输出信息的时候on_message函数会拿到其数据再通过format转换。

核心作用:的是能够直接将数据以json格式输出,(数据是二进制的时候)也可以使用send。

注:使用send的时候会自动将对象转json格式输出

1
2
3
4
5
6
Java.perform(function () 
{
var jni_env = Java.vm.getEnv();
console.log("console.log方式输出--->"+jni_env);
send("send方式输出--->"+jni_env);
});

console直接输出了[object Object],无法输出其正常的内容,因为jni_env实际上是一个对象,但是使用send的时候会自动将对象转json格式输出。

3 变量类型

索引API含义
new Int64(v)定义一个有符号Int64类型的变量值为v,参数v可以是字符串或者以0x开头的的十六进制值
2new UInt64(v)定义一个无符号Int64类型的变量值为v,参数v可以是字符串或者以0x开头的的十六进制值
3new NativePointer(s)定义一个指针,指针地址为s;可简写为ptr(s)
ptr(“0”)同上;NULL的简写

二、Process常用api

1.Process.id

1
Process.id:返回附加目标进程的PID

2 Process.isDebuggerAttached()

Process.isDebuggerAttached():检测当前是否对目标程序已经附加

3 Process.enumerateModules()

枚举当前加载的模块,返回模块对象的数组。Process.enumerateModules()会枚举当前所有已加载的so模块,并且返回了数组Module对象。

4 Process.getCurrentThreadId()

Process.getCurrentThreadId():获取此线程的操作系统特定 ID 作为值。

1
2
3
4
5
6
7
8
9
10
11
12
function frida_Process() {
Java.perform(function () {
var process_Obj_Module_Arr = Process.enumerateModules();
for(var i = 0; i < process_Obj_Module_Arr.length; i++) {
console.log("模块名字---->"+process_Obj_Module_Arr[i].name);
console.log("pid---->"+Process.id)
console.log("是否对目标程序已经附加---->"+Process.isDebuggerAttached())
console.log("当前线程id—-->"+ Process.getCurrentThreadId())
}
});
}
setImmediate(frida_Process,0);

5 Process.enumerateThreads()

Process.enumerateThreads():枚举当前所有的线程,

1
2
3
4
5
6
7
8
9
10
11
12
function frida_Process() {
Java.perform(function () {
var enumerateThreads = Process.enumerateThreads();
for(var i = 0; i < enumerateThreads.length; i++) {
console.log("");
console.log("id:",enumerateThreads[i].id);
console.log("state:",enumerateThreads[i].state);
console.log("context:",JSON.stringify(enumerateThreads[i].context));
}
});
}
setImmediate(frida_Process,0);

三、Module常用api

1 Module对象的属性

  • name:规范模块名称作为字符串
  • base:基地址为 NativePointer
  • size:字节大小
  • path:完整的文件系统路径作为字符串
1
2
3
4
5
6
7
8
9
10
11
12
function frida_Modle(){
Java.perform(function(){
var process_Obj_Module_Arr=Process.enumerateModules()
for (var i=0; i<process_Obj_Module_Arr.length;i++){
console.log("模块的名字--->"+process_Obj_Module_Arr[i].name);
console.log("模块的基址--->"+process_Obj_Module_Arr[i].base);
console.log("模块的大小--->"+process_Obj_Module_Arr[i].size);
console.log("模块的路径--->"+process_Obj_Module_Arr[i].path);
}
});
}
setImmediate(frida_Modle,0);

执行结果:

1
2
3
4
5
模块的名字--->SettingsGoogle.odex
模块的基址--->0x70df8ab000
模块的大小--->14770176
模块的路径--->/system/priv-app/SettingsGoogle/oat/arm64/SettingsGoogle.odex
......

2 常见Module对象的API

2.1 Module.load(path)

加载指定so文件,返回一个Module对象。如果无法加载指定的模块,则会引发异常。

1
2
3
4
5
6
7
function frida_Module(){
Java.perform(function(){
var loadModule=Module.load("libxposed_art.so");
console.log("模块名字--->"+loadModule.name)
});
}
setImmediate(frida_Module,0)

2.2 Process.enumerateModules()

枚举所有的加载的模块,并返加Module数组对象

2.3 enumerateExports()

枚举模块中所有Import库函数,返回Module数组对象

  • type:函数类型
  • name:函数名称
  • address:函数地址
1
2
3
4
5
6
7
8
9
10
11
12
13
function frida_Module(){
Java.perform(function(){
var loadModule=Module.load("libxposed_art.so");
var moduleExport=loadModule.enumerateExports();
for (var i=0; i<moduleExport.length;i++){
console.log("name--->"+moduleExport[i].name);
console.log("tyep--->"+moduleExport[i].type);
console.log("address--->"+moduleExport[i].address);
console.log("-----------")
}
});
}
setImmediate(frida_Module,0)

执行结果如下:

1
2
3
4
name--->_ZN6xposed39XposedBridge_invokeOriginalMethodNativeEP7_JNIEnvP7_jclassP8_jobjectiP13_jobjectArrayS3_S5_S7_
tyep--->function
address--->0x70faf3b1c4
-----------

2.4 enumerateImports()

枚举模块中所有Export库函数,返回Module数组对象

  • type:函数类型
  • name:函数名称
  • module:模块名称作为字符串
  • address:函数地址

具体用法同2.3

2.5 enumerateSymbols()

枚举模块中所有Symbol库函数,返回Module数组对象

  • isGloba:布尔值,指定符号是否全局可见
  • type:函数类型
  • name:符号名称作为字符串
  • address:绝对地址
  • size:如果存在,则一个数字,以字节为单位指定符号的大小

具体用法同2.3

2.6 Module.ensureInitialized(name):

确保已运行指定模块的初始化程序。这对于早期检测非常重要,例如,代码在流程生命周期的早期运行,以便能够与API安全交互。

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

1
用法:findExportByName(moduleName,exportName)

返回so文件中Export函数库中函数名称为exportName函数的绝对地址。如果不知道该模块,则可以通过它null代替其名称,但这可能是一项代价高昂的搜索,应避免使用。如果找不到这样的模块或导出,则findExportByName函数将返回null,而getExportByName函数将引发异常。

1
2
3
4
5
6
7
8
9
10
function frida_Module(){
Java.perform(function(){
var loadModule=Module.load("libxposed_art.so");
var getExportName= Module.getExportByName('libxposed_art.so','_ZN6xposed36methodXposedBridgeHandleHookedMethodE')
var findExportName= Module.findExportByName('libxposed_art.so','_ZN6xposed36methodXposedBridgeHandleHookedMethodE')
console.log("getExportByName----->"+getExportName)
console.log("findExportByName----->"+findExportName)
});
}
setImmediate(frida_Module,0)

执行结果:

1
2
getExportByName----->0x70faf42028
findExportByName----->0x70faf42028

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

返回name 模块的基地址。如果找不到此类模块,则findBaseAddress函数将返回null,而getBaseAddress函数将引发异常。

1
2
3
4
5
6
7
8
9
10
function frida_Module(){
Java.perform(function(){
var loadModule=Module.load("libxposed_art.so");
var findAddress=Module.findBaseAddress(loadModule.name);
var findAddress2=Module.getBaseAddress(loadModule.name);
console.log("findBaseAddress----->"+findAddress)
console.log("getBaseAddress----->"+findAddress2)
});
}
setImmediate(frida_Module,0)

执行结果

1
2
findBaseAddress----->0x70faf36000
getBaseAddress----->0x70faf36000

四、Memory常用api

1 搜索数据

1.1 Memory.scan

Memory.scan(address, size, pattern, callbacks):扫描内存以查找pattern在address和给定的内存范围内的情况size。此函数相当于搜索内存的功能。

1.2 Memory.scanSync

返回多个匹配到条件的数据

注:pattern参数必须是双字符的格式,如‘1f 2d ?f’,不能是‘1f 2d ?’

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 () {
//
const m = Process.enumerateModules()[0];
console.log("进程的名字是--->"+m.name)
console.log(hexdump(m.base));
const pattern = '7f 45 ?? 46 ?? 01';
Memory.scan(m.base, m.size, pattern, {
onMatch(address, size) {
console.log('Memory.scan()在'+ address+'处找到,大小为'+ size+'字节');
return 'stop';
},
onComplete() {
console.log('Memory.scan()执行完成');
}
});
const results = Memory.scanSync(m.base, m.size, pattern);
console.log('Memory.scanSync() result:\\n' +JSON.stringify(results));
});
}
setImmediate(frida_Memory,0);

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
进程的名字是--->app_process64_xposed
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
555c99b000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 .ELF............
555c99b010 03 00 b7 00 01 00 00 00 e8 8c 00 00 00 00 00 00 ................
555c99b020 40 00 00 00 00 00 00 00 98 b6 01 00 00 00 00 00 @...............
555c99b030 00 00 00 00 40 00 38 00 09 00 40 00 1f 00 1c 00 ....@.8...@.....
555c99b040 06 00 00 00 04 00 00 00 40 00 00 00 00 00 00 00 ........@.......
555c99b050 40 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 @.......@.......
555c99b060 f8 01 00 00 00 00 00 00 f8 01 00 00 00 00 00 00 ................
555c99b070 08 00 00 00 00 00 00 00 03 00 00 00 04 00 00 00 ................
555c99b080 38 02 00 00 00 00 00 00 38 02 00 00 00 00 00 00 8.......8.......
555c99b090 38 02 00 00 00 00 00 00 15 00 00 00 00 00 00 00 8...............
555c99b0a0 15 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
555c99b0b0 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 ................
555c99b0c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
555c99b0d0 bc 14 01 00 00 00 00 00 bc 14 01 00 00 00 00 00 ................
555c99b0e0 00 10 00 00 00 00 00 00 01 00 00 00 06 00 00 00 ................
555c99b0f0 90 17 01 00 00 00 00 00 90 27 01 00 00 00 00 00 .........'......
Memory.scanSync() result:
[{"address":"0x555c99b000","size":6}]
Memory.scan()在0x555c99b000处找到,大小为6字节
Memory.scan()执行完成

2 内存分配Memory.alloc

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

3 内存复制Memory.copy

Memory.copy(dst, src, n),不返回任何内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function frida_Memory() {     
Java.perform(function () {
//
const m = Process.enumerateModules()[0];
console.log("进程的名字是--->"+m.name)
console.log(hexdump(m.base));
//申请一个内存空间大小为10个字节
const r =Memory.alloc(10);
//复制
Memory.copy(r,m.base,10);
console.log("Memory.copy---->")
console.log(hexdump(r,{
offset: 0,
length: 10,
header: true,
ansi: false
}))
});
}
setImmediate(frida_Memory,0);

4 写入数据Memory.writeByteArray

将字节数组写入一个指定内存。

5 读取数据Memory.readByteArray

注:读取内存数据时,只能用hexdump读取16进制数据

写入数据与读取数据示例:

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
function frida_Memory() {     
Java.perform(function () {
// >>> binascii.b2a_hex('lngwu'.encode('utf-8'))
// >>> b'6c696e677775'
//定义需要写入的字节数组
var arr = [ 0x6c, 0x69, 0x6e, 0x67, 0x77, 0x75];
//申请一个新的内存空间 返回指针 大小是arr.length
const r = Memory.alloc(arr.length);

console.log("4.写入数据----->"+ r)
//4.将arr数组写入R地址中
Memory.writeByteArray(r,arr);
//5.读取内存数据,长度是arr.length
var buffer = Memory.readByteArray(r, arr.length);

//输出 只能用16进制读取。
console.log("5.读取数据----->");
console.log(hexdump(buffer, {
offset: 0,
length: arr.length,
header: true,
ansi: false
}));
});
}
setImmediate(frida_Memory,0);

显示结果如下:

1
2
3
4
4.写入数据----->0x799f3a1cf0
5.读取数据----->
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 6c 69 6e 67 77 75 lingwu