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

一、前言

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

第一篇:如何使用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信息:

0?wx_fmt=pnguploading.4e448015.gif轉存失敗重新上傳取消

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

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

好了,再次點擊運行:

OK了,成功了,從這個log信息可以看出來了,解密之後的js對象名稱是:SmokeyBear,那麼下面就簡單了,我們在構造一個url頁面,直接調用:SmokeyBear.showToast即可。

 

注意:

這裏我們看到,如果知道了Java層的native方法的定義,那麼我們就可以調用這個native方法來獲取native層的函數功能了,這個還是很不安全的,但是我們如何防止自己的so被別人調用呢?可以在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中大體的破解方式,當然這三種方式不是萬能的,因爲加固和破解是相生相剋的,沒有哪個有絕對的優勢,只是兩者相互進步罷了,當然還有很多其他的破解方式,後面如果遇到的話,會在詳細說明。

 

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

點擊立即購買:京東  天貓  

更多內容:點擊這裏

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

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