Android逆向之旅---破解某應用加密算法(動態調試so和frida hook so代碼)

一、樣本靜態分析

最近有位同學發了一個樣本給我,主要是有一個解密方法,把字符串加密了,加解密方法都放在so中,所以之前也沒怎麼去給大家介紹arm指令和解密算法等知識,正好藉助這個樣本給大家介紹一些so加密方法的破解,首先我們直接在Java層看到加密信息,這個是這位同學直接告訴我這個類,我沒怎麼去搜了:

這個應用不知道幹嘛的,但是他的防護做的還挺厲害的,之前我們介紹過小黃車應用內部也用了這種中文混淆變量和方法等操作,這裏就不多解釋了,這裏主要看那個加密算法:

看到這裏有一個加解密方法,傳入字符串字節,返回加解密之後的字節數據,我們直接用IDA打開這個libwechat.so文件:

這裏可惜沒有收到Java_xxx這樣的函數,說明他可能用了動態註冊,所以就去搜JNI_OnLoad函數,所以這裏注意大家以後如果打開so之後發現沒有Java_xxx這樣的函數開頭一般都是在JNI_OnLoad中採用了動態註冊方式,所以只需要找到JNI_OnLoad函數,然後找到RegisterNatives函數即可,不過在這個過程中我們需要轉換JNIEnv指針信息:

這裏大家如果看到類似於vXX+YY這樣的,選中vXX變量,然後按Y按鍵,然後替換成JNIEnv*即可,我們如果手動註冊過Native方法,都知道RegisterNatives函數的三個參數含義:

jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)

第一個參數:需要註冊native函數的上層Java類

第二個參數:註冊的方法結構體信息

第三個參數:需要註冊的方法個數

這裏當然是重點看第二個參數,這裏當然也需要知道方法結構體信息:

typedef struct {
const char* name;
const char* signature;
void*       fnPtr;
} JNINativeMethod;

結構體包含三部分分別是:方法名、方法的簽名、對應的native函數地址;那麼這裏我們肯定重點看第三部分,因爲要找到具體的解密函數,這時候我們需要去對RegisterNatives函數查看他的實參值:

這裏選中RegisterNatives函數名,然後右鍵選擇Force call type即可:

這時候就看到了RegisterNatives的三個參數值,其實這裏看到是四個,這個主要是調用方式的區別,因爲我們還會看到有這種調用方式:(*JNIEnv)->RegisterNatives(JNIEnv env...),所以第一個參數其實是JNIEnv變量,這裏就看第三個參數的地址就是需要註冊方法的結構體信息,點擊進入查看:

這裏看到了方法名,方法簽名以及對應的具體函數,這裏主要看解密函數,找到decryptData即可,然後點擊進入查看:

按下F5查看C語言代碼:

繼續點擊進入查看:

這裏就是實際的解密算法的地方了,大致看一下其實還是很簡單的,就是有一個AES_CBC_128算法加解密的,我們用過這個算法都知道需要key和iv值,因爲是128位的,所以這兩個值肯定是16(128/8)個字節,這個是基礎知識也是非常關鍵的知識,知道是16個字節對於後面分析破解非常關鍵。然後需要從解密之後的字節數組的最後一位獲取實際字節的長度,最後構建byte數組返回給Java層即可。所以這裏我們看到最重要的是如何獲取aes解密的key和iv值。這裏有很多種方式可以動態調試,可以hook。但是我們先不介紹這兩種解密方式,我們先來看看另外一個問題。

 

二、調用so功能函數(修改指令)

我們在之前是不是有時候解密一個so算法,其實沒不要真的知道他的解密算法,而是可以調用他的so然後直接解密出來數據即可,所以我們本文也來嘗試做一下,爲什麼這麼做因爲在這個過程中我想給大家介紹一些知識點比如修改arm指令等,我們把這個應用的so拷貝到項目中,然後構建一個native類和方法,最後調用解密方法,發現調用直接出現崩潰信息:這時候我們發現在進入JNI_OnLoad掛了,說明JNI_OnLoad中做了一些東西檢測:

看到JNI_OnLoad函數中有這兩個函數調用,第一個我們都知道爲了防止自己的進程被人惡意附加,就自己先佔坑,這樣別人就附加失敗了,第二個看似也是類似功能,不過不用關心內部實現,我們爲了後面動態調試成功,這裏還是先把這兩個函數幹掉吧,這裏幹掉簡單直接改成NOP空指令就可以了,就相當於沒調用了。因爲這兩個函數的執行邏輯和返回結果和後面的邏輯是沒任何關係的,所以可以這麼做,如果有關係那隻能修改返回值了。修改指令之前其實介紹過了,很簡單先找到指令對應的偏移地址:

然後用010Editor工具打開so文件,找到這個地址:

怎麼修改成NOP指令呢?有一個牛逼的網站在線轉換arm爲hex值:http://armconverter.com

這裏看到轉換BLX指令的HEX正好和上面看到的HEX值對應上了,這裏修改NOP指令:

看到NOP指令對應的HEX值是C046,那就修改吧:

這裏注意需要把那兩條指令的所有HEX全部改成NOP指令,保存再用IDA打開查看:

修改成功,這兩個函數就等於沒調用了,在運行調用so還是崩潰,這時候需要想到的是有簽名校驗,而巧合的是在搜索JNI的時候無意發現了這個函數:

當然如果大家想知道so中有沒有簽名校驗,可以直接Shift+F12查找字符串內容"signatures":

一般有這類字符串信息都有簽名校驗功能了,我們繼續看上面那個簽名校驗函數:

果然這裏會獲取簽名信息,然後比對返回1表示正確的簽名信息,這裏我們不要直接修改返回值和那個v5變量值,因爲我們知道strcmp函數執行的結果是-1,0,1;這裏明顯是需要讓返回值是0纔可以,那不如直接修改v3的初始值爲1即可,修改方法和上面的指令修改類似:

記住這個便宜地址,然後去010Editor工具中查看:

然後把賦值修改成1:

然後去010Editor修改即可:

修改之後保存,用IDA打開so:

看到已經修改成功了,然後在F5查看僞代碼:

這裏不管簽名對不對,都直接返回1了,修改了之後我們在運行發現還是報錯,這個需要再去看JNI_OnLoad函數了:

這裏需要獲取一個Java層的類,所以我們在工程中新建這個類即可,這個類可以沒有任何方法:

然後運行成功,看看解密之後的內容是啥:

看到解密之後的內容是個字符串version內容,到此我們就成功的過掉了so中的一些檢測調用so解密出來內容了,那麼在這個過程中我們依然可以學到很多東西:

第一、修改指令,如果不想讓一個函數執行,只需要把跳轉指令修改成NOP空指令即可,前提是這個函數的執行結果和後面的邏輯沒有半毛錢的關係,如果有那麼就需要修改函數的返回值,一般需要修改跳轉指令之後的MOVS指令的寄存器值,如果簡單點可以直接修改變量的初始化值,比如這裏的過掉簽名校驗。

第二、如果快速的知道so中是否有簽名校驗功能,可以直接在字符串列表中搜索"signatures"即可,現在也有很多應用會在so中調用Java層的類信息,所以需要去看JNI_OnLoad中arm指令,或者直接搜索字符串列表,因爲一般Java層類信息,都是xxx/yyy/zzz/MMM這樣的字符串格式,通過肉眼排查也是可以的。

 

三、動態調試so獲取解密算法

雖然我們成功的調用了so解密出內容了,但是這個不是本文的重點,本文的重點是把這個解密算法弄出來,不過在之前已經分析了大概,我們只需要弄到aes的key和iv值即可,這裏有兩種方式一種是用Frida進行hook操作,一種是動態調試,這裏動態調試非常簡單,前提是用我們上面已經修改過指令的so包,不然內部有一些反調試檢測。爲了方便用我們的demo工程進行動態調試即可:

第一步:運行手機端的android_server

第二步:端口轉發

adb forward tcp:23946 tcp:23946

第三步:調試運行程序

adb shell am start -D -n cn.wjdiankong.awwechathack/.MainActivity

第四步:打開IDA附加進程


第五步:設置Debugger Option中勾選上加載so斷點

這裏其實是爲了防止JNI_OnLoad函數中有一些檢測,我們需要斷點調試找到這些檢測,但是因爲我們之前已經手動的把檢測給弄掉了,所以這裏勾不勾選都無所謂了。

第六步:等待調試jdb連接

jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8600

這裏的端口號需要從DDMS中查看,是個紅色小蜘蛛:

第七步:IDA中運行程序或者按F9

這裏可以一路往下按,因爲會加載很多系統so文件,一路按下去直到加載到了我們自己的so,這裏沒有檢測直接過去即可

運行成功就jdb連接成功了。

第八步:在Module中找到需要調試的so文件

找到之後雙擊進入so文件。

第九步:找到需要調試的函數

找到需要調試的函數之後,點擊進入查看即可,到這裏我們就給我們需要調試的函數下好了斷點信息,上面的幾個步驟都是基本操作,因爲之前已經介紹了很多遍了,這裏就不再多介紹了,如果想了解更多內容可以購買本人的逆向大黃書《Android應用安全防護和逆向分析》;然後我們在通過靜態分析獲取到我們那個aes解密算法地方:

我們可以通過靜態分析so找到那個需要調試的函數地址:

看到這裏雙開IDA的好處是動靜結合非常方便,這時候我們只需要查看函數調用前的參數值也就是那幾個MOV指令的寄存器值:

R0寄存器保存的是需要解密的內容:

這裏看到後面的幾個參數很可能就是key或者是iv值,我們點擊查看R3寄存器的詳細值:

看到這裏的數據有點特別,首先是16個字節的不知道是啥可能是iv或者是key值,但是後面接着是一個16字節的值,所以這裏看到這兩個16字節的值可能就是我們想要的key和iv值了,所以這裏有一個重要的知識點就是key和iv肯定是16字節值,因爲用的是aes_cbc_128的加密方式。可以繼續往下看:

看到這個不確定是iv還是key的值,接着往下走:

看到這個解密後的內容就開心多了,而且看到後面的值是9,也就是總長度16-9=7也就是version的長度,這裏的總長度是固定的16,因爲Java層傳遞的字節數組長度是16。

 

四、Hook獲取解密算法

好了到這裏我們成功的獲取到了需要的key值和iv值,但是不確定他們是具體的值,這個簡單,我們用Java代碼寫一個然後嘗試互換彼此即可,但是到這裏就結束了嗎?其實不然,因爲按照我的性格我會通過一個案例把我知道的我會的統統告訴大家,所以我們用另外一種方式獲取key和iv值,因爲看到上面的動態調試雖然靠譜但是過於繁瑣,所以這裏就用之前介紹過的Frida框架來hook這個解密函數直接打印他的幾個參數值,關於Frida來hook功能不瞭解的同學可以查看這一篇文章:Hook神器Frida介紹;這裏我們看到這個加密函數是導出的:

然後寫一下frida的hook腳本:

然後我們就開始運行了,當然前提是你得安裝好Frida環境,具體內容看我的那一篇文章即可:

第一步:運行手機端的frida-server

第二步:轉發端口

第三步:運行hook腳本

運行之後我們就看到數據了,通過和上面的IDA動態調試出來的數據也是一致的,而且發現這種hook方式太無敵了,非常方便快捷,太好用了。

 

五、Java代碼實現解密算法

接下來我們就把這個key和iv放到Java代碼中運行一下吧:

這裏不確定key和iv值,互換一下嘗試就弄出來了:

然後運行就成功解密了:

 

六、技術總結

到這裏我們終於把so中的加密算法解密出來了,本文的內容非常多,因爲我不想給大家只是一個結果,我在這個過程中我遇到的問題和解決辦法以及我學到的東西我都想告訴你們,接下來我們總結本文的內容:

第一、修改指令,如果不想讓一個函數執行,只需要把跳轉指令修改成NOP空指令即可,前提是這個函數的執行結果和後面的邏輯沒有半毛錢的關係,如果有那麼就需要修改函數的返回值,一般需要修改跳轉指令之後的MOVS指令的寄存器值,如果簡單點可以直接修改變量的初始化值,比如這裏的過掉簽名校驗。

第二、如果快速的知道so中是否有簽名校驗功能,可以直接在字符串列表中搜索"signatures"即可,現在也有很多應用會在so中調用Java層的類信息,所以需要去看JNI_OnLoad中arm指令,或者直接搜索字符串列表,因爲一般Java層類信息,都是xxx/yyy/zzz/MMM這樣的字符串格式,通過肉眼排查也是可以的。

第三、動態調試so的步驟還是需要熟悉的,但是有時候爲了方便快捷Frida直接hook得到結果也是一個非常不錯的選擇。

第四、對於一些常規的加密算法的特點一定要知道,這個是對於一個破解者最基本的素質要求,本文如果知道了aes_cbc_128的加密方式的key和iv都是16個字節對於本文來說是非常關鍵的。沒事別看那些步兵騎兵啥的,多看看加密算法的特點。

 

本文的目的只有一個就是學習更多的逆向技巧和思路,如果有人利用本文技術去進行非法商業獲取利益帶來的法律責任都是操作者自己承擔,和本文以及作者沒關係,本文涉及到的代碼項目可以去編碼美麗小密圈自取,歡迎加入小密圈一起學習探討技術

 

七、總結

感謝這位同學提供的樣本,每一次樣本我們都可以學到很多東西,本文通過動態調試,Hook操作都是可以獲取到解密的key信息,所以問題只有一個,辦法有很多種。寫這一篇文章真的不容易光截圖就幾十張了,看完希望大家多多轉發支持!

 

《Android應用安全防護和逆向分析》

點擊立即購買:京東  天貓  

更多內容:點擊這裏

關注微信公衆號,最新技術乾貨實時推送


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章