hook工具frida原理及使用

一,原理簡介:

  1. 手機端安裝一個server程序
  2. 然后把手機端的端口轉到PC端,
  3. PC端寫python腳本進行通信,python腳本中需要hook的代碼采用javascript語言。

frida使用的是動態二進制插樁技術DBI),首先來了解一下插樁技術:

插樁技術是指將額外的代碼注入程序中以收集運行時的信息,可分為兩種:
(1)源代碼插樁[Source Code Instrumentation(SCI)]:顧名思義,在程序源代碼的基礎上增加(注入)額外的代碼,從而達到預期目的或者功能;

源代碼插樁實例

(2)二進制插樁(Binary Instrumentation):額外代碼注入到二進制可執行文件中,通過修改匯編地址,改變程序運行內容,運行后再返回到原來程序運行出處,從而實現程序的額外功能。
●靜態二進制插樁[Static Binary Instrumentation(SBI)]:在程序執行前插入額外的代碼和數據,生成一個永久改變的可執行文件。
●動態二進制插樁[Dynamic Binary Instrumentation(DBI)]:在程序運行時實時地插入額外代碼和數據,對可執行文件沒有任何永久改變。*

DBI能做什么?

(1)訪問進程的內存
(2)在應用程序運行時覆蓋一些功能
(3)從導入的類中調用函數
(4)在堆上查找對象實例并使用這些對象實例
(5)Hook,跟蹤和攔截函數等等

frida原理和xposed差不多,文章寫完才發現發現這里有一篇m4bln大神的文章對原理解釋更清楚,解析了frida的源碼,從dalvik和art的啟動底層原理講起,Frida源碼分析
下面重點講下frida的使用。

二,安裝

首先通過上面的介紹,明白frida是通過本地設備與遠程設備端通信實現hook腳本傳遞注入的,所以可以確定要有本地客戶端和遠程服務端兩部分組成:

1,本地客戶端是基于Python的,首先安裝python環境,然后使用 pip install frida

所以在遠程端一定要有一個服務端用來接收我們發過去的js代碼,即frida-server官方地址 , 其中包括各種平臺

服務端下載頁面

這里我們需要根據我們要hook設備的系統選擇相應的下載包,這里我們選擇Android系統
server

其實我們在這里還可以看到有很多其他的Android的選擇,如下,其實每類服務端都適用于不同的場景,
比如:gadget適用于當無法獲取root權限時可以將gadget.so植入目標apk中重打包,通過修改應用,使得server以應用的權限啟動;還有frida-gum、frida-gumjs、frida-inject、frida-devkit等。
inject

gumjs

gum
devkit

gadget

三,使用:

下面主要針對frida-server和frida-Gadget兩種來展開使用記錄。

1,frida-server

下面是frida官網給出的Android環境下hook的使用demo:

其中中間綠色部分是js代碼,上下部分是python語言;
python語言部分只是單純的為了將js代碼發送到設備而已,核心功能還是在js部分實現;
1,js語言是弱語言,不對變量類型做強檢查,所以我們可以都用var表示;
2,java中的類都用java.use獲取;
3,js代碼function大括號內部是用于hook的主要代碼,其余部分基本不變

import frida, sys
def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

jscode = """
Java.perform(function () {
  // Function to hook is defined here
  var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');

  // Whenever button is clicked
  var onClick = MainActivity.onClick;
  onClick.implementation = function (v) {
    // Show a message to know that the function got called
    send('onClick');

    // Call the original onClick handler
    onClick.call(this, v);

    // Set our values after running the original onClick handler
    this.m.value = 0;
    this.n.value = 1;
    this.cnt.value = 999;

    // Log to the console that it's done, and we should have the flag!
    console.log('Done:' + JSON.stringify(this.cnt));
  };
});
"""

process = frida.get_usb_device().attach('com.example.seccon2015.rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running CTF')
script.load()
sys.stdin.read()

下面以姜維在CSDN上給出的幾個例子來講解記錄一下這部分js代碼的用法:

java層hook:

1,hook構造方法

原方法:

原方法

hook代碼:
hook代碼

首先腳本中使用Java.use方法通過類名獲取類類型,然后構造方法是固定寫法:$init;這個要記住,然后因為需要重載所以用overload(……)形式即可,參數和參數之間用逗號隔開即可,這里用return重新調用了原來方法。

2,hook普通方法(static、private、public等)

原方法:

原方法

hook代碼:
hook代碼

這里的用法和構造方法基本一致,就是把$init換成了方法名而已;

3,修改參數和返回值

參數和返回值的修改在上面內容很容易看出來,就是在方法攔截后通過arguments參數去獲取傳入參數,然后修改,返回值的話直接修改return函數就好了,這里重點講的是自定義類型的參數


構造一個新對象的方法有很多,這里選擇最簡單的$new即可,上面那行注釋也是可以用的,這里的返回值直接修改成了我們構造出來的這個對象coinObj,但是如果要修改一個對象的內部值的話,直接用對象名加參數是不行的,那么就需要用反射了:
反射修改對象參數

其中java.cast(Object.getClass(),).getDeclaredField("")的固定用法記住即可;

4,打印方法的堆棧信息

可以用來查看方法的調用鏈關系,這里提供兩種思路,一種是自己編寫一個打印堆棧的方法,弄成dex注入到程序中,需要打印的時候直接調用;第二種就是簡單粗暴的直接在打印的地方構造一個異常對象然后拋出,但是這種方法會因為異常直接導致程序崩潰,不過無所謂了,我們可以看到調用關系就好了;

Native層hook:

1,hook未導出的函數

首先計算出目標函數實際地址=函數偏移+so基址+1,+1是因為要標識arm和thumb指令區別;
然后通過實際地址構造NativePointer對象;
通過攔截器模式attach到目標開始hook,這里的onEnter和onLeave與xposed中的before和after很像,其實功能也一樣,都是在函數開始前和結束后;

var nativePointer = new NativePointer(實際地址)

這里讀取到的參數其實一個指針,也就是一個地址,想要獲取內容需要用到Memory.readCString(地址)來打印參數;讀取返回值的時候,因為C語言中是多數情況是通過參數來返回函數結果的,我們就把參數地址里的內容打印出來,如果有多個參數就全打印出來再分析,這里是將返回結果的字節數組按字節打印,然后拼接在一起在打印出來的;

2,hook導出的函數

這里省去了手動計算地址的過程,只需要傳入so名字和函數名字即可,如果是c++的話則需要注意,因為C++支持重載,所以導出函數名會不同;
這里通過函數名可以知道就是一個native函數了,那么他第一個參數肯定是JNIEnv指針,第二個參數是jclass類型,這個是標準的如果是靜態方法第二個參數沒啥用,后面的參數就是真的傳遞到native層的值了,比如這里Java層的方法:



放到native層就變成4個參數了:



只有后面兩個才是我們需要的,然后查看hook返回結果:

這里可以看到傳入參數都返回了,但是函數return值是空的,這是因為這里的返回值是一個jString類型,看過jni.h文件的同學應該知道,jni里有一套自己的數據類型定義,雖然格式都和java一致,但是他屬于自定義類型;

這里new一個NativeFunction的用法可以查看api:



至于獲取jString的方法官網也有說明,很簡單:
var env =Java.vm.getEnv();
var jstring = env.newStringUtf("HelloWorld");

2,frida-Gadget

frida-gadget作為一個動態庫文件無法直接在linux/Android環境下運行,需要用過將apk反編譯成smali代碼,然后通過System.loadlibrary("frida-Gadget");的形式將so動態庫運行起來,此時Frida-server就是以應用的權限運行起來的,在應用的沙箱中是擁有全部權限的,包括攔截、插樁等操作;

1,程序首先要打開可調試狀態開關,

比如AndroidManifest文件中的Application中增加android:debuggable="true"標簽

2,將frida-gadget.so放到目標apk的lib文件夾下,將so的名字改成lib***.so的格式
3,植入點通常放在Application的入口處,因為盡可能早的hook住程序就可以避免漏過方法執行;

在smali代碼下表現為Application中的構造函數處,.methods static constructor <clinit>()V

static{
        // //loadlibary里 要把SO文件名的lib和后綴去掉。libfgma.so --> fgma
        System.loadLibrary("fgma"); 
    }
入口

修改后 [注意此處的.locals標識的此函數中用的寄存器的數量]

如果一個應用有多個進程的話則不必將so的植入點放到最初application的構造函數中,可以選擇將植入點選在新進程開啟后的合適的地點,最好還是構造函數初始化過程,但不限于最早組件,可選擇適當時機啟動的組件,如下,并在加載后使進程休眠20s的時間,用來等待粘貼js代碼;


這里在植入so之后增加了20s的進程睡眠時間,用來等帶用戶的手動操作
4,程序運行到so啟動時會卡住,此時是在等待客戶端像服務端發送建立連接的命令

此時在pc端命令行輸入adb shell netstat -ls查看網絡端口會發現有27042的消息端口;
如果在pc端命令行輸入frida -U Gadget命令即可與之建立連接,此過程相當于frida-server模式下的python注入過程,在此建立連接之后我們可以直接輸入js代碼去執行目的代碼,
例如:

//打印目的內存值
 var tar = new NativePointer(0x94b778d9);send(hexdump(this.tar));
//獲取目的函數模塊
var nativePointer = Module.findExportByName(null,'SeedDecryptAndIDEncrypt');
//打印目的函數地址
send("smm native pointers:" + nativePointer);
//連接目的函數進程
Interceptor.attach(nativePointer,{
   onEnter: function(args){
//打印目標函數傳入參數值
        send("sDIDE args: args[0]:" + args[0] + ", args[1]:" + args[1] + ", args[2]:" + args[2] + ", args[3]:" + args[3]);
//打印目的函數參數作為地址指向的內存的數值
        send("smm sDIDE Memory args: args[0]:" + Memory.readInt(args[0]).toString() + 
        ", args[2]:" + Memory.readInt(args[2]).toString() + 
        ", args[3]:" + Memory.readInt(args[3]).toString());
   },
   onLeave: function(retval){
//打印目標函數的返回值
        send("smm SeedDecryptAndIDEncrrypt retval:" + retval.toString());
   } 
});

注意此處的方法名使用單引號,findExportByName()函數的第一個參數可以用so名字或者為空;
以上就是frida的主要用法,其實frida的功能還有很多,而且不限于Android平臺,總之,大佬們的總結是個好東西,官網api也是個好東西。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。