Android逆向之旅---Hook神器家族的Frida工具使用詳解

來源:互聯網
上載者:User

標籤:詳細介紹   xxx   操作   準備   one   文法   alt   OLE   class   

一、前言

在逆向過程中有一個Hook神器是必不可少的工具,之前已經介紹了Xposed和Substrate了,不瞭解的同學可以看這兩篇文章:Android中Hook神器Xposed工具介紹 和 Android中Hook神器SubstrateCydia工具介紹 這兩篇文章非常重要一個是Hook Java層的時候最常用的Xposed和Hook Native層的SubstrateCydia,可以看我之前的文章比如寫外掛程式等都採用了Xposed工具,因為個人覺得Xposed用起來比較爽,寫代碼比較方便。而對於SubstrateCydia工具可以Hook Native層的,本文會介紹一下如何使用。那麼有了這兩個神器為啥還要介紹Frida工具呢?而且這個工具網上已經有介紹了,為什麼還有介紹了,因為這個Frida工具對於逆向者操作破解來說非常方便,所謂方便是他的安裝環境和配置要求都非常簡單相容性也非常好,因為最近在弄一個協議解密,無奈手機上安裝Cydia之後不相容導致死機所以就轉向用了這個工具實現了hook,所以覺得這個工具非常好用就單獨介紹一下。

 

二、環境安裝配置

因為網上的確有介紹了,而且官網也有文檔說明:https://www.frida.re/docs/javascript-api,但是最重要的是片段化就是東一處西一處,沒有歸納性的總結,而且很多常用的功能都沒介紹,所以本文就把常用的hook工具詳細介紹一下,主要從以下幾個方面來介紹:

第一、如何修改Java層的函數參數和傳回值

第二、如何列印Java層的方法堆棧資訊

第三、如何攔截native層的函數參數和傳回值

對於Java層會注重介紹,因為我們用過Xposed工具之後都知道,比如參數是自訂類型怎麼Hook等。不多說了直接用一個案例作為樣本進行操作,為了能夠覆蓋所有的操作可能性案例需要寫的複雜點:

參數和傳回值有基本類型,也有自訂類型,接下來我們就開始我們的Frida之旅吧。

 

這個網上都已經有教程了,因為Frida大致原理是手機端安裝一個server程式,然後把手機端的連接埠轉到PC端,PC端寫python指令碼進行通訊,而python指令碼中需要hook的代碼採用javascript語言。所以這麼看來我們首先需要安裝PC端的python環境,這個沒難度直接安裝python即可,然後開始安裝frida了,直接運行命令:pip install frida 

前提是你需要配置好python環境變數,不然提示pip命令找不到。安裝完成之後,我們再去官網下載對應版本的手機端程式frida-server:https://github.com/frida/frida/releases 注意這裡一定要把frida-server版本和上面PC端安裝的frida版本一致,不然運行報錯的。其實這裡看到真的實現hook功能的是手機端的frida-server,這個也是開源的大家可以研究他的原理。我們也看到這個工具和IDA是不是很類似,也是把手機端的連接埠轉寄到PC端進行通訊而已。有了frida-server之後就好辦了,直接push到手機目錄下,然後修改一下檔案的屬性即可:

adb push /data/local/tmp frida-server

root# chmod 777 /data/local/tmp/frida-server

然後直接運行這個程式:

/data/local/tmp# ./frida-server

然後把連接埠轉寄到PC端:

adb forward tcp:27042 tcp:27042

adb forward tcp:27043 tcp:27043

到這裡我們就把通訊的手機端工作做完了,是不是感覺和Xposed相比非常方便,相容性非常好,不需要安裝Xposed等工具考慮系統手機等適配問題了。接下來就開始在PC端開始編寫hook程式進行操作了:

這裡代碼也非常簡單,因為安裝好了frida模組,直接匯入模組,然後調用api擷取裝置的session然後hook程式包名,接著就可以執行js指令碼代碼進行hook操作,然後列印訊息:

這裡用了python的print函數列印,其實如果想要列印可以在上面的js指令碼中使用console.log也是可以的,看自己的習慣了。所以這裡我們看到指令碼的大致流程就是最外面用python引用frida庫進行和裝置通訊,然後編寫js指令碼執行hook操作。所以這裡最主要的還是js指令碼也就是需要理解js文法了。不過這個沒啥難度的。好了以上的準備條件都弄完了,下面就開始分部拆解操作看看如何涵蓋我們平常使用的hook案例。

 

三、Java層Hook操作案例分析第一個案例:hook類的構造方法

我們有時候想hook一個類的構造方法,在Xposed中直接用findConstructor方法就可以了,因為構造方法可能有多種重載形式,所以需要用參數作為區分,這裡我們hook我們案例的CoinMoney類的構造方法:

首先指令碼中使用Java.use方法通過類名擷取類類型,然後構造方法是固定寫法:$init;這個要記住,然後因為需要重載所以用overload(......)形式即可,參數和參數之間用逗號隔開即可。後面就是攔截之後的操作了,這裡方法參數可以自訂變數名,因為js是弱語言,不對類型做強檢查,當然這裡還有其他擷取參數的方法後面會介紹。這裡CoinMoney類的構造方法:

然後我們這裡使用send來發送列印訊息即可,當然也可以用console.log形式列印日誌,代碼編寫完了,下面就開始運行看效果,運行也很簡單,直接python frida.py:

在這之前一定要先開啟hook的應用,不然會報錯提示找不到這個程式進程:

這時候在運行看到了就成功了,我們把構造方法的參數列印出來了,那麼這裡hook就成功了。所以可以看到這個操作是不是比Xposed工具更方便呢。但是他也有弊端後面會總結的。

 

第二、hook類的普通方法

這裡的普通方法包括了靜態方法,私人方法和公開方法等,這個操作和上面的構造方法其實很類似,代碼如下:

這個就是把構造方法的固定寫法$init改成了需要hook的方法名即可。如果方法有重載形式還是用overload進行區分即可,比如這裡我們hook了Uitls.getPwd(String pwd)方法:

然後這裡我們看到可以用一個隱含的變數arguments擷取參數,這個是儲存了方法的參數資訊是系統內建的。所以我們有兩種方式擷取方法的參數資訊。運行看一下效果:

看到列印訊息,hook成功了。所以這裡就把hook方法擷取參數的案例都介紹完了,總結一下很簡單,構造方法使用固定寫法$init,其他方法全部用方法名即可。如果方法有重載形式需要用overload形式巨集指令引數用逗號分隔。擷取參數可以自訂參數名或者用系統隱含的arguments變數擷取。當然在這之前都需要用Java.use通過類名擷取類型。

 

第三、修改方法的參數和傳回值

我們在使用Xposed進行hook的時候最常用的可能就是修改參數和傳回值來實現外掛程式和外掛功能了,在Frida中其實也可以做到但是和Xposed不一樣,我們從上面的代碼可以看到,沒有像Xposed的before方法和after方法,而Frida直接是你可以在function中調用原來的方法這樣來進行參數修改,比如這裡我要修改上面的方法參數和傳回值:

因為Frida中沒有before和after方法,但是可以直接調用原來的方法其實Xposed中也可以可以直接調用原來的方法的,但是不怎麼常用,只要可以調用原來的方法,那麼參數和傳回值就可以隨意修改了,這裡我們把參數改成jiangwei212,傳回值後面追加yyyy了,看列印的日誌:

其實這麼做比before和after形式更為方便,而且可以在原始方法調用前做一些事情和後面做一些事情。

 

第四、構造和修改自訂類型對象和屬性

我們在Xposed寫外掛的時候也會遇到這種比較常見的問題,就是方法的參數不是基本類型是自訂類型,然後也想修改他的屬性值或者調用他的一個方法我們會使用反射來進行操作,而在傳回值的時候,想構造一個自訂類型的對象也是直接用反射執行個體化一個對象進行操作的。其實在這裡因為js中也是支援反射操作的,所以就很簡單了:

這裡構造一個對象其實很簡單直接固定寫法$new即可,然後有了對象也可以直接調用其對應的方法即可,然後就是如何修改一個物件類型的欄位值呢?這個就要用反射了:

這裡我們攔截了getCoinMoney方法,參數是CoinMoney類型,我們想修改他的money欄位值,這時候我們直接調用他的方法沒什麼問題,但是如果直接調用欄位值或者修改就會出現失敗了,所以只能通過反射去修改欄位值,不過要先擷取這個對象對應的class類型,用Java.cast介面就可以,然後擷取反射欄位直接修改即可,這裡要注意不管欄位是private還是public的寫法都是一樣的,都是這段代碼大家要注意把這段代碼記住即可。我們看看hook之後的結果:

如果沒有用反射去操作直接擷取欄位值列印就是object了。

 

第五、列印方法的堆棧資訊

我們在破解過程中有時候通過拋出異常來列印堆棧資訊跟蹤代碼效率會更高,Xposed中操作很方便直接Java代碼用Log.xxx方法列印堆棧資訊即可,但是在Frida中有點麻煩了,因為他是js代碼不好操作,第一次想到的辦法就是自己寫一個列印堆棧資訊的類然後弄成一個dex之後,把這個dex注入到程式中,因為Frida支援把一個dex檔案注入到原始程式中啟動並執行,注入之後在需要列印堆棧資訊的方法中調用這個dex中的那個方法就可以了。具體怎麼注入本文不多介紹了。當時覺得這種方案太麻煩了,那麼還有其他方案嗎?其實還是有的,因為我們既然可以構造一個對象那麼為什麼不直接構造一個Exception對象呢?其實操作很簡單,首先我們用Java.use方法擷取類型變數:var Exception = Java.use("java.lang.Exception");然後是js中支援throw文法的,直接在需要列印堆棧資訊的方法中調用即可:

不過這個是真得拋出異常了,沒有捕獲住,所以程式崩潰,我們在開發Android應用的時候如果程式崩潰了最快的查看異常資訊的方法就是用日誌過濾方式:adb logcat -s AndroidRuntime

這樣我們就把堆棧資訊列印出來了,其實這裡可以看到這個是真的一個崩潰異常了,因為沒有catch所以直接用系統崩潰日誌就可以查看了。這種方式最簡單粗暴了。對於跟蹤代碼非常有用的。

 

static struct : public v8::String::ExternalOneByteStringResource {    // 重寫父類函數    // 強制進行const unsigned char[] =>www.bomaoyule.cn/ const char*的類型轉換    const char* data() const override {        return reinterpret_cast<const char*>(raw_internal_bootstrap_loaders_value);    }    // 數組長度    size_t length() const override { return arraysize(raw_internal_bootstrap_loaders_value); }    // 預設delete函數    void Dispose() override {www.huayi1.cn /*

www.huazongyule.net/
www.jyz521.com

 Default calls `delete this`. */ }    // const char* => Local<String>的類型轉換    v8::Local<v8::String> ToStringChecked(v8::Isolate* isolate) {        return v8::String::NewExternalOneByte(isolate,www.078881.cn this).ToLocalChecked();    }} internal_bootstrap_loaders_value;

到這裡我們就把所有可能遇到的情形Java層hook操作都介紹完了,主要包括以下幾種常見情形:

第一、Hook類的構造方法和普通方法,注意構造方法是固定寫法$init即可,擷取參數可以通過自訂參數名也可以直接用系統隱含的arguments變數擷取即可。

第二、修改方法的參數和傳回值,直接調用原始方法傳入需要修改的參數值和直接修改傳回值即可。

第三、構造對象使用固定寫法$new即可。

第四、如果需要修改對象的欄位值需要用反射去進行操作。

第五、堆棧資訊列印直接調用Java的Exception類即可,通過adb logcat -s AndroidRuntime來過濾日誌資訊查看崩潰堆棧資訊。

總結:記得用Java.use方法擷取類的類型,如果遇到重載的方法用overload實現即可。

 

四、Native層Hook操作案例分析

下面繼續來看Frida更強大的地方就是hook native代碼,說的強大不是因為功能,而是便捷程度,我們之前hook native可能用Cydia比較多,但是都知道Cydia和Xposed一樣都有相容問題,環境安裝配置太麻煩了,而Frida還是只需要幾行js代碼即可搞定,這裡hook native還是用兩個案例介紹:一個是hook匯出的函數,一個是hook未匯出的函數,通過擷取參數和修改傳回值來示範,這裡我們不自己寫native代碼了,直接用之前破解快手的資料請求的so檔案,他有一個函數在底層擷取字串資訊,還有一個是最近正在研究的資訊類app的密碼編譯演算法so,我們修改他的函數傳回值。

第一、hook未匯出函數功能

未匯出的函數我們需要手動的計算出函數地址,然後將其轉化成一個NativePointer的對象然後進行hook操作,那麼如何計算一個函數地址呢?這個很簡單只要得到so的記憶體基地址加上函數的相對位址就可以了。基地址擷取直接查看程式對應的maps檔案即可:

相對位址直接用IDA開啟so檔案就可以查看,比如這裡我們通過靜態分析之後想hook這個sub_5070函數:

然後我們F5查看函數對應的C語言代碼查看參數資訊:

這裡看到是三個參數,那麼計算了後的實際地址就是0x7816A000+5070=0x7816F070,不過這個地址不是最後的地址,因為thumb和arm指令的區分,地址最後一位的奇偶性來進行標誌,所以這裡還需加1也就是最終的0x7816F071,這一點很重要不管使用Cydia還是Frida都要注意最後計算的絕對位址要+1,不然會報錯的:

這裡hook之後有兩個回調方法一個是進入函數之前,一個是執行完之後,這個和Xposed非常類似了,我們列印參數,不過這個和之前Hook Java層就不一樣了,因為在C中大部分都是和地址指標相關,特別是常見的字串資訊,我們如果要正確的列印字串值就需要藉助Memory系統類別來通過指標擷取字串資訊了,這個類非常重要,在後面修改傳回值也是用它寫記憶體值的。我們先看看這個函數原始傳回值是什麼:

這個是加密之後的值了,然後我們擷取到參數了,而通過IDA分析之後發現這個函數最終的結果不是通過return來返回的,而是通過第三個指標參數返回的,因為C中有一個參數傳值功能,就是直接操作指標就可以傳回結果,這個在C中經常用到,因為一個函數傳回值只有一處要是一個函數有多個傳回值就沒辦法了,所以可以通過參數指標來傳遞。所以如果我們想修改函數的最終結果,需要修改參數指標的記憶體段資料,我們先把那個記憶體段資料擷取到列印出來,這裡因為通過靜態分析知道最終的結果是16個位元組資料,所以這裡不能在用讀取記憶體字串方法了,而是讀取純的位元組資料:

然後在把傳回值修改了,傳回值修改也很簡單,直接重寫那段記憶體值就可以了,比如這裡修改成1111:

所以看到了C語言中很多地方都在直接操作記憶體也就是地址,特別需要藉助Memory類,他有很多方法,包括記憶體拷貝等。具體用到的可以去官網查詢:https://www.frida.re/docs/javascript-api/#memory;然後我們看hook結果:

我們hook到了他的參數資訊,第一個參數是需要加密的字串資訊我們是通過Memory方法擷取字串的,因為本身這個參數是一個字串指標,第二個參數應該是字串長度,第三個參數是操作結果值的指標,然後看到我們擷取到的結果值就是原始加密的資訊。說明我們擷取成功了,然後再看看我們修改之後的1111值,通過日誌查看:

看到了在Java成通過native訪問得到的簽名資訊已經被修改成了1111了,說明我們成功了。到這裡我們就成功的,在hook native的時候一定要注意函數的絕對位址要計算對,最後一定要記住+1,函數的傳回值有可能不是通過return而是參數指標傳遞的,操作記憶體的時候用Memory類即可。

 

第二、hook匯出函數功能

這部分內容很簡單了,比上面的簡單是因為不需要手動的計算函數地址,因為是匯出的,所以直接可以得到匯出的函數名即可,因為C語言中沒有重載的形式,而C++中有,所以有時候發現匯出的函數名和正常的函數名前面加上了一串資料作為區分那應該是C++代碼寫的。有了so檔案和匯出的函數名就不需要構造NativePoniter了:

這個看到比上面自己手動找函數地址方便多了吧,列印參數都一樣的代碼了。這裡通過函數名可以知道就是一個native函數了,那麼他第一個參數肯定是JNIEnv指標,第二個參數是jclass類型,這個是標準的如果是靜態方法第二個參數沒啥用,後面的參數就是真的傳遞到native層的值了,比如這裡Java層的方法:

那麼按照上面的說明native層的函數就是4個參數了:

的確是這樣的,後面兩個參數才是我們想要的值,我們通過IDA查看這個函數:

然後我們用F5查看虛擬碼他的傳回值:

用env指標調用了NewStringUTF返回一個jstring對象了,好了到這裡我們先不說傳回值修改的問題,先看看hook參數資訊:

但是我們看到我們列印的傳回值是個空也就是null 指標,而如果這裡我們想hook他的傳回值怎麼辦呢?如果是一個正常的返回字串資訊,我們可以直接用Memory的方法構造出來Memory.allocUtf8String("XXXXX")一個記憶體字串資訊,然後直接返回一個指標地址即可,但是現在這裡是返回一個jstring對象,其實這個我們通過查看jni.h檔案可以知道jstring是C++中定義的對象:

而基本類型就是基礎資料型別 (Elementary Data Type):

這個修改沒有任何問題的,那麼現在問題是修改非基本類型,比如這裡的如何返回jstring對象呢?這裡我能想到的一個辦法就是通過擷取NewStringUTF函數指標,通過NativeFunction方法擷取函數,然後調用

這裡看到代碼邏輯沒什麼問題,現在缺的就是NewStringUTF的函數地址了,這個因為在so中沒法查看,所以怎麼辦呢?不著急我們在看看JNIEnv的定義:

他是一個結構體,再看看那個函數地址:

我們已經有了JNIEnv結構體指標了,每個函數指標都是int類型也就是四個位元組,所以從JNIEnv指標開始依次計算就可以得到NewStringUTF函數對應的地址了。不過都說了找不到方法的時候就去官網找,JNIEnv變數其實有對應的方法,這裡構造jstring方法其實很簡單:

這個比找函數指正方便多了,其實env有很多方法在這裡都有對應的api。

 

所以到這裡我們發現了Frida在Hook底層函數返回jni中的類型的時候有點麻煩了,但是Cydia就不會了,因為他是Android工程,可以引用jni.h標頭檔的,比如我們用Cydia來修改這個函數的傳回值:

看到了吧,這樣就很方便了因為是Android工程,所以可以直接應用jni.h標頭檔,然後直接調用NewStringUTF方法返回了,看看hook的結果:

也修改成功了。所以這裡看到Frida也不是萬能的,要看什麼問題怎麼去分析了。

 

五、技術總結

到這裡我們就把Frida常用的功能和hook常見的用法都說明完了,下面就來總結一下:

第一、Java層代碼Hook操作

1、hook方法包括構造方法和對象方法,構造方法固定寫法是$init,普通方法直接是方法名,參數可以自己定義也可以使用系統隱含的變數arguments擷取。

2、修改方法的參數和傳回值,直接調用原始方法通過傳入想要修改的參數來做到修改參數的目的,以及修改傳回值即可。

3、構造對象和修改對象的屬性值,直接用反射進行操作,構造對象用固定寫法的$new即可。

4、直接用Java的Exception對象列印堆棧資訊,然後通過adb logcat -s AndroidRuntime來查看異常資訊跟蹤代碼。

總結:擷取對象的類類型是Java.use方法,方法有重載的話用overload(.......)解決。

第二、Native層代碼Hook操作

1、hook匯出的函數直接用so檔案名稱和函數名即可。

2、hook未匯出的函數需要計算出函數在記憶體中的絕對位址,通過查看maps檔案擷取so的基地址+函數的相對位址即可,最後不要忘了+1操作。

總結:Native中最常用的就是記憶體位址指標了,所以如果要正確的擷取值一定要用Memory類作為輔助,特別是字串資訊。

 

六、Hook家族神器的對比

下面繼續來看看Frida,Xposed,SubstrateCydia這三個Hook神器的區別和優缺點:

第一、Xposed的優缺點

優點:在編寫Java層hook外掛程式的時候非常好用,這一點完全優越於Frida和SubstrateCydia,因為他也是Android項目,可以直接編寫Java代碼調用各類api進行操作。而且可以安裝到手機上直接使用。

缺點:配置安裝環境繁瑣,相容性差,在Hook底層的時候就很無助了。

第二、Frida的優缺點

優點:在上面我們可以看到他的優點在於配置環境很簡單,操作也很便捷,對於破解者開發階段非常好用。支援Java層和Native層hook操作,在Native層hook如果是非基本類型的話操作有點麻煩。

缺點:因為他只適用於破解者在開發階段,也就是他沒法像Xposed用於實踐生產中,比如我寫一個外掛用Frida寫肯定不行的,因為他無法在手機端運行。也就是破解者用的比較多。

第三、SubstrateCydia的優缺點

優點:可以運行在手機端,和Xposed類似可以用於實踐生產中。支援Java層和Native層的hook操作,但是Java層hook不怎麼常用,用的比較多的是Native層hook操作,因為他也是Android工程可以引用系統api,操作更為方便。

缺點:和Xposed一樣安裝配置環境繁瑣,相容性差。

以上這三個工具可以說是現在用的最多的hook工具了,總結一句話就是寫Java層Hook還是Xposed方便,寫Native層Hook還是Cydia了,而對於破解者開發那還是Frida最靠譜了。但是不管怎麼樣,寫外掛最難的也是最重要的不是寫代碼而是尋找hook點,也就是逆向分析app找到那個地方,然後寫hook代碼實現外掛程式功能。

Android逆向之旅---Hook神器家族的Frida工具使用詳解

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.