抖音數據採集Frida教程,Java、Interceptor、NativePointer(Function/Callback)使用方法及示例

抖音數據採集Frida教程,Java、Interceptor、NativePointer(Function/Callback)使用方法及示例

注意,運行如下任何代碼時都須要提早啓動手機中的frida-server文件。
java

1.1 Java對象

Java是十分哦不,應該說是極其重要的API,不管是想對so層亦或java層進行攔截,都必須編寫Java.perform,在使用上面這些API時,應該都已經發現了吧這章咱們就來詳細看看`Java`對象都有哪些`API`python

1.1.1 Java.available

該函數通常用來判斷當前進程是否加載了JavaVM,DalvikART虛擬機,我們來看代碼示例!android

function frida_Java() {
    Java.perform(function () {
        //做爲判斷用
        if(Java.available)
        {
            //注入的邏輯代碼
            console.log("hello java vm");
        }else{
            //未能正常加載JAVA VM
            console.log("error");
        }
    });
}       
setImmediate(frida_Java,0);
輸出以下。
hello java vm

核心注入的邏輯代碼寫在<注入的邏輯代碼>內會很是的安全萬無一失~api

1.1.2 Java.androidVersion

顯示android系統版本號數組

function frida_Java() {
    Java.perform(function () {
        //做爲判斷用
        if(Java.available)
        {
            //注入的邏輯代碼
            console.log("",Java.androidVersion);
        }else{
            //未能正常加載JAVA VM
            console.log("error");
        }
    });
}       
setImmediate(frida_Java,0);
輸出以下。
9
由於個人系統版本是9版本~

1.1.3 枚舉類Java.enumerateLoadedClasses

該API枚舉當前加載的全部類信息,它有一個回調函數分別是onMatch、onComplete函數,咱們來看看代碼示例以及效果!安全

function frida_Java() {
    Java.perform(function () {
        if(Java.available)
        {
            //console.log("",Java.androidVersion);
            //枚舉當前加載的全部類
            Java.enumerateLoadedClasses({
                //每一次回調此函數時其參數className就是類的信息
                onMatch: function (className)
                {
                    //輸出類字符串
                    console.log("",className);
                },
                //枚舉完畢全部類以後的回調函數
                onComplete: function ()
                {
                    //輸出類字符串
                    console.log("輸出完畢");
                }
            });
        }else{
            console.log("error");
        }
    });
}       
setImmediate(frida_Java,0);

我們來看執行的效果圖1-7。
image.png
圖1-7 終端執行
它還有一個好兄弟 Java.enumerateLoadedClassesSync(),它返回的是一個數組。服務器

1.1.4 枚舉類加載器Java.enumerateLoadedClasses

api枚舉Java VM中存在的類加載器,其有一個回調函數,分別是onMatch: function (loader)onComplete: function (),接着咱們來看代碼示例。app

function frida_Java() {
    Java.perform(function () {
        if(Java.available)
        {
            //枚舉當前加載的Java VM類加載器
            Java.enumerateClassLoaders({
                //回調函數,參數loader是類加載的信息
                onMatch: function (loader)
                {
                    console.log("",loader);
                },
                //枚舉完畢全部類加載器以後的回調函數
                onComplete: function ()
                {
                    console.log("end");
                }
            });
        }else{
            console.log("error");
        }
    });
}       
setImmediate(frida_Java,0);

執行的效果圖1-8。
image.png
圖1-8 終端執行
它也有一個好兄弟叫Java.enumerateClassLoadersSync()也是返回的數組。函數

1.1.5 附加調用Java.perform

API極其重要,Java.perform(fn)主要用於當前線程附加到Java VM而且調用fn方法。咱們來看看示例代碼及其含義。學習

function frida_Java() {
    //運行當前js腳本時會對當前線程附加到Java VM虛擬機,而且執行function方法
    Java.perform(function () {
        //判斷是否Java VM正常運行
        if(Java.available)
        {
            //如不意外會直接輸出 hello
            console.log("hello");
        }else{
            console.log("error");
        }
    });
}       
setImmediate(frida_Java,0);
輸出以下。
[Google Pixel::com.roysue.roysueapplication]-> hello

沒錯你猜對了,它也有一個好兄弟。Java.performNow(fn)~

1.1.6 獲取類Java.use

Java.use(className),動態獲取className的類定義,經過對其調用$new()來調用構造函數,能夠從中實例化對象。當想要回收類時能夠調用$Dispose()方法顯式釋放,固然也能夠等待JavaScript的垃圾回收機制,當實例化一個對象以後,能夠經過其實例對象調用類中的靜態或非靜態的方法,官方代碼示例定義以下。

Java.perform(function () {
  //獲取android.app.Activity類
  var Activity = Java.use('android.app.Activity');
  //獲取java.lang.Exception類
  var Exception = Java.use('java.lang.Exception');
  //攔截Activity類的onResume方法
  Activity.onResume.implementation = function () {
    //調用onResume方法的時候,會在此處被攔截而且調用如下代碼拋出異常!
    throw Exception.$new('Oh noes!');
  };
});

1.1.7 掃描實例類Java.choose

在堆上查找實例化的對象,示例代碼以下!

Java.perform(function () {
    //查找android.view.View類在堆上的實例化對象
    Java.choose("android.view.View", {
        //枚舉時調用
        onMatch:function(instance){
            //打印實例
            console.log(instance);
        },
        //枚舉完成後調用
        onComplete:function() {
            console.log("end")
        }});
});
輸出以下:
android.view.View{2292774 V.ED..... ......ID 0,1794-1080,1920 #1020030 android:id/navigationBarBackground}
android.view.View{d43549d V.ED..... ......ID 0,0-1080,63 #102002f android:id/statusBarBackground}
end

1.1.8 類型轉換器Java.cast

Java.cast(handle, klass),就是將指定變量或者數據強制轉換成你全部須要的類型;建立一個 JavaScript 包裝器,給定從 Java.use() 返回的給定類klas的句柄的現有實例。此類包裝器還具備用於獲取其類的包裝器的類屬性,以及用於獲取其類名的字符串表示的$className屬性,一般在攔截so層時會使用此函數將jstring、jarray等等轉換以後查看其值。

1.1.9 定義任意數組類型Java.array

frida提供了在js代碼中定義java數組的api,該數組能夠用於傳遞給java API,咱們來看看如何定義,代碼示例以下。

Java.perform(function () {
        //定義一個int數組、值是1003, 1005, 1007
        var intarr = Java.array('int', [ 1003, 1005, 1007 ]);
        //定義一個byte數組、值是0x48, 0x65, 0x69
        var bytearr = Java.array('byte', [ 0x48, 0x65, 0x69 ]);
        for(var i=0;i<bytearr.length;i++)
        {
            //輸出每一個byte元素
            console.log(bytearr[i])
        }
});

咱們經過上面定義int數組和byte的例子能夠知道其定義格式爲Java.array('type',[value1,value2,....]);那它都支持type呢?咱們來看看~

type 含義
Z boolean
B byte
C char
S short
I int
J long
F float
D double
V void

1.1.10 註冊類Java.registerClass(spec)

Java.registerClass:建立一個新的Java類並返回一個包裝器,其中規範是一個包含:
name:指定類名稱的字符串。
superClass:(可選)父類。要從 java.lang.Object 繼承的省略。
implements:(可選)由此類實現的接口數組。
fields:(可選)對象,指定要公開的每一個字段的名稱和類型。
methods:(可選)對象,指定要實現的方法。
註冊一個類,返回類的實例,下面我貼一個基本的用法~實例化目標類對象而且調用類中的方法

Java.perform(function () {
          //註冊一個目標進程中的類,返回的是一個類對象
          var hellojni = Java.registerClass({
            name: 'com.roysue.roysueapplication.hellojni'
          });
          console.log(hellojni.addInt(1,2));
});

咱們再深刻看看官方怎麼來玩的:

//獲取目標進程的SomeBaseClass類
var SomeBaseClass = Java.use('com.example.SomeBaseClass');
//獲取目標進程的X509TrustManager類
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
var MyWeirdTrustManager = Java.registerClass({
  //註冊一個類是進程中的MyWeirdTrustManager類
  name: 'com.example.MyWeirdTrustManager',
  //父類是SomeBaseClass類
  superClass: SomeBaseClass,
  //實現了MyWeirdTrustManager接口類
  implements: [X509TrustManager],
  //類中的屬性
  fields: {
    description: 'java.lang.String',
    limit: 'int',
  },
  //定義的方法
  methods: {
    //類的構造函數
    $init: function () {
      console.log('Constructor called');
    },
    //X509TrustManager接口中方法之一,該方法做用是檢查客戶端的證書
    checkClientTrusted: function (chain, authType) {
      console.log('checkClientTrusted');
    },
    //該方法檢查服務器的證書,不信任時。在這裏經過本身實現該方法,可使之信任咱們指定的任何證書。在實現該方法時,也能夠簡單的不作任何處理,即一個空的函數體,因爲不會拋出異常,它就會信任任何證書。
    checkServerTrusted: [{
      //返回值類型
      returnType: 'void',
      //參數列表
      argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String'],
      //實現方法
      implementation: function (chain, authType) {
         //輸出
        console.log('checkServerTrusted A');
      }
    }, {
      returnType: 'java.util.List',
      argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.lang.String'],
      implementation: function (chain, authType, host) {
        console.log('checkServerTrusted B');
        //返回null會信任全部證書
        return null;
      }
    }],
    // 返回受信任的X509證書數組。
    getAcceptedIssuers: function () {
      console.log('getAcceptedIssuers');
      return [];
    },
  }
});

咱們來看看上面的示例都作了啥?實現了證書類的javax.net.ssl.X509TrustManager類,,這裏就是至關於本身在目標進程中從新建立了一個類,實現了本身想要實現的類構造,重構造了其中的三個接口函數、從而繞過證書校驗。

1.1.11 Java.vm對象

Java.vm對象十分經常使用,好比想要拿到JNI層的JNIEnv對象,可使用getEnv();咱們來看看具體的使用和基本小實例。~

function frida_Java() {     
    Java.perform(function () {
         //攔截getStr函數
         Interceptor.attach(Module.findExportByName("libhello.so" , "Java_com_roysue_roysueapplication_hellojni_getStr"), {
            onEnter: function(args) {
                console.log("getStr");
            },
            onLeave:function(retval){
                //它的返回值的是retval 在jni層getStr的返回值的jstring 
                //咱們在這裏作的事情就是替換掉結果
                //先獲取一個Env對象
                var env = Java.vm.getEnv();
                //經過newStringUtf方法構建一個jstirng字符串
                var jstring = env.newStringUtf('roysue');
                //replace替換掉結果
                retval.replace(jstring);
                console.log("getSum方法返回值爲:roysue")
            }
    });
}
setImmediate(frida_Java,0);

1.2 Interceptor對象

該對象功能十分強大,函數原型是Interceptor.attach(target, callbacks):參數target是須要攔截的位置的函數地址,也就是填某個so層函數的地址便可對其攔截,target是一個NativePointer參數,用來指定你想要攔截的函數的地址,NativePointer咱們也學過是一個指針。須要注意的是對於Thumb函數須要對函數地址+1callbacks則是它的回調函數,分別是如下兩個回調函數:

1.2.1 Interceptor.attach

onEnter:函數(args):回調函數,給定一個參數args,可用於讀取或寫入參數做爲 NativePointer 對象的數組。
onLeave:函數(retval):回調函數給定一個參數 retval,該參數是包含原始返回值的 NativePointer 派生對象。能夠調用 retval.replace(1337) 以整數 1337 替換返回值,或者調用 retval.replace(ptr("0x1234"))以替換爲指針。請注意,此對象在 OnLeave 調用中回收,所以不要將其存儲在回調以外並使用它。若是須要存儲包含的值,請製做深副本,例如:ptr(retval.toString())
咱們來看看示例代碼~

//使用Module對象getExportByNameAPI直接獲取libc.so中的導出函數read的地址,對read函數進行附加攔截
Interceptor.attach(Module.getExportByName('libc.so', 'read'), {
  //每次read函數調用的時候會執行onEnter回調函數
  onEnter: function (args) {
    this.fileDescriptor = args[0].toInt32();
  },
  //read函數執行完成以後會執行onLeave回調函數
  onLeave: function (retval) {
    if (retval.toInt32() > 0) {
      /* do something with this.fileDescriptor */
    }
  }
});

經過咱們對Interceptor.attach函數有一些基本瞭解了~它還包含一些屬性
咱們來看看示例代碼。

function frida_Interceptor() {
    Java.perform(function () {
        //對So層的導出函數getSum進行攔截
        Interceptor.attach(Module.findExportByName("libhello.so" , "Java_com_roysue_roysueapplication_hellojni_getSum"), {
            onEnter: function(args) {
                //輸出
                console.log('Context information:');
                //輸出上下文因其是一個Objection對象,須要它進行接送、轉換才能正常看到值
                console.log('Context  : ' + JSON.stringify(this.context));
                //輸出返回地址
                console.log('Return   : ' + this.returnAddress);
                //輸出線程id
                console.log('ThreadId : ' + this.threadId);
                console.log('Depth    : ' + this.depth);
                console.log('Errornr  : ' + this.err);
            },
            onLeave:function(retval){
            }
        });
    });
}
setImmediate(frida_Interceptor,0);


咱們注入腳本以後來看看執行以後的效果以及輸出的這些都是啥,執行的效果圖1-9
image.png
圖1-9 終端執行

1.2.2 Interceptor.detachAll

簡單來講這個的函數的做用就是讓以前全部的Interceptor.attach附加攔截的回調函數失效。

1.2.3 Interceptor.replace

至關於替換掉本來的函數,用替換時的實現替換目標處的函數。若是想要徹底或部分替換現有函數的實現,則一般使用此函數。,咱們也看例子,例子是最直觀的!代碼以下。

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));
    });
}

我來看注入腳本以後的終端是是否是顯示了3123見下圖1-10
image.png
圖1-10 終端執行

1.3 NativePointer對象

同等與C語言中的指針

1.3.1 new NativePointer(s)

聲明定義NativePointer類型

function frida_NativePointer() {
    Java.perform(function () {
        //第一種字符串定義方式 十進制的100 輸出爲十六進制0x64
        const ptr1 = new NativePointer("100");
        console.log("ptr1:",ptr1);
        //第二種字符串定義方式 直接定義0x64 同等與定義十六進制的64
        const ptr2 = new NativePointer("0x64");
        console.log("ptr2:",ptr2);        
        //第三種定數值義方式 定義數字int類型 十進制的100 是0x64
        const ptr3 = new NativePointer(100);
        console.log("ptr3:",ptr3);
    });
}     
setImmediate(frida_NativePointer,0);
輸出以下,都會自動轉爲十六進制的0x64
ptr1: 0x64
ptr2: 0x64
ptr3: 0x64

1.3.2 運算符以及指針讀寫API

它也能調用如下運算符


看完API含義以後,咱們來使用他們,下面該腳本是readByteArray()示例~

function frida_NativePointer() {
    Java.perform(function () {
       console.log("");
        //拿到libc.so在內存中的地址
        var pointer = Process.findModuleByName("libc.so").base;
        //讀取從pointer地址開始的16個字節
        console.log(pointer.readByteArray(0x10));
    });
}     
setImmediate(frida_NativePointer,0);
輸出以下:
           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............

首先我先來用readByteArray函數來讀取libc.so文件在內存中的數據,這樣咱們方便測試,咱們從libc文件讀取0x10個字節的長度,確定會是7F 45 4C 46...由於ELF文件頭部信息中的Magic屬性。

1.3.3 readPointer()

我們直接從API索引11開始玩readPointer(),定義是今後內存位置讀取NativePointer,示例代碼以下。省略function以及Java.perform~

var pointer = Process.findModuleByName("libc.so").base;
    console.log(pointer.readByteArray(0x10));
    console.log("readPointer():"+pointer.readPointer());
    輸出以下。
    readPointer():0x464c457f

也就是將readPointer的前四個字節的內容轉成地址產生一個新的NativePointer

1.3.4 writePointer(ptr)

讀取ptr指針地址到當前指針

//先打印pointer指針地址
        console.log("pointer :"+pointer);
        //分配四個字節的空間地址
        const r = Memory.alloc(4);
        //將pointer指針寫入剛剛申請的r內
        r.writePointer(pointer);
        //讀取r指針的數據
        var buffer = Memory.readByteArray(r, 4);
        //r指針內放的pointer指針地址
        console.log(buffer);
輸出以下。
//console.log("pointer :"+pointer); 這句打印的地址 也就是libc的地址
pointer :0xf588f000
//console.log(buffer); 輸出buffer 0xf588f000在內存數據會以00 f0 88 f5方式顯示
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  00 f0 88 f5                                      ....

1.3.5 readS32()、readU32()

從該內存位置讀取有符號或無符號8/16/32/etc或浮點數/雙精度值,並將其做爲數字返回。這裏拿readS32()、readU32()做爲演示.

//從pointer地址讀4個字節 有符號
    console.log(pointer.readS32());
    //從pointer地址讀4個字節 無符號
    console.log(pointer.readU32());
輸出以下。
           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............
1179403647 == 0x464c457f
1179403647 == 0x464c457f

1.3.6 writeS32()、writeU32()

將有符號或無符號8/16/32/等或浮點數/雙精度值寫入此內存位置。

//申請四個字節的內存空間
    const r = Memory.alloc(4);
    //將0x12345678寫入r地址中
    r.writeS32(0x12345678);
    //輸出
    console.log(r.readByteArray(0x10));
// writeS32()、writeU32()輸出的也是同樣的,只是區別是有符號和無符號
輸出以下。
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  78 56 34 12 00 00 00 00 00 00 00 00 00 00 00 00  xV4.............

1.3.7 readByteArray(length))、writeByteArray(bytes)

readByteArray(length))連續讀取內存length個字節,、writeByteArray連續寫入內存bytes

//先定義一個須要寫入的字節數組
       var arr = [ 0x72, 0x6F, 0x79, 0x73, 0x75, 0x65];
       //這裏申請以arr大小的內存空間
       const r = Memory.alloc(arr.length);
       //將arr數組字節寫入r
       Memory.writeByteArray(r,arr);
       //讀取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
        }));
輸出以下。       
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

1.3.8 readCString([size = -1])、writeUtf8String(str)

readCString功能是讀取指針地址位置的字節字符串,對應的writeUtf8String是寫入指針地址位置的字符串處。(這裏的r是接着上面的代碼的變量)。

//在這裏直接使用readCString讀取會把上面的'roysue'字符串讀取出來
        console.log("readCString():"+r.readCString());
        //這裏是寫入字符串 也就是 roysue起始位置開始被替換爲haha
        const newPtrstr = r.writeUtf8String("haha");
        //替換完了以後再繼續輸出 必然是haha
        console.log("readCString():"+newPtrstr.readCString());


圖1-11 終端執行

1.4 NativeFunction對象

建立新的NativeFunction以調用address處的函數(用NativePointer指定),其中rereturn Type指定返回類型,argTypes數組指定參數類型。若是不是系統默認值,還能夠選擇指定ABI。對於可變函數,添加一個‘.’固定參數和可變參數之間的argTypes條目,咱們來看看官方的例子。

// LargeObject HandyClass::friendlyFunctionName();
//建立friendlyFunctionPtr地址的函數
var friendlyFunctionName = new NativeFunction(friendlyFunctionPtr,
    'void', ['pointer', 'pointer']);
//申請內存空間    
var returnValue = Memory.alloc(sizeOfLargeObject);
//調用friendlyFunctionName函數
friendlyFunctionName(returnValue, thisPtr);

我來看看它的格式,函數定義格式爲new NativeFunction(address, returnType, argTypes[, options]),參照這個格式可以建立函數而且調用!returnType和argTypes[,]分別能夠填void、pointer、int、uint、long、ulong、char、uchar、float、double、int八、uint八、int1六、uint1六、int3二、uint3二、int6四、uint64這些類型,根據函數的所須要的type來定義便可。
在定義的時候必需要將參數類型個數和參數類型以及返回值徹底匹配,假設有三個參數都是int,則new NativeFunction(address, returnType, ['int', 'int', 'int']),而返回值是intnew NativeFunction(address, 'int', argTypes[, options]),必需要所有匹配,而且第一個參數必定要是函數地址指針。

1.5 NativeCallback對象

new NativeCallback(func,rereturn Type,argTypes[,ABI]):建立一個由JavaScript函數func實現的新NativeCallback,其中rereturn Type指定返回類型,argTypes數組指定參數類型。您還能夠指定ABI(若是不是系統默認值)。有關支持的類型和Abis的詳細信息,請參見NativeFunction。注意,返回的對象也是一個NativePointer,所以能夠傳遞給Interceptor#replace。當將產生的回調與Interceptor.replace()一塊兒使用時,將調用func,並將其綁定到具備一些有用屬性的對象,就像Interceptor.Attach()中的那樣。咱們來看一個例子。以下,利用NativeCallback作一個函數替換。

Java.perform(function () {
       var add_method = new NativeFunction(Module.findExportByName('libhello.so', 'c_getSum'), 
       'int',['int','int']);
       console.log("result:",add_method(1,2));
       //在這裏new一個新的函數,可是參數的個數和返回值必須對應
       Interceptor.replace(add_method, new NativeCallback(function (a, b) {
            return 123;
       }, 'int', ['int', 'int']));
       console.log("result:",add_method(1,2));
    });

結語

本篇我們學習了很是實用的API,如Interceptor對象對so層導出庫函數攔截、NativePointer對象的指針操做、NativeFunction對象的實例化so函數的使用等都是當前灰常好用的函數建議童鞋了多多嘗試

短視頻、直播數據實時採集接口,請查看文檔: TiToData

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

相關文章
相關標籤/搜索