抖音数据采集 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
免责申明:本文档仅供学习与参考,请勿用于非法用处!否则所有后果自负。