Android應(yīng)用方法隱藏及反調(diào)試技術(shù)淺析

轉(zhuǎn)載:http://www.see-source.com/blog/300000105/963.html

0×00、前言
Android應(yīng)用的加固和對(duì)抗不斷升級(jí),單純的靜態(tài)加固效果已無(wú)法滿足需求,所以出現(xiàn)了隱藏方法加固,運(yùn)行時(shí)動(dòng)態(tài)恢復(fù)和反調(diào)試等方法來(lái)對(duì)抗,本文通過實(shí)例來(lái)分析有哪些對(duì)抗和反調(diào)試手段。
0×01、對(duì)抗反編譯
首先使用apktool進(jìn)行反編譯,發(fā)現(xiàn)該應(yīng)用使用的加固方式會(huì)讓apktool卡死,通過調(diào)試apktool源碼,發(fā)現(xiàn)解析時(shí)拋出異常,如下圖:


根據(jù)異常信息可知是readSmallUint出錯(cuò),調(diào)用者是getDebugInfo,查看源碼如下:

可見其在計(jì)算該偏移處的uleb值時(shí)得到的結(jié)果小于0,從而拋出異常。
在前文《Android程序的反編譯對(duì)抗研究》中介紹了DEX的文件格式,其中提到與DebugInfo相關(guān)的字段為DexCode結(jié)構(gòu)的debugInfoOff字段。猜測(cè)應(yīng)該是在此處做了手腳,在010editor中打開dex文件,運(yùn)行模板DEXTemplate.bt,找到debugInfoOff字段。果然,該值被設(shè)置為了0xFEEEEEEE。

接下來(lái)修復(fù)就比較簡(jiǎn)單了,由于debugInfoOff一般情況下是無(wú)關(guān)緊要的字段,所以只要關(guān)閉異常就行了。
為了保險(xiǎn)起見,在readSmallUint方法后面添加一個(gè)新方法readSmallUint_DebugInfo,復(fù)制readSmallUint的代碼,if語(yǔ)句內(nèi)result賦值為0并注釋掉拋異常代碼。

然后在getDebugInfo中調(diào)用readSmallUint_DebugInfo即可。

重新編譯apktool,對(duì)apk進(jìn)行反編譯,一切正常。
然而以上只是開胃菜,雖然apktool可以正常反編譯了,但查看反編譯后的smali代碼,發(fā)現(xiàn)所有的虛方法都是native方法,而且類的初始化方法<clinit>中開頭多了2行代碼,如下圖:

其基本原理是在dex文件中隱藏虛方法,運(yùn)行后在第一次加載類時(shí)通過在<clinit>方法(如果沒有<clinit>方法,則會(huì)自動(dòng)添加該方法)中調(diào)用ProxyApplication的init方法來(lái)恢復(fù)被隱藏的虛方法,其中字符串 "aHcuaGVsbG93b3JsZC5NYWluQWN0aXZpdHk=" 是當(dāng)前類名的base64編碼。
ProxyApplication類只有2個(gè)方法,clinit和init,clinit主要是判斷系統(tǒng)版本和架構(gòu),加載指定版本的so保護(hù)模塊(X86或ARM);而init方法也是native方法,調(diào)用時(shí)直接進(jìn)入了so模塊。

那么它是如何恢復(fù)被隱藏的方法的呢?這就要深入SO模塊內(nèi)部一探究竟了。
0×02、動(dòng)態(tài)調(diào)試so模塊
如何使用IDA調(diào)試android的SO模塊,網(wǎng)上有很多教程,這里簡(jiǎn)單說(shuō)明一下。
1、準(zhǔn)備工作
1.1、準(zhǔn)備好模擬器并安裝目標(biāo)APP。
1.2、將IDA\dbgsrv\目錄下的android_server復(fù)制到模擬器里,并賦予可執(zhí)行權(quán)限。
adb push d:\IDA\dbgsrv\android_server /data/data/svadb shell chmod 755 /data/data/sv
1.3、運(yùn)行android_server,默認(rèn)監(jiān)聽23946端口。
adb shell /data/data/sv
1.4、端口轉(zhuǎn)發(fā)。
adb forward tcp:23946 tcp:23946
2、以調(diào)試模式啟動(dòng)APP,模擬器將出現(xiàn)等待調(diào)試器的對(duì)話框。
adb shell am start -D -n hw.helloworld/hw.helloworld.MainActivity

3、啟動(dòng)IDA,打開debugger->attach->remote Armlinux/andoid debugger,設(shè)置hostname為localhost,port為23946,點(diǎn)擊OK;然后選擇要調(diào)試的APP并點(diǎn)擊OK。
[圖片上傳中。。。(9)]
這時(shí),正常狀態(tài)下會(huì)斷下來(lái):

然后設(shè)置在模塊加載時(shí)中斷:

點(diǎn)擊OK,按F9運(yùn)行。
然后打開DDMS并執(zhí)行以下命令,模擬器就會(huì)自動(dòng)斷下來(lái):
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
(如果出現(xiàn)如下無(wú)法附加到目標(biāo)VM的錯(cuò)誤,可嘗試端口8600)
[圖片上傳中。。。(12)]
此時(shí),可在IDA中正常下斷點(diǎn)調(diào)試,這里我們斷JNI_OnLoad和init函數(shù)。


由于IDA調(diào)試器還不夠完善,單步調(diào)試的時(shí)候經(jīng)常報(bào)錯(cuò),最好先做一個(gè)內(nèi)存快照,然后分析關(guān)鍵點(diǎn)的函數(shù)調(diào)用,在關(guān)鍵點(diǎn)下斷而不是單步調(diào)試。
0×03、反調(diào)試初探
一般反調(diào)試在JNI_OnLoad中執(zhí)行,也有的是在INIT_ARRAY段和INIT段中早于JNI_OnLoad執(zhí)行。可通過readelf工具查看INIT_ARRAY段和INIT段的信息,定位到對(duì)應(yīng)代碼進(jìn)行分析。
[圖片上傳中。。。(15)]
INIT_ARRAY如下:

其中函數(shù)sub_80407A88的代碼如下,通過檢測(cè)時(shí)間差來(lái)檢測(cè)是否中間有被單步調(diào)試執(zhí)行:

sub_8040903C函數(shù)里就是脫殼了,首先讀取/proc/self/maps找到自身模塊基址,然后解析ELF文件格式,從程序頭部表中找到類型為PT_LOAD,p_offset!=0的程序頭部表項(xiàng),并從該程序段末尾讀取自定義的數(shù)組,該數(shù)組保存了被加密的代碼的偏移和大小,然后逐項(xiàng)解密。
[圖片上傳中。。。(18)]
函數(shù)check_com_android_reverse里檢測(cè)是否加載了com.android.reverse,檢測(cè)到則直接退出。
[圖片上傳中。。。(19)]
JNI_OnLoad函數(shù)中有幾個(gè)關(guān)鍵的函數(shù)調(diào)用:
[圖片上傳中。。。(20)]
call_system_property_get檢測(cè)手機(jī)上的一些硬件信息,判斷是否在調(diào)試器中。
[圖片上傳中。。。(21)]
checkProcStatus函數(shù)檢測(cè)進(jìn)程的狀態(tài),打開/proc/$PID/status,讀取第6行得到TracerPid,發(fā)現(xiàn)被跟蹤調(diào)試則直接退出。
[圖片上傳中。。。(22)]
通過命令行查詢進(jìn)程信息,一共有3個(gè)同名進(jìn)程,創(chuàng)建順序?yàn)?3->415->430->431。其中415和431處于調(diào)試狀態(tài):
[圖片上傳中。。。(23)]
進(jìn)程415被進(jìn)程405(即IDA的android_server)調(diào)試:
[圖片上傳中。。。(24)]
進(jìn)程431被其父進(jìn)程430調(diào)試:
[圖片上傳中。。。(25)]
要過這種反調(diào)試可在調(diào)用點(diǎn)直接修改跳轉(zhuǎn)指令,讓代碼在檢測(cè)到被調(diào)試后繼續(xù)正常的執(zhí)行路徑,或者干脆nop掉整個(gè)函數(shù)即可。
檢測(cè)調(diào)試之后,就是調(diào)用ptrace附加自身,防止其他進(jìn)程再一次附加,起到反調(diào)試作用。
[圖片上傳中。。。(26)]
修改跳轉(zhuǎn)指令BNE(0xD1)為B(0xE0),直接返回即可。
[圖片上傳中。。。(27)]
當(dāng)然,更加徹底的方法是修改android源碼中bionic中的libc中的ptrace系統(tǒng)調(diào)用。檢測(cè)到一個(gè)進(jìn)程試圖附加自身時(shí)直接返回0即可。
上面幾處反調(diào)試點(diǎn)在檢測(cè)到調(diào)試器后都直接調(diào)用exit()退出進(jìn)程了,所以直接nop掉后按F9執(zhí)行。然后就斷在了init函數(shù)入口,順利過掉反調(diào)試:
[圖片上傳中。。。(28)]
init函數(shù)在每個(gè)類加載的時(shí)候被調(diào)用,用于恢復(fù)當(dāng)前類的被隱藏方法.首次調(diào)用時(shí)解密dex文件末尾的附加數(shù)據(jù),得到事先保存的所有類的方法屬性,然后根據(jù)傳入的類名查找該類的被隱藏方法,并恢復(fù)對(duì)應(yīng)屬性字段。
執(zhí)行完init函數(shù),當(dāng)前類的方法已經(jīng)恢復(fù)了。然后轉(zhuǎn)到dex文件的內(nèi)存地址:
[圖片上傳中。。。(29)]
dump出dex文件,保存為dump.dex。
[圖片上傳中。。。(30)]
0×04、恢復(fù)隱藏方法
對(duì)比一下原始dex文件,發(fā)現(xiàn)dex文件末尾的附加數(shù)據(jù)被解密出來(lái)了:
[圖片上傳中。。。(31)]
仔細(xì)分析一下附加數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)可以發(fā)現(xiàn),它是一個(gè)數(shù)組,保存了所有類的所有方法的method_idx、access_flags、code_off、debug_info_off屬性,解密后的這些屬性都是uint類型的,如下圖:
[圖片上傳中。。。(32)]
其中黃色框里的就是MainActivity的各方法的屬性,知道這些就可以修復(fù)dex文件,恢復(fù)出被隱藏的方法了。下圖就是恢復(fù)后的MainActivity類:
[圖片上傳中。。。(33)]
0×05、總結(jié)
以上就是通過實(shí)例分析展示出來(lái)的對(duì)抗和反調(diào)試手段。so模塊中的反調(diào)試手段比較初級(jí),可以非常簡(jiǎn)單的手工patch內(nèi)存指令過掉,而隱藏方法的這種手段對(duì)art模式不兼容,不推薦使用這種方法加固應(yīng)用。總的來(lái)說(shuō)還是過于簡(jiǎn)單。預(yù)計(jì)未來(lái)通過虛擬機(jī)來(lái)加固應(yīng)用將是一大發(fā)展方向。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容