抖音數據採集Frida教程,rpc、Process、Module、Memory使用方法及示例

抖音數據採集Frida教程,rpc、Process、Module、Memory使用方法及示例

前言

你們好,窩又來寫文章了,我們如今在這篇文章中,咱們來對其官方的一些很是經常使用的API進行學習。所謂工欲善其事,必先利其器。想要好好學習FRIDA咱們就必須對FRIDA API深刻的學習以對其有更深的瞭解和使用,一般大部分核心原理也在官方API中寫着,咱們學會來使用一些案例來結合API的使用。
注意,運行如下任何代碼時都須要提早啓動手機中的frida-server文件。


python

1.1 FRIDA輸出打印

1.1.1 console輸出

不管是什麼語言都好,第一個要學習老是如何輸出和打印,那咱們就來學習在FRIDA打印值。在官方API有兩種打印的方式,分別是consolesend,咱們先來學習很是的簡單的console,這裏我建立一個js文件,代碼示例以下。android

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文件。shell

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 代碼示例


image.png
圖1-1 終端執行
能夠到終點已經成功注入了腳本而且打印了hello,可是顏色不一樣,這是log的級別的緣由,在FRIDAconsole中有三個級別分別是log、warn、errorjson

級別 含義
log 正常
warn 警告
error 錯誤

1.1.2 console之hexdump

error級別最爲嚴重其次warn,可是通常在使用中咱們只會使用log來輸出想看的值;而後咱們繼續學習console的好兄弟,hexdump,其含義:打印內存中的地址,target參數能夠是ArrayBuffer或者NativePointer,而options參數則是自定義輸出格式能夠填這幾個參數offset、lengt、header、ansi
hexdump代碼示例以及執行效果以下。ubuntu

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示例以及執行效果。api

# -*- 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變量類型

學完輸出以後咱們來學習如何聲明變量類型。session

API 含義
new Int64(v) 定義一個有符號Int64類型的變量值爲v,參數v能夠是字符串或者以0x開頭的的十六進制值
new UInt64(v) 定義一個無符號Int64類型的變量值爲v,參數v能夠是字符串或者以0x開頭的的十六進制值
new NativePointer(s) 定義一個指針,指針地址爲s
ptr(「0」) 同上

代碼示例以及效果app

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。
image.png
圖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的返回的結果爲5sub則是2,下見下圖1-3
image.png
圖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屬性,nameso模塊的名稱。見下圖1-4
image.png
圖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
image.png
圖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
image.png
圖1-6 終端執行
咱們要如何驗證搜索究竟是不是圖1-5112A地址,其實很簡單。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字節的空間<br>能夠看到在`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

免責聲明:本文檔僅供學習與參考,請勿用於非法用途!不然一切後果自負。

相關文章
相關標籤/搜索