抖音数据采集Frida教程,rpc、Process、Module、Memory应用办法及示例
前言
大家好,窝又来写文章了,咱们当初在这篇文章中,咱们来对其官网的一些十分罕用的API
进行学习。所谓工欲善其事,必先利其器。想要好好学习FRIDA
咱们就必须对FRIDA API
深刻的学习以对其有更深的理解和应用,通常大部分外围原理也在官网API
中写着,咱们学会来应用一些案例来联合API
的应用。
留神,运行以下任何代码时都须要提前启动手机中的frida-server
文件。
1.1 FRIDA输入打印
1.1.1 console输入
不论是什么语言都好,第一个要学习总是如何输入和打印,那咱们就来学习在FRIDA
打印值。在官网API有两种打印的形式,别离是console
、send
,咱们先来学习十分的简略的console
,这里我创立一个js
文件,代码示例如下。
function hello_printf() {
Java.perform(function () {
console.log("");
console.log("hello-log");
console.warn("hello-warn");
console.error("hello-error");
});
}
setImmediate(hello_printf,0);
当文件创建好之后,咱们须要运行在手机中装置的frida-server
文件,在上一章咱们学过了如何装置在android
手机装置frida-server
,当初来应用它,咱们在ubuntu
中开启一个终端,运行以下代码,启动咱们装置好的frida-server
文件。
roysue@ubuntu:~$ adb shell
sailfish:/ $ su
sailfish:/ $ ./data/local/tmp/frida-server
而后执行以下代码,对指标利用app
的过程com.roysue.roysueapplication
应用-l
命令注入Chap03.js
中的代码1-1
以及执行脚本之后的效果图1-1
!frida -U com.roysue.roysueapplication -l Chap03.js
代码1-1 代码示例
图1-1 终端执行
能够到起点曾经胜利注入了脚本并且打印了hello
,然而色彩不同,这是log
的级别的起因,在FRIDA
的console
中有三个级别别离是log、warn、error
。
级别 | 含意 |
---|---|
log | 失常 |
warn | 正告 |
error | 谬误 |
1.1.2 console之hexdump
error
级别最为重大其次warn
,然而个别在应用中咱们只会应用log
来输入想看的值;而后咱们持续学习console
的好兄弟,hexdump
,其含意:打印内存中的地址,target
参数能够是ArrayBuffer
或者NativePointer
,而options
参数则是自定义输入格局能够填这几个参数offset、lengt、header、ansi
。hexdump
代码示例以及执行成果如下。
var libc = Module.findBaseAddress('libc.so');
console.log(hexdump(libc, {
offset: 0,
length: 64,
header: true,
ansi: true
}));
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 .ELF............
00000010 03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00 ..(.........4...
00000020 34 a8 04 00 00 00 00 05 34 00 20 00 08 00 28 00 4.......4. ...(.
00000030 1e 00 1d 00 06 00 00 00 34 00 00 00 34 00 00 00 ........4...4...
1.1.3 send
send
是在python
层定义的on_message
回调函数,jscode
内所有的信息都被监控script.on('message', on_message)
,当输入信息的时候on_message
函数会拿到其数据再通过format
转换, 其最重要的性能也是最外围的是可能间接将数据以json
格局输入,当然数据是二进制的时候也仍然是能够应用send
,非常不便,咱们来看代码1-2
示例以及执行成果。
# -*- coding: utf-8 -*-
import frida
import sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
jscode = """
Java.perform(function ()
{
var jni_env = Java.vm.getEnv();
console.log(jni_env);
send(jni_env);
});
"""
process = frida.get_usb_device().attach('com.roysue.roysueapplication')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()
运行脚本成果如下:
roysue@ubuntu:~/Desktop/Chap09$ python Chap03.py
[object Object]
[*] {'handle': '0xdf4f8000', 'vm': {}}
能够看出这里两种形式输入的不同的成果,console
间接输入了[object Object]
,无奈输入其失常的内容,因为jni_env
实际上是一个对象,然而应用send
的时候会主动将对象转json
格局输入。通过比照,咱们就晓得send
的益处啦~
1.2 FRIDA变量类型
学完输入之后咱们来学习如何申明变量类型。
API | 含意 |
---|---|
new Int64(v) | 定义一个有符号Int64类型的变量值为v,参数v能够是字符串或者以0x结尾的的十六进制值 |
new UInt64(v) | 定义一个无符号Int64类型的变量值为v,参数v能够是字符串或者以0x结尾的的十六进制值 |
new NativePointer(s) | 定义一个指针,指针地址为s |
ptr(“0”) | 同上 |
代码示例以及成果
Java.perform(function () {
console.log("");
console.log("new Int64(1):"+new Int64(1));
console.log("new UInt64(1):"+new UInt64(1));
console.log("new NativePointer(0xEC644071):"+new NativePointer(0xEC644071));
console.log("new ptr('0xEC644071'):"+new ptr(0xEC644071));
});
输入成果如下:
new Int64(1):1
new UInt64(1):1
new NativePointer(0xEC644071):0xec644071
new ptr('0xEC644071'):0xec644071
frida
也为Int64(v)
提供了一些相干的API:
API | 含意 |
---|---|
add(rhs)、sub(rhs)、and(rhs)、or(rhs)、xor(rhs) | 加、减、逻辑运算 |
shr(N)、shl(n) | 向右/向左移位n位生成新的Int64 |
Compare(Rhs) | 返回整数比拟后果 |
toNumber() | 转换为数字 |
toString([radix=10]) | 转换为可选基数的字符串(默认为10) |
我也写了一些应用案例,代码如下。
function hello_type() {
Java.perform(function () {
console.log("");
//8888 + 1 = 8889
console.log("8888 + 1:"+new Int64("8888").add(1));
//8888 - 1 = 8887
console.log("8888 - 1:"+new Int64("8888").sub(1));
//8888 << 1 = 4444
console.log("8888 << 1:"+new Int64("8888").shr(1));
//8888 == 22 = 1 1是false
console.log("8888 == 22:"+new Int64("8888").compare(22));
//转string
console.log("8888 toString:"+new Int64("8888").toString());
});
}
代码执行成果如图1-2。
图1-2 Int64 API
1.3 RPC近程调用
能够替换或插入的空对象,以向应用程序公开RPC
款式的API
。该键指定办法名称,该值是导出的函数。此函数能够返回一个纯值以立刻返回给调用方,或者承诺异步返回。也就是说能够通过rpc的导出的性能应用在python
层,使python
层与js
交互,官网示例代码有Node.js
版本与python
版本,咱们在这里应用python
版本,代码如下。
1.3.1 近程调用代码示例
import frida
def on_message(message, data):
if message['type'] == 'send':
print(message['payload'])
elif message['type'] == 'error':
print(message['stack'])
session = frida.get_usb_device().attach('com.roysue.roysueapplication')
source = """
rpc.exports = {
add: function (a, b) {
return a + b;
},
sub: function (a, b) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(a - b);
}, 100);
});
}
};
"""
script = session.create_script(source)
script.on('message', on_message)
script.load()
print(script.exports.add(2, 3))
print(script.exports.sub(5, 3))
session.detach()
1.3.2 近程调用代码示例详解
官网源码示例是附加在指标过程为iTunes
,再通过将rpc
的./agent.js
文件读取到source
,进行应用。我这里批改了附加的指标的过程以及间接将rpc
的代码定义在source
中。咱们来看看这段是咋运行的,依然先对指标过程附加,而后在写js
中代码,也是source
变量,通过rpc.exports
关键字定义须要导出的两个函数,下面定义了add
函数和sub
函数,两个的函数写作形式不一样,大家当前写依照add
办法写就好了,sub
略微有点简单。申明完函数之后创立了一个脚本并且注入过程,加载了脚本之后能够到print(script.exports.add(2, 3))
以及print(script.exports.sub(5, 3)),
在python
层间接调用。add
的返回的后果为5
,sub
则是2
,下见下图1-3
。
图1-3 执行python脚本
1.4 Process对象
咱们当初来介绍以及应用一些Process
对象中比拟罕用的api
~
1.4.1 Process.id
Process.id
:返回附加指标过程的PID
1.4.2 Process.isDebuggerAttached()
Process.isDebuggerAttached()
:检测以后是否对目标程序曾经附加
1.4.3 Process.enumerateModules()
枚举以后加载的模块,返回模块对象的数组。Process.enumerateModules()
会枚举以后所有已加载的so
模块,并且返回了数组Module
对象,Module
对象下一节咱们来具体说,在这里咱们临时只应用Module
对象的name
属性。
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);
}
});
}
setImmediate(frida_Process,0);
我来们开看看这段js
代码写了啥:在js
中可能间接应用Process
对象的所有api
,调用了Process.enumerateModules()
办法之后会返回一个数组,数组中存储N个叫Module的对象,既然曾经晓得返回了的是一个数组,很简略咱们就来for
循环它便是,这里我应用下标的形式调用了Module
对象的name
属性,name
是so
模块的名称。见下图1-4
。
图1-4 终端输入了所有已加载的so
1.4.4 Process.enumerateThreads()
Process.enumerateThreads()
:枚举以后所有的线程,返回蕴含以下属性的对象数组:
属性 | 含意 |
---|---|
id | 线程id |
state | 以后运行状态有running, stopped, waiting, uninterruptible or halted |
context | 带有键pc和sp的对象,它们是别离为ia32/x64/arm指定EIP/RIP/PC和ESP/RSP/SP的NativePointer对象。也能够应用其余处理器特定的密钥,例如eax、rax、r0、x0等。 |
应用代码示例如下:
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);
获取以后是所有线程之后返回了一个数组,而后循环输入它的值,如下图1-5
。
图1-4 终端执行
1.4.5 Process.getCurrentThreadId()
Process.getCurrentThreadId()
:获取此线程的操作系统特定 ID
作为数字
1.5 Module对象
3.4章节中Process.EnumererateModules()
办法返回了就是一个Module
对象,咱们这里来具体说说Module
对象,先来瞧瞧它都有哪些属性。
1.5.1 Module对象的属性
属性 | 含意 |
---|---|
name | 模块名称 |
base | 模块地址,其变量类型为NativePointer |
size | 大小 |
path | 残缺文件系统门路 |
除了属性咱们再来看看它有什么办法。
1.5.2 Module对象的API
API | 含意 |
---|---|
Module.load() | 加载指定so文件,返回一个Module对象 |
enumerateImports() | 枚举所有Import库函数,返回Module数组对象 |
enumerateExports() | 枚举所有Export库函数,返回Module数组对象 |
enumerateSymbols() | 枚举所有Symbol库函数,返回Module数组对象 |
Module.findExportByName(exportName)、Module.getExportByName(exportName) | 寻找指定so中export库中的函数地址 |
Module.findBaseAddress(name)、Module.getBaseAddress(name) | 返回so的基地址 |
1.5.3 Module.load()
在frida-12-5
版本中更新了该API
,次要用于加载指定so
文件,返回一个Module
对象。
应用代码示例如下:
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
1.5.4 Process.EnumererateModules()
咱们这一小章节就来应用Module
对象,把上章的Process.EnumererateModules()
对象输入给它补全了,代码如下。
function frida_Module() {
Java.perform(function () {
var process_Obj_Module_Arr = Process.enumerateModules();
for(var i = 0; i < process_Obj_Module_Arr.length; i++) {
if(process_Obj_Module_Arr[i].path.indexOf("hello")!=-1)
{
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_Module,0);
输入如下:
模块名称: libhello.so
模块地址: 0xdf2d3000
大小: 24576
文件系统门路 /data/app/com.roysue.roysueapplication-7adQZoYIyp5t3G5Ef5wevQ==/lib/arm/libhello.so
这边如果去除判断的话会打印所有加载的so
的信息,这里咱们就晓得了哪些办法返回了Module
对象了,而后咱们再持续深刻学习Module
对象自带的API
。
1.5.5 enumerateImports()
该API会枚举模块中所有中的所有Import函数,示例代码如下。
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
...
1.5.6 enumerateExports()
该API会枚举模块中所有中的所有Export
函数,示例代码如下。
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
...
1.5.7 enumerateSymbols()
代码示例如下。
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
...
1.5.8 Module.findExportByName(exportName), Module.getExportByName(exportName)
返回so
文件中Export
函数库中函数名称为exportName
函数的相对地址。
代码示例如下。
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
1.5.9 Module.findBaseAddress(name)、Module.getBaseAddress(name)
返回name
模块的基地址。
代码示例如下。
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
1.6 Memory对象
Memory
的一些API
通常是对内存解决,譬如Memory.copy()
复制内存,又如writeByteArray
写入字节到指定内存中,那咱们这章中就是学习应用Memory API
向内存中写入数据、读取数据。
1.6.1 Memory.scan搜寻内存数据
其次要性能是搜寻内存中以address
地址开始,搜寻长度为size
,须要搜是条件是pattern,callbacks
搜寻之后的回调函数;此函数相当于搜寻内存的性能。
咱们来间接看例子,而后联合例子解说,如下图1-5
。
图1-5 IDA中so文件某处数据
如果我想搜寻在内存中112A
地址的起始数据要怎么做,代码示例如下。
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
条件的在内存中能匹配的数据。搜寻到之后依据回调函数返回数据。
咱们来看看执行之后的效果图1-6
。
图1-6 终端执行
咱们要如何验证搜寻到底是不是图1-5
中112A
地址,其实很简略。so
的基址是0xdf2d3000
,而搜到的地址是0xdf2d412a
,咱们只有df2d412a-df2d3000=112A
。就是说咱们曾经搜寻到了!
1.6.2 搜寻内存数据Memory.scanSync
性能与Memory.scan
一样,只不过它是返回多个匹配到条件的数据。
代码示例如下。
function frida_Memory() {
Java.perform(function () {
var module = Process.findModuleByName("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}]
1.6.3 内存调配Memory.alloc
在指标过程中的堆上申请size
大小的内存,并且会依照Process.pageSize
对齐,返回一个NativePointer
,并且申请的内存如果在JavaScript
外面没有对这个内存的应用的时候会主动开释的。也就是说,如果你不想要这个内存被开释,你须要本人保留一份对这个内存块的援用。
应用案例如下
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);
以上代码在指标过程中申请了10
字节的空间~
能够看到在0xdfe4cd40
处申请了10
个字节内存空间~
也能够应用:Memory.allocUtf8String(str)
调配utf字符串Memory.allocUtf16String
调配utf16字符串Memory.allocAnsiString
调配ansi字符串
1.6.4 内存复制Memory.copy
如同c api memcp
一样调用,应用案例如下。
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);
输入如下。
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
e8142070 7f 45 4c 46 01 01 01 00 00 00 .ELF......
从module.base
中复制10
个字节的内存到新年申请的r
内
1.6.6 写入内存Memory.writeByteArray
将字节数组写入一个指定内存,代码示例如下:
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
1.6.7 读取内存Memory.readByteArray
将一个指定地址的数据,代码示例如下:
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
结语
在这篇中咱们学会了在FRIDACLI中如何输入想要输入格局,也学会如何申明变量,一步步的学习。在逐渐的学习的过程,总是会遇到不同的问题。歌曲<奇观再现>我置信你肯定听过吧~,新的风暴曾经呈现,怎么可能停止不前..遇到问题不要怕,总会解决的。
短视频、直播数据实时采集接口,请查看文档: TiToData
免责申明:本文档仅供学习与参考,请勿用于非法用处!否则所有后果自负。
发表回复