安卓動態除錯七種武器之離別鉤 – Hooking(下)

NO IMAGE

0x00 序

隨著移動安全越來越火,各種除錯工具也都層出不窮,但因為環境和需求的不同,並沒有工具是萬能的。另外工具是死的,人是活的,如果能搞懂工具的原理再結合上自身的經驗,你也可以創造出屬於自己的除錯武器。因此,筆者將會在這一系列文章中分享一些自己經常用或原創的除錯工具以及手段,希望能對國內移動安全的研究起到一些催化劑的作用。

目錄如下:
安卓動態除錯七種武器之長生劍 – Smali Instrumentation
安卓動態除錯七種武器之孔雀翎 – Ida Pro
安卓動態除錯七種武器之離別鉤 – Hooking (上)
安卓動態除錯七種武器之離別鉤 – Hooking (下)
安卓動態除錯七種武器之碧玉刀- Customized DVM
安卓動態除錯七種武器之多情環- Customized Kernel
安卓動態除錯七種武器之霸王槍 – Anti Anti-debugging
安卓動態除錯七種武器之拳頭 – Tricks & Summary

文章中所有提到的程式碼和工具都可以在我的github下載到,地址是: https://github.com/zhengmin1989/TheSevenWeapons

0x01 利用函式掛鉤實現native層的hook

我們在離別鉤(上)中已經可以做到動態的載入自定義so檔案並且執行so檔案中的函式了,但還不能做到hook目標函式,這裡我們需要用到函式掛鉤的技術來做到這一點。函式掛鉤的基本原理是先用mprotect()將原始碼段改成可讀可寫可執行,然後修改原函式入口處的程式碼,讓pc指標跳轉到動態載入的so檔案中的hook函式中,執行完hook函式以後再讓pc指標跳轉回原本的函式中。

用來注入的程式hook5邏輯與之前的hook4相比並沒有太大變化,僅僅少了“呼叫 dlclose 解除安裝so檔案”這一個步驟,因為我們想要執行的hook後的函式在so中,所以並不需要呼叫dlclose進行解除安裝。基本步驟如下:

儲存當前暫存器的狀態 -> 獲取目標程式的mmap, dlopen, dlsym, dlclose 地址 -> 呼叫mmap分配一段記憶體空間用來儲存引數資訊 –> 呼叫dlopen載入so檔案 -> 呼叫dlsym找到目標函式地址 -> 使用ptrace_call執行目標函式 -> 恢復暫存器的狀態

hook5的主要程式碼邏輯如下:
圖片描述
圖片描述

我們知道arm處理器支援兩種指令集,一種是arm指令集,另一種是thumb指令集。所以要hook的函式可能是被編譯成arm指令集的,也有可能是被編譯成thumb指令集的。Thumb指令集可以看作是arm指令壓縮形式的子集,它是為減小程式碼量而提出,具有16bit的程式碼密度。Thumb指令體系並不完整,只支援通用功能,必要時仍需要使用ARM指令,如進入異常時。需要注意的一點是thumb指令的長度是不固定的,但arm指令是固定的32位長度。

為了讓大家更容易的理解hook的原理,我們先只考慮arm指令集,因為arm相比thumb要簡單一點,不需要考慮指令長度的問題。所以我們需要將target和hook的so編譯成arm指令集的形式。怎麼做呢?很簡單,只要在Android.mk中的檔名後面加上”.arm”即可 (真正的檔案不用加)。
圖片描述

確定了指令集以後,我們來看實現掛鉤最重要的邏輯,這個邏輯是在注入後的so裡實現的。首先我們需要一個結構體儲存彙編程式碼和hook地址:
圖片描述

我們接著來看注入的邏輯,最重要的函式為hook_direct(),他有三個引數,一個引數是我們最開始定義的用來儲存彙編程式碼和hook地址的結構體,第二個是我們要hook的原函式的地址,第三個是我們用來執行hook的函式。函式的原始碼如下:
圖片描述

雖然android有ASLR,但並沒有PIE,所以program image是固定在0x8000這個地址的,因此我們用mprotect()函式將整個target程式碼段變成RWX,這樣我們就能修改函式入口處的程式碼了。是否修改成功可以通過cat /proc/[pid]/maps檢視:
圖片描述

隨後我們需要確定目標函式的地址,這個有兩種方法。如果目標程式本身沒有被strip的話,那些symbol都是存在的,因此可以使用dlopen()和dlsym()等方法來獲取目標函式地址。但很多情況,目標程式都會被strip,特別是可以直接執行的二進位制檔案預設都會被直接strip。比如target中的sevenWeapons()這個函式名會在編譯的時候去掉,所以我們使用dlsym()的話是無法找到這個函式的。這時候我們就需要使用ida或者objdump來定位一下目標函式的地址。比如我們用objdump找一下target程式裡面sevenWeapons(int number)這個函式的地址:
圖片描述

雖然target這個binary被strip了,但還是可以找到sevenWeapons()這個函式的起始地址是在0x84f4。因為”mov r2, r0”就是載入number這個引數的指令,隨後呼叫了<[email protected]>用來輸出結果。 最後一個引數也就是我們要執行的hook函式的地址。得到這個地址非常簡單,因為是so中的函式,呼叫hook_direct()的時候直接寫上函式名即可。

hook_direct(&eph,hookaddr,my_sevenWeapons);

接下來我們看如何修改函式入口(modify function entry),首先我們儲存一下原函式的地址和那個函式的前三條指令。隨後我們把目標函式的第一條指令修改為 LDR pc, [pc, #0],這條指令的意思是跳轉到PC指標所指的地址,由於pc暫存器讀出的值實際上是當前指令地址加8,所以我們把後面兩處指令都儲存為hook函式的地址,這樣的話,我們就能控制PC跳轉到hook函式的地址了。
最後我們再呼叫hook_cacheflush()這個函式來重新整理一下指令的快取。因為雖然前面的操作修改了記憶體中的指令,但有可能被修改的指令已經被快取起來了,再執行的話,CPU可能會優先執行快取中的指令,使得修改的指令得不到執行。所以我們需要使用一個隱藏的系統呼叫來重新整理一下快取。hook_cacheflush()程式碼如下:
圖片描述

重新整理完快取後,再執行到原函式的時候,pc指標就會跳轉到我們自定義的hook函式中了,hook函式裡的程式碼如下:
圖片描述

首先在hook函式中,我們可以獲得原函式的引數,並且我們可以對原函式的引數進行修改,比如說將數字乘2。隨後我們使用hook_precall(&eph);將原本函式的內容進行還原。hook_precall()內容如下:
圖片描述

在hook_precall()中,我們先對原本的三條指令進行還原,然後使用hook_cacheflush()對記憶體進行重新整理。經過處理之後,我們就可以執行原來的函式orig_sevenWeapons(number)了。執行完後,如果我們還想再次hook這個函式,就需要呼叫hook_postcall(&eph)將原本的三條指令再進行一次修改。
下面我們來使用hook5和libinject2.so來注入一下target這個程式:
圖片描述

可以看到經過注入後,我們成功的獲取了引數number的值,並且將”Hello,LiBieGou!”後面的數字變成了原來的兩倍。

0x02 使用adbi實現JNI層的hook

我們在上一節中介紹瞭如何hook native層的函式。下面我們再來講講如何利用adbi來hook JNI層的函式。Adbi是一個android平臺上的一個注入框架,本身是開源的。Hook的原理和我們之前介紹的技術是一樣的,這個框架支援arm和thumb指令集,也支援通過字串定位symbol函式的地址。
首先我們需要一個例子用來講解,所以我寫了程式叫test2。
圖片描述

enter image description here

點選程式中的button後,程式會呼叫so中的Java_com_mzheng_libiegou_test2_MainActivity_stringFromJNI(JNIEnv* env,jobject thiz,jint a,jint b)函式用來計算a b的結果。我們預設傳的引數是a=1, b=1。接下來我就來教你如何利用adbi來hook這個JNI函式。

因為adbi是一個注入框架,我們下載好原始碼後,只要對應著原始碼中給的example照貓畫虎即可。Hijack那個資料夾是儲存的用來注入的程式,和我們之前講的hook5.c的原理是一樣的,所以不用做任何修改。我們只需要修改example中的程式碼,也就是將要注入的so檔案的原始碼。

首先,我們在/adbi-master/instruments/example這個資料夾下新建兩個檔案”hookjni.c”和” hookjni_arm.c”。“hookjni_arm.c”其實只是一個殼,用來將hook函式的入口編譯成arm指令集的,內容如下:
圖片描述

這個檔案的目的僅僅是為了用arm指令集進行編譯,可以看到Android.mk中在”hookjni_arm.c”後面多了個”.arm”:
圖片描述

下面我們來看”hookjni.c”:
圖片描述

這段程式碼和我上一節講的程式碼非常像,my_init()用來進行hook操作,我們需要提供想要hook的so檔名和函式名,然後再提供thumb指令集和arm指令集的hook函式地址。
my_Java_com_mzheng_libiegou_test2_MainActivity_stringFromJNI()就是我們提供的hook函式了。我們在這個hook函式中把a和b都改成了10。除此之外,我們還使用counter這個全域性變數來控制hook的次數,這裡我們把counter設定為3,當hook了3次以後,就不再進行hook操作了。
編輯好程式碼後,我們只需要在adbi的根目錄下執行” build.sh”進行編譯:
圖片描述

編譯好後,我們把hijack和libexample.so拷貝到/data/local/tmp目錄下。然後使用hijack進行注入:
圖片描述

然後我們再點選button就可以看到我們已經成功的修改了a和b的值為10了,最後顯示1 1=20。
圖片描述

通過cat adbi_example.log我們可以看到hook過程中列印的log:
圖片描述

可以看到adbi是通過thumb指令集進行hook的,原因是test2程式是用thumb指令集進行編譯的。其實hook thumb指令集和arm指令集差不多,在”/adbi-master/instruments/base”中可以找到hook thumb指令集的邏輯:
圖片描述

其實h->jumpt[20]這個字元陣列儲存的就是thumb指令集下控制pc指標跳轉到hook函式地址的程式碼。Hook完之後的流程就和arm指令集的hook一樣了。

0x03 使用Cydia或Xposed實現JAVA層的hook

關於Cydia和Xposed的文章和例子已經很多了,這裡就不再重複的進行介紹了。這裡推薦一下瘦蛟舞和我寫的文章,基本上就知道怎麼使用這兩個框架了:

Android.Hook框架xposed篇(Http流量監控) http://drops.wooyun.org/papers/7488
Android.Hook框架Cydia篇(脫殼機制作) http://drops.wooyun.org/tips/8084
手把手教你當微信運動第一名 – 利用Android Hook進行微信運動作弊 http://drops.wooyun.org/tips/8416

個人感覺Xposed框架要做的更好一些,主要原因是Cydia的作者已經很久沒有更新過Cydia框架了,不光有很多bug還不支援ART。但是有很多不錯的除錯軟體/外掛是基於兩個框架製作的,所以有時候還是需要用到Cyida的。

接下來就推薦幾個很實用的基於Cydia和Xposed的外掛:

  1. ZjDroid: ZjDroid是基於Xposed Framewrok的動態逆向分析模組,逆向分析者可以通過ZjDroid完成以下工作: 1、DEX檔案的記憶體dump 2、基於Dalvik關鍵指標的記憶體BackSmali,有效破解主流加固方案 3、敏感API的動態監控 4、指定記憶體區域資料dump 5、獲取應用載入DEX資訊。 6、獲取指定DEX檔案載入類資訊。 7、dump Dalvik java堆資訊。 8、在目標程序動態執行lua指令碼。 https://github.com/halfkiss/ZjDroid

  2. XPrivacy: XPrivacy是一款基於Xposed框架的模組應用,可以對所有應用可能洩露隱私的許可權進行管理,對禁止可能會導致崩潰的應用採取欺騙策略,提供偽造資訊,比如說可以偽造手機的IMEI號碼等。 https://github.com/M66B/XPrivacy

  3. Introspy: Introspy是一款可以追蹤分析移動應用的黑盒測試工具並且可以發現安全問題。這個工具支援很多密碼庫的hook,還支援自定義hook。 https://github.com/iSECPartners/Introspy-Android

0x04 Introspy實戰

我們使用alictf上的evilapk400作為例子講解如何利用introspy來除錯程式。Evilapk400使用了比較複雜的dex加殼技術,如果不利用基於自定義dalvik的脫殼工具來進行脫殼的話做起來會非常麻煩。但我們如果換一種思路,直接通過動態除錯的方法來獲取加密演算法的字串,key和IV等資訊就可以直接獲取答案了。
首先我們安裝cyida.apk,Introspy-Android Config.apk到手機上,然後用eclipse開啟“Introspy-Android Core”的原始碼增加一個自定義的hook函式。雖然Introspy預設對密碼庫進行了hook,但卻沒有對一些strings的函式進行hook。所以我們手動新增一個對String.equals()的hook:
圖片描述
圖片描述

然後我們編譯,生成並安裝Introspy-Android Core.apk到手機上。然後我們安裝上EvilApk400。然後開啟Introspy-Android Config勾選com.ali.encryption。
圖片描述

接著開啟evilapk400,然後隨便輸入點內容並點選登陸。
圖片描述

然後我們使用adb logcat就能看到Introspy輸出的資訊了:
圖片描述

通過log,很容易就能看出來evilapk400使用了DES加密。通過log我們獲取了密文,Key以及IV,所以我們可以寫一個python程式來計算出最後的答案:
圖片描述
圖片描述
圖片描述

0x05 總結

本篇介紹了native層,JNI層以及JAVA層的hook,基本上可以滿足我們平時對於android上hook的需求了。 另外文章中所有提到的程式碼和工具都可以在我的github下載到,地址是: https://github.com/zhengmin1989/TheSevenWeapons

0x06 參考資料

Android平臺下hook框架adbi的研究(下)http://blog.csdn.net/roland_sun/article/details/36049307
ALICTF Writeups from Dr. Mario

作者:蒸米@阿里聚安全,更多技術文章,請訪問阿里聚安全部落格