Android逆向之旅---動態方式破解apk終極篇(加固apk破解方式)

yangxi_001發表於2016-12-02

一、前言

今天總算迎來了破解系列的最後一篇文章了,之前的兩篇文章分別為:

第一篇:如何使用Eclipse動態除錯smali原始碼 

第二篇:如何使用IDA動態除錯SO檔案

現在要說的就是最後一篇了,如何應對Android中一些加固apk安全防護,在之前的兩篇破解文章中,我們可以看到一個是針對於Java層的破解,一個是針對於native層的破解,還沒有涉及到apk的加固,那麼今天就要來介紹一下如何應對現在市場中一些加固的apk的破解之道,現在市場中加固apk的方式一般就是兩種:一種是對源apk整體做一個加固,放到指定位置,執行的時候在解密動態載入,還有一種是對so進行加固,在so載入記憶體的時候進行解密釋放。我們今天主要看第一種加固方式,就是對apk整體進行加固。


二、案例分析

按照國際慣例,咋們還是得用一個案例來分析講解,這次依然採用的是阿里的CTF比賽的第三題:


題目是:要求輸入一個網頁的url,然後會跳轉到這個頁面,但是必須要求彈出指定內容的Toast提示,這個內容是:祥龍!

瞭解到題目,我們就來簡單分析一下,這裡大致的邏輯應該是,輸入的url會傳遞給一個WebView控制元件,進行展示網頁,如果按照題目的邏輯的話,應該是網頁中的Js會呼叫本地的一個Java方法,然後彈出相應的提示,那麼這裡我們就來開始操作了。

按照我們之前的破解步驟:

第一步:肯定是先用解壓軟體搞出來他的classes.dex檔案,然後使用dex2jar+jd-gui進行檢視java程式碼


擦,這裡我們看到這裡只有一個Application類,從這裡我們可以看到,這個apk可能被加固了,為什麼這麼說呢?因為我們知道一個apk加固,外面肯定得套一個殼,這個殼必須是自定義的Application類,因為他需要做一些初始化操作,那麼一般現在加固的apk的殼的Application類都喜歡叫StubApplication。而且,這裡我們可以看到,除了一個Application類,沒有其他任何類了,包括我們的如可Activity類都沒有了,那麼這時候會發現,很蛋疼,無處下手了。


第二步:我們會使用apktool工具進行apk的反編譯,得到apk的AndroidManifest.xml和資源內容



反編譯之後,看到程式會有一個入口的Activity就是MainActivity類,我們記住一點就是,不管最後的apk如何加固,即使我們看不到程式碼中的四大元件的定義,但是肯定會在AndroidManifest.xml中宣告的,因為如果不宣告的話,執行是會報錯的。那麼這裡我們也分析完了該分析的內容,還是沒發現我們的入口Activity類,而且我們知道他肯定是放在本地的一個地方,因為需要解密動態載入,所以不可能是放在網上的,肯定是本地,所以這裡就有一些技巧了:

當我們發現apk中主要的類都沒有了,肯定是apk被加固了,加固的源程式肯定是在本地,一般會有這麼幾個地方需要注意的:

1、應用程式的asset目錄,我們知道這個目錄是不參與apk的資源編譯過程的,所以很多加固的應用喜歡把加密之後的源apk放到這裡

2、把源apk加密放到殼的dex檔案的尾部,這個肯定不是我們這裡的案例,但是也有這樣的加固方式,這種加固方式會發現使用dex2jar工具解析dex是失敗的,我們這時候就知道了,肯定對dex做了手腳

3、把源apk加密放到so檔案中,這個就比較難了,一般都是把源apk進行拆分,存到so檔案中,分析難度會加大的。

一般都是這三個地方,其實我們知道記住一點:就是不管源apk被拆分,被加密了,被放到哪了,只要是在本地,我們都有辦法得到他的。

好了,按照這上面的三個思路我們來分析一下,這個apk中加固的源apk放在哪了?

通過剛剛的dex檔案分析,發現第二種方式肯定不可能了,那麼會放在asset目錄中嗎?我們檢視asset目錄:


看到asset目錄中的確有兩個jar檔案,而且我們第一反應是使用jd-gui來檢視jar,可惜的是開啟失敗,所以猜想這個jar是經過處理了,應該是加密,所以這裡很有可能是存放源apk的地方。但是我們上面也說了還有第三種方式,我們去看看libs目錄中的so檔案:


擦,這裡有三個so檔案,而我們上面的Application中載入的只有一個so檔案:libmobisec.so,那麼其他的兩個so檔案很有可能是拆分的apk檔案的藏身之處。

通過上面的分析之後,我們大致知道了兩個地方很有可能是源apk的藏身地方,一個是asset目錄,一個是libs目錄,那麼分析完了之後,我們發現現在面臨兩個問題:

第一個問題:asset目錄中的jar檔案被處理了,打不開,也不知道處理邏輯

第二個問題:libs目錄中的三個so檔案,唯一載入了libmobisec.so檔案了

那麼這裡現在的唯一入口就是這個libmobisec.so檔案了,因為上層的程式碼沒有,沒法分析,下面來看一下so檔案:


擦,發現蛋疼的是,這裡沒有特殊的方法,比如Java_開頭的什麼,所以猜測這裡應該是自己註冊了native方法,混淆了native方法名稱,那麼到這裡,我們會發現我們遇到的問題用現階段的技術是沒法解決了。


三、獲取正確的dex內容

分析完上面的破解流程之後,發現現在首要的任務是先得到源apk程式,通過分析知道,處理的源apk程式很難找到和分析,所以這裡就要引出今天說的內容了,使用動態除錯,給libdvm.so中的函式:dvmDexFileOpenPartial 下斷點,然後得到dex檔案在記憶體中的起始地址和大小,然後dump處dex資料即可。

那麼這裡就有幾個問題了:

第一個問題:為何要給dvmDexFileOpenPartial 這個函式下斷點?

因為我們知道,不管之前的源程式如何加固,放到哪了,最終都是需要被載入到記憶體中,然後執行的,而且是沒有加密的內容,那麼我們只要找到這的dex的記憶體位置,把這部分資料搞出來就可以了,管他之前是如何加固的,我們並不關心。那麼問題就變成了,如何獲取載入到記憶體中的dex的地址和大小,這個就要用到這個函式了:dvmDexFileOpenPartial 因為這個函式是最終分析dex檔案,載入到記憶體中的函式:

int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex);

第一個引數就是dex記憶體起始地址,第二個引數就是dex大小。

第二個問題:如何使用IDA給這個函式下斷點

我們在之前的一篇文章中說到了,在動態除錯so,下斷點的時候,必須知道一個函式在記憶體中的絕對地址,而函式的絕對地址是:這個函式在so檔案中的相對地址+so檔案對映到記憶體中的基地址,這裡我們知道這個函式肯定是存在libdvm.so檔案中的,因為一般涉及到dvm有關的函式功能都是存在這個so檔案中的,那麼我們可以從這個so檔案中找到這個函式的相對地址,執行程式之後,在找到libdvm.so的基地址,相加即可,那麼我們如何獲取到這個libdvm.so檔案呢?這個檔案是存放在裝置的/system/lib目錄下的:


那麼我們只需要使用adb pull 把這個so檔案搞出來就可以了。


好了,解決了這兩個問題,下面就開始操作了:

第一步:執行裝置中的android_server命令,使用adb forward進行埠轉發


這裡的android_server工具可以去ida安裝目錄中dbgsrv資料夾中找到



第二步:使用命令以debug模式啟動apk

adb shell am start -D -n com.ali.tg.testapp/.MainActivity


因為我們需要給libdvm.so下斷點,這個庫是系統庫,所以載入時間很早,所以我們需要像之前給JNI_OnLoad函式下斷點一樣,採用debugger模式執行程式,這裡我們通過上面的AndroidManifest.xml中,得到應用的包名和入口Activity:


而且這裡的android:debuggable=true,可以進行debug除錯的。


第三步:雙開IDA,一個用於靜態分析libdvm.so,一個用於動態除錯libdvm.so


通過IDA的Debugger選單,進行程式附加操作:



第四步:使用jdb命令啟動連線attach偵錯程式

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

但是這裡可能會出現這樣的錯誤:


這個是因為,我們的8700埠沒有指定,這時候我們可以通過Eclipse的DDMS進行埠的檢視:


看到了,這裡是8600埠,但是基本埠8700不在,所以這裡我們有兩種處理方式,一種是把上面的命令的埠改成8600,還有一種是選中這個應用,使其具有8700埠:


點選這個條目即可,這時候我們在執行上面的jdb命令:


處於等待狀態。


第四步:給dvmDexFileOpenPartial函式下斷點

使用一個IDA靜態分析得到這個函式的相對地址:43308


在動態除錯的IDA解密,使用Ctrl+S鍵找到libdvm.so的在記憶體中的基地址:41579000


然後將兩者相加得到絕對地址:43308+41579000=415BC308,使用G鍵,跳轉:


跳轉到dvmDexFileOpenPartial函式處,下斷點:



第五步:點選執行按鈕或者F9執行程式

之前的jdb命令就連線上了:


IDA出現如下介面,不要理會,一路點選取消按鈕即可


執行到了dvmDexFileOpenPartial函式處:


使用F8進行單步除錯,但是這裡需要注意的是,只要執行過了PUSH命令就可以了,記得不要越過下面的BL命令,因為我們沒必要走到那裡,當執行了PUSH命令之後,我們就是使用指令碼來dump處記憶體中的dex資料了,這裡有一個知識點,就是R0~R4暫存器一般是用來存放一個函式的引數值的,那麼我們知道dvmDexFileOpenPartial函式的第一個引數就是dex記憶體起始地址,第二個引數就是dex大小:


那麼這裡就可以使用這樣的指令碼進行dump即可:

static main(void)
{
    auto fp, dex_addr, end_addr;
    fp = fopen(“F:\\dump.dex”, “wb”);
    end_addr = r0 + r1;
    for ( dex_addr = r0; dex_addr < end_addr; dex_addr ++ )
        fputc(Byte(dex_addr), fp);
}

指令碼不解釋了,非常簡單,而且這個是固定的格式,以後dump記憶體中的dex都是這段程式碼,我們將dump出來的dex儲存到F盤中。

然後這時候,我們使用:Shirt+F2 調出IDA的指令碼執行介面:


點選執行,這裡可能需要等一會,執行成功之後,我們去F盤得到dump.dex檔案,其實這裡我們的IDA使命就完成了,因為我們得到了記憶體的dex檔案了,下面開始就簡單了,只要分析dex檔案即可


四、分析正確的dex檔案內容

我們拿到dump.dex之後,使用dex2jar工具進行反編譯:


可惜的是,報錯了,反編譯失敗,主要是有一個類導致的,開始我以為是dump出來的dex檔案有問題,最後我用baksmali工具得到smali檔案是可以的,所以不是dump出來的問題,我最後用baksmali工具將dex轉化成smali原始碼:

java -jar baksmali-2.0.3.jar -o C:\classout/ dump.dex


得到的smali原始碼目錄classout在C盤中:


我們得到了指定的smali原始碼了。


那麼下面我們就可以使用靜態方式分析smali即可了:

首先找到入口的MainActivity原始碼:


這裡不解釋了,肯定是找按鈕的點選事件程式碼處,這裡是一個btn_listener變數,看這個變數的定義:


是MainActivity$1內部類定義,檢視這個類的smali原始碼,直接檢視他的onClick方法:


這裡可以看到,把EditText中的內容,用Intent傳遞給WebViewActivity中,但是這裡的intent資料的key是加密的。

下面繼續看WebViewActivity這個類:


我們直接查詢onCreate方法即可,看到這裡是初始化WebView,然後進行一些設定,這裡我們看到一個@JavascriptInterface

這個註解,我們在使用WebView的時候都知道,他是用於Js中能夠訪問的設定了這個註解的方法,沒有這個註解的方法Js是訪問不了的

注意:

我們知道這個註解是在SDK17加上的,也就是Android4.2版本中,那麼在之前的版本中沒有這個註解,任何public的方法都可以在JS程式碼中訪問,而Java物件繼承關係會導致很多public的方法都可以在JS中訪問,其中一個重要的方法就是  getClass()。然後JS可以通過反射來訪問其他一些內容。那麼這裡就有這個問題了:比如下面的一段JS程式碼:

<script>
function findobj(){
for (var obj in window) { 
if ("getClass" in window[obj]) { 
return window[obj] 


}
</script> 

看到了,這段js程式碼很危險的,使用getClass方法,得到這個物件(java中的每個物件都有這個方法的),用這個方法可以得到一個java物件,然後我們就可以呼叫這個物件中的方法了。這個也算是WebView的一個漏洞了。

所以通過引入 @JavascriptInterface註解,則在JS中只能訪問 @JavascriptInterface註解的函式。這樣就可以增強安全性。


迴歸到正題,我們上面分析了smali原始碼,看到了WebView的一些設定資訊,我們可以繼續往下面看:


這裡的我們看到了一些重要的方法,一個是addJavascriptInterface,一個是loadUrl方法。

我們知道addjavaascriptInterface方法一般的用法:

mWebView.addJavascriptInterface(new JavaScriptObject(this), "jiangwei");

第一個引數是本地的Java物件,第二個引數是給Js中使用的物件的名稱。然後js得到這個物件的名稱就可以呼叫本地的Java物件中的方法了。

看了這裡的addjavaascriptInterface方法程式碼,可以看到,這裡用

ListViewAutoScrollHelpern;->decrypt_native(Ljava/lang/String;I)Ljava/lang/String;

將js中的名稱進行混淆加密了,這個也是為了防止惡意的網站來攔截url,然後呼叫我們本地的Java中的方法。

注意:

這裡又存在一個關於WebView的安全問題,就是這裡的js訪問的物件的名稱問題,比如現在我的程式中有一個Js互動的類,類中有一個獲取裝置重要資訊的方法,比如這裡獲取裝置的imei方法,如果我們的程式沒有做這樣名稱的混淆的話,破解者得到這個js名稱和方法名,然後就偽造一個惡意url,來呼叫我們程式中的這個方法,比如這樣一個例子:


然後在設定js名稱:


我們就可以偽造一個惡意的url頁面來訪問這個方法,比如這個惡意的頁面程式碼如下:


執行程式:


看到了,這裡惡意的頁面就成功的呼叫了程式中的一個重要方法。

所以,我們可以看到,對Js互動中的物件名稱做混淆是必要的,特別是本地一些重要的方法。


迴歸到正題,我們分析完了WebView的一些初始化和設定程式碼,而且我們知道如果要被Js訪問的方法,那麼必須要有@JavascriptInterface註解 因為在Java中註解也是一個類,所以我們去註解類的原始碼看看那個被Js呼叫的方法:


這裡看到了有一個showToast方法,展示的內容:\u7965\u9f99\uff01 ,我們線上轉化一下:


擦,這裡就是題目要求展示的內容。


好了,到這裡我們就分析完了apk的邏輯了,下面我們來整理一下:

1、在MainActivity中輸入一個頁面的url,跳轉到WebViewActivity進行展示

2、WebViewActivity有Js互動,需要呼叫本地Java物件中的showToast方法展示訊息

問題:

因為這裡的js物件名稱進行了加密,所以這裡我們自己編寫一個網頁,但是不知道這個js物件名稱,無法完成showToast方法的呼叫


五、破解的方法

下面我們就來分析一下如何解決上面的問題,其實解決這個問題,我們現有的方法太多了

第一種方法:修改smali原始碼,把上面的那個js物件名稱改成我們自己想要的,比如:jiangwei,然後在自己編寫的頁面中直接呼叫:jiangwei.showToast方法即可,不過這裡需要修改smali原始碼,在使用smali工具回編譯成dex檔案,在弄到apk中,在執行。方法是可行的,但是感覺太複雜,這裡不採用

第二種方法:利用Android4.2中的WebView的漏洞,直接使用如下Js程式碼即可


這裡根本不需要任何js物件的名稱,只需要方法名就可以完成呼叫,所以這裡可以看到這個漏洞還是很危險的。

第三種方法:我們看到了那個加密方法,我們自己寫一個程式,來呼叫這個方法,盡然得到正確的js物件名稱,這裡我們就採用這種方式,因為這個方式有一個新的技能,所以這裡我就講解一下了。

那麼如果用第三種方法的話,就需要再去分析那個加密方法邏輯了:


android.support.v4.widget.ListViewAutoScrollHelpern在這個類中,我們再去查詢這個smali原始碼:


這個類載入了libtranslate.so庫,而且加密方法是native層的,那麼我們用IDA檢視libtranslate.so庫:


我們搜一下Java開頭的函式,發現並沒有和decrypt_native方法對應的native函式,說明這裡做了native方法的註冊混淆,我們直接看JNI_OnLoad函式:


這裡果然是自己註冊了native函式,但是分析到這裡,我就不往下分析了,為什麼呢?因為我們其實沒必要搞清楚native層的函式功能,我們知道了Java層的native方法定義,那麼我們可以自己定義一個這麼個native方法來呼叫libtranslate.so中的加密函式功能:


我們新建一個Demo工程,仿造一個ListViewAutoScrollHelpern類,內部在定義一個native方法:


然後我們在MainActivity中載入libtranslate.so:


然後呼叫那個native方法,列印結果:


這裡的方法的引數可以檢視smali原始碼中的那個方法引數:


點選執行,發現有崩潰的,我們檢視log資訊:


是libtranslate.so中有一個PagerTitleStripIcsn類找不到,這個類應該也有一個native方法,我們在構造這個類:


再次執行,還是報錯,原因差不多,還需要在構造一個類:TaskStackBuilderJellybeann


好了,再次點選執行:


OK了,成功了,從這個log資訊可以看出來了,解密之後的js物件名稱是:SmokeyBear,那麼下面就簡單了,我們在構造一個url頁面,直接呼叫:SmokeyBear.showToast即可。

注意:

這裡我們看到,如果知道了Java層的native方法的定義,那麼我們就可以呼叫這個native方法來獲取native層的函式功能了,這個還是很不安全的,但是我們如何防止自己的so被別人呼叫呢?之前的一篇文章:Android中的安全攻防之戰 已經說過了,可以在so中的native函式做一個應用的簽名校驗,只有屬於自己的簽名應用才能呼叫,否則直接退出。


六,開始測試

上面已經知道了js的物件名稱,下面我們就來構造這個頁面了:


那麼這裡又有一個問題了,這個頁面構造好了?放哪呢?有的同學說我有伺服器,放到伺服器上,然後輸入url地址就可以了,的確這個方法是可以的,但是有的同學沒有伺服器怎麼辦呢?這個也是有方法的,我們知道WebView的loadUrl方法是可以載入本地的頁面的,所以我們可以把這個頁面儲存到本地,但是需要注意的是,這裡不能存到SD卡中,因為這個應用沒有讀取SD的許可權,我們可以檢視他的AndroidManifest.xml檔案:


我們在不重新打包的情況下,是沒辦法做到的,那麼放哪呢?其實很簡單了,放在這個應用的/data/data/com.ali.tg.testapp/目錄下即可,因為除了SD卡位置,這個位置是最好的了,那麼我們知道WebView的loadUrl方法在載入本地的頁面的格式是:

file:///data/data/com.ali.tg.testapp/crack.html

那麼我們直接輸入即可

注意:

這裡在說一個小技巧:就是我們在一個文字框中輸入這麼多內容,是不是有點蛋疼,我們其實可以藉助於命令來實現輸入的,就是使用:adb shell input text ”我們需要輸入的內容“。

具體用法很簡單,開啟我們需要輸入內容的EditText,點選調出系統的輸入法介面,然後執行上面的命令即可:


不過這裡有一個小問題,就是他不識別分號:


不過我們直接修改成分號點選進入:


執行成功,看到了toast的展示。


手癢的同學可以戳這裡:http://download.csdn.net/detail/jiangwei0910410003/9543445


七、內容整理

到這裡我們就破解成功了,下面來看看整理一下我們的破解步驟:

1、破解的常規套路

我們按照破解慣例,首先解壓出classses.dex檔案,使用dex2jar工具檢視java程式碼,但是發現只有一個Application類,所以猜測apk被加殼了,然後用apktool來反編譯apk,得到他的資原始檔和AndroidManifest.xml內容,找到了包名和入口的Activity類。

2、加固apk的源程式一般存放的位置

知道是加固apk了,那麼我們就分析,這個加固的apk肯定是存放在本地的一個地方,一般是三個地方:

1》應用的asset目錄中

2》應用的libs中的so檔案中

3》應用的dex檔案的末尾

我們分析了一下之後,發現asset目錄中的確有兩個jar檔案,但是打不開,猜測是被經過處理了,所以我們得分析處理邏輯,但是這時候我們也沒有程式碼,怎麼分析呢?所以這時候就需要藉助於dump記憶體dex技術了:

不管最後的源apk放在哪裡,最後都是需要經歷解密動態載入到記憶體中的,所以分析底層載入dex原始碼,知道有一個函式:dvmDexFileOpenPartial 這個函式有兩個重要引數,一個是dex的其實地址,一個是dex的大小,而且知道這個函式是在libdvm.so中的。所以我們可以使用IDA進行動態除錯獲取資訊

3、雙開IDA開始獲取記憶體中的dex內容

雙開IDA,走之前的動態破解so方式來給dvmDexFileOpenPartial函式下斷點,獲取兩個引數的值,然後使用一段指令碼,將記憶體中的dex資料儲存到本地磁碟中。

4、分析獲取到的dex內容

得到了記憶體中的dex之後,我們在使用dex2jar工具去檢視原始碼,但是發現儲存,以為是dump出來的dex格式有問題,但是最後使用baksmali工具進行處理,得到smali原始碼是可以的,然後我們就開始分析smali原始碼。

5、分析原始碼瞭解破解思路

通過分析原始碼得知在WebViewActivity頁面中會載入一個頁面,然後那個頁面中的js會呼叫本地的Java物件中的一個方法來展示toast資訊,但是這裡我們遇到了個問題:Js的Java物件名稱被混淆加密了,所以這時候我們需要去分析那個加密函式,但是這個加密函式是native的,然後我們就是用IDA去靜態分析了這個native函式,但是沒有分析完成,因為我們不需要,其實很簡單,我們只需要結果,不需要過程,現在解密的內容我們知道了,native方法的定義也知道了,那麼我們就去寫一個簡單的demo去呼叫這個so的native方法即可,結果成功了,我們得到了正確的Js物件名稱。

6、瞭解WebView的安全性

WebView的早期版本的一個漏洞資訊,在Android4.2之前的版本WebView有一個漏洞,就是可以執行Java物件中所有的public方法,那麼在js中就可以這麼處理了,先獲取geClass方法獲取這個物件,然後在呼叫這個物件中的一些特定方法即可,因為Java中所有的物件都有一個getClass方法,而這個方法是public的,同時能夠返回當前物件。所以在Android4.2之後有了一個註解:

@JavascriptInterface ,只有這個註解標識的方法才能被Js中呼叫。

7、獲取輸入的新技能

驗證結果的過程中我們發現了一個技巧,就是我們在輸入很長的文字的時候,比較繁瑣,可以藉助adb shell input text命令來實現。


八、技術點概要

1、通過dump出記憶體中的dex資料,可以佛擋殺佛了,不管apk如何加固,最終都是需要載入到記憶體中的。

2、瞭解到了WebView的安全性的相關知識,比如我們在WebView中js物件名稱做一次混淆還是有必要的,防止被惡意網站呼叫我們的本地隱私方法。

3、可以嘗試呼叫so中的native方法,在知道了這個方法的定義之後

4、adb shell input text 命令來輔助我們的輸入


九、總結

這裡就介紹了Android中如何dump出那些加固的apk程式,其實核心就一個:不管上層怎麼加固,最終載入到記憶體的dex肯定不是加固的,所以這個dex就是我們想要的,這裡使用了IDA來動態除錯libdvm.so中的dvmDexFileOpenPartial函式來獲取記憶體中的dex內容,同時還可以使用gdb+gdbserver來獲取,這個感興趣的同學自行搜尋吧。結合了之前的兩篇文章,就算善始善終,介紹了Android中大體的破解方式,當然這三種方式不是萬能的,因為加固和破解是相生相剋的,沒有哪個有絕對的優勢,只是兩者相互進步罷了,當然還有很多其他的破解方式,後面如果遇到的話,會在詳細說明,我們的目的不是編寫應用,而且讓別人的應用變成炮灰!!

相關文章