Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android逆向之旅---動態方式破解apk終極篇(加固apk破解方式)

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

編輯:關於Android編程

一、前言

現在要說的就是最後一篇了,如何應對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中大體的破解方式,當然這三種方式不是萬能的,因為加固和破解是相生相克的,沒有哪個有絕對的優勢,只是兩者相互進步罷了,當然還有很多其他的破解方式,後面如果遇到的話,會在詳細說明,我們的目的不是編寫應用,而且讓別人的應用變成炮灰!!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved