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方法,展示的內容:u7965u9f99uff01 ,我們在線轉化一下:
擦,這里就是題目要求展示的內容。
好了,到這里我們就分析完了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中大體的破解方式,當然這三種方式不是萬能的,因為加固和破解是相生相克的,沒有哪個有絕對的優勢,只是兩者相互進步罷了,當然還有很多其他的破解方式,后面如果遇到的話,會在詳細說明,我們的目的不是編寫應用,而且讓別人的應用變成炮灰!!
總結
以上是生活随笔為你收集整理的Android动态方式破解apk终极篇(加固apk破解方式)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spark SQL读写方法
- 下一篇: mac终端命令sftp