三、Android安全機制之Apk防護

一、代碼和資源混淆

1. 代碼混淆

    Android使用的ProGuard,起到壓縮,混淆,預檢,優化的作用,用法就是在build.gradle文件中minifyEnabled設置屬性爲true,然後在proguard-android.txt編寫相應的規則,混淆是用“a b c”等字符替換程序類名、變量名和方法名,加大了反編譯後代碼的閱讀難度,同時還有一個好處就是減少了Apk的體積。

2. 資源混淆

    資源混淆主要爲了混淆資源ID長度,同時利用7z深度壓縮,大大減少了安裝包體積,提升了反破解難度。採用微信開源工具AndResGuard做Android資源混淆,資源混淆簡單來說希望實現將res/drawable/icon,png變成res/drawable/a.png,甚至可以將文件路徑也同時混淆,改成r/s/a.png。

資源混淆主要通過修改resources.arsc來實現的,所以首先需要對其文件格式有一定的瞭解resources.arsc一共有五種chunk類型,分別爲TYPETABLE,TYPEPACKAGE,TYPESTRING ,TYPETYPE,TYPECONFIG。


--package,指的是一個package的開始,其實在resources.arsc是可以有多個package的。而packageID即是資源resID的最高八位,一般來說系統android的是1(0x01),普通的例如com.tencent.mm會是127(0x7f),剩下的是從2開始起步。當然這個我們在aapt也是可以指定的(1-127即八位的合法空間,一些混合編譯就是改這個packageID)。

--string, 代表stringblock,我們一共有三種類型的stringblock。分別是table stringblock,typename stringblock, specsname stringblock。

--type,這裏講的是typename stringblock裏面我們用到的各種type(用到多少種類型的type,就有多少個type chunk,例如attr, drawable, layout, id, color, anim等,Type ID是緊跟着Package ID。

--config, 即是Android用來描述資源維度,例如橫豎屏,屏幕密度,語言等。對於每一種type,它定義了多少種config,它後面就緊跟着多少個config chunk,例如我們定義了drawable-mdpi,drawable-hdpi,那後面就會有兩個config。

--entry,儘管沒有entry這個chunk,但是每個config裏面都會有很多的entry,例如drawable-mdpi中有icon1.png,icon2.png兩個drawable,那在mdpi這個config中就存在兩個entry。

 

typename stringblock,--type,--config,--entry的直觀的關係樹狀圖如下:



簡單來說方案爲:


reference:

http://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=208135658&idx=1&sn=ac9bd6b4927e9e82f9fa14e396183a8f#rd

二、Apk加固

      即是給Dex或者So加殼,一般附帶有反調試,防二次打包的功能,目前提供這方面服務的廠商還是很多的,BAT,網易,360,愛加密,梆梆,娜迦,通付盾,網秦,頂象,幾維等等,兩年前我瞭解的時候,也只有百度,愛加密和梆梆有這方面服務,當時的加固也就是第一代加固了。

      第一代加固技術原理是對dex加密,運行時動態解密,因爲加載到內存中是完整的dex,所以這種方式容易被脫殼,只要hook住系統加載dex的函數,如dexFileParse,即可導出解密後的dex,這方面的脫殼工具也是蠻多的,比如dexHunter,drizzleDumper,ZjDroid,DexExtractor。

      第二代加固技術是在第一代的基礎上加了函數級別的加密,主要的流程是:發佈階段將原始DEX內的函數內容清除,單獨移除到一個加密文件中,虛擬機讀取DEX文件後,內部的每一個函數有一個結構體,這個結構體上有一個指針指向函數內容,可以通過修改這個指針指向原函數內容。或者攔截虛擬機內與查找執行代碼相關的函數,返回解密後的函數內容。強度再高一點的就是直接把dex函數標記爲native,內容被抽離並轉換成jni動態庫,舉個例子,比如目前的360加固,脫殼出來的dex的onCreate函數被動態註冊爲native函數,在未修復前是無法還原和安裝運行的。

      未來的第三代加固技術是VMP殼,由windows平臺演變而來,主要是保護Native代碼。本人暫未研究,就不多介紹了。

      Apk加固能起到比較大的安全作用,對逆向來說就多了一道門坎,就是脫殼,對於有加固的Apk,逆向之前首要的任務就是脫殼,而且脫殼也是一件費時費力的事情,非專業人士一看到加固的Apk基本就知難而退了。當然加固也有一些弊端就是無法保證加固後的Apk在所有廠商訂製平臺上運行良好,所以一些大廠的Apk基本是沒做加固的。

三、簽名校驗

      簽名校驗,被重編譯其簽名肯定是不一樣的,所以目前最常用的防止重編譯的方式也就是簽名校驗。通過獲取應用自身的簽名然後進行比對,如果不一致則說明是破解版,禁止下一步的操作。但是現在這種簽名校驗的方式已經不安全了,通過Hook技術可以輕鬆的破解。

      Hook技術在Java層一般是採用反射和動態代理實現,也有通過方法替換來實現的,比如Legend,YAHFA,ArtHook等,但是對系統版本都有所限制。

      在Native層的話,主要是通過解析映射到內存中的elf的結構,解析出GOT表,GOT表中存儲的是elf調用外部函數地址,通過Hook替換之。代表項目是AllHookInOne,ELF文件格式提供了兩種視圖,分別是鏈接視圖和執行視圖,鏈接視圖是以節(section)爲單位,執行視圖是以段(segment)爲單位。動態鏈接庫在加載的過程中,linker只關注ELF中的段(Segment)信息。因此ELF中的節信息被完全篡改或者甚至刪除掉,並不會影響linker的加載過程,所以這裏是基於執行視圖(Execution View)進行符號解析。

      在root的情況下,Xposed通過替換/system/bin/app_process程序控制zygote進程,使得app_process在啓動過程中會加載XposedBridge.jar這個jar包,從而完成對Zygote進程及其創建的Dalvik虛擬機的劫持,可以達到hook整個系統中所有進程內存裏面的對象的目的。

      Hook應用獲取簽名的方法是採用Java層反射和動態代理實現,大概思想就是通過接口生成一個代理對象,通過反射替換內存中的接口對象,並持有原對象的引用,這樣每次對原對象操作的時候都會先經過代理對象,代理對象就可以修改傳入參數,或者直接返回結果。


      獲取簽名一般是通過PackageManager類的getPackageInfo方法,其底層ActivityThread是通過AIDL接口IPackageManager來與系統的PackageManagerService進行交互的,所以我們只要hook我們應用內的IPackageManager就可以達到修改PMS的一些服務目的,通過hookPms來修改應用內簽名獲取的方法,反編譯的時候,遇到反編譯的APP有簽名校驗的時候,可以用事先獲取到的真實簽名數據來替換APP內獲取的簽名,從而達到破解簽名校驗的目的。

      可見通過PackageManager獲取簽名進行校驗已經不安全,不過可以通過解壓APK裏的文件來進行MD5校驗,判斷是否被篡改,比如判斷dex或者簽名文件裏的RSA文件都可以,需要注意的是,每次編譯後,其值都會改變,所以無法放在代碼裏存儲,一般存雲端。

四、其他

1,重要邏輯代碼用C/C++實現,因爲相比逆向Java代碼,逆向NDK程序的彙編代碼是一件及其枯燥和艱難的事情,能堅持下來的人少之又少,沒有彙編基礎的人更是極易放棄。

2,防debug,AndroidMainfest的Application標籤的debug屬性設爲false,當然release版本自動會幫你加上此屬性爲false,但是此屬性是很容易被逆向修改的,所以最好在代碼再做一層判斷。再者就是NDK層防調試的方法一般是輪訓檢查進程的TracerPid值,如果非0說明被調試了,因爲如果應用被調試了,那麼它的TracerPid值就是調試進程的pid值。

3,關閉log等調試信息,如果log信息沒關閉,那麼很可能就會被利用。如果破解者想知道軟件中某個功能的實現流程,並且執行此功能的時候有log輸出,那麼就可以根據log打印的信息定位到具體某處代碼,在此處打印堆棧信息就可以知道代碼的流程。

4,防Activity劫持,Activity劫持的概念:惡意的應用在後臺檢測到目標進程的Activity在運行,從而彈出一個頁面相仿的釣魚Activity,欺騙用戶輸入敏感信息從而進行竊取。防Activity劫持目前並沒有太有效的手段,一般處理方式就是在Activity的onStop方法里加上“應用已在後臺運行”的提示,用於警示用戶。
5,防截屏,獲取了root權限的惡意應用是可以通過連續地調用系統截屏方式來竊取用戶輸入的密碼的,所有應該在密碼輸入頁面所屬的Activity添加一項getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE),即可禁止掉截屏。微信的登錄頁面也做了此項防護措施。

6,防雙開軟件,雙開軟件利用插件技術給App創造了一個虛擬沙盒,隔離了App與Android系統的交互,使App進程成爲了雙開軟件的子進程,從而擁有了子App的訪問權限,進而導致這個App內的數據在非root的情況下也可以被隨意地獲取和修改。比如修改某些變量的值,修改某些方法的參數或返回值,或者直接替換整個方法的代碼邏輯等。檢測應用是否被雙開也沒那麼簡單,因爲母軟件充當了中介,子軟件想獲取一些特徵都得經過中介來與系統交互,所以獲取的信息可能被中介做了改動,導致判斷錯誤。比如往\data\data\包名下寫文件,最終被重定向到雙開軟件的目錄下,返回子應用的寫成功,所以通過這種方式來判斷是否被雙開是行不通的。以下是本人經過測試行之有效的判斷方法:
原理是每個應用有一個獨立的uid,當前uid下我們可以開一個或多個我們知道的進程,當檢測出現了我們不知道的進程,那說明被雙開了,具體代碼如下:
String result = RuntimeUtil.exec("ps | grep  "+android.os.Process.myPid()+" ");//獲取當前進程信息
String uid = result.substring(0,result.indexOf(" "));//截取得當前應用uid
//根據uid查找當前應用下的所有進程,過濾掉查找進程
result = RuntimeUtil.exec("ps | grep "+uid+" | grep -v sh"+" | grep -v ps"+" | grep -v grep");
Log.d("---->","本應用下的所有進程");
Log.d("---->",result);
然後分別在正常打開和雙開打開的情況下,查看log

在雙開的情況下多了兩個未知進程(即雙開應用virtualapp的進程),據此,可判斷應用被雙開。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章