一、代碼和資源混淆
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的程序),據此,可判斷應用被雙開。