一、前言
最近一直在學習Android加固方面的知識,看了不少論文、技術博客以及一些github上的源代碼,下面總結一下混淆方面的技術,也算是給想學習加固的同學做一些科普,在文中將到的論文、資料以及源碼,我都會給出相應的鏈接,供大家進一步去深入學習。后面我會弄成一個系列的文章,如有一些混淆技術沒講到,還希望大家指點,當做是交流學習。
二、Android混淆技術介紹
2.1 控制流平坦化
2.1.1 概念和思路
控制流平坦化,就是在不改變源代碼的功能前提下,將C或C 代碼中的if、while、for、do等控制語句轉換成switch分支語句。這樣做的好處是可以模糊switch中case代碼塊之間的關系,從而增加分析難度。
這種技術的思想是,首先將要實現(xiàn)平坦化的方法分成多個基本塊(就是case代碼塊)和一個入口塊,為每個基本快編號,并讓這些基本塊都有共同的前驅模塊和后繼模塊。前驅模塊主要是進行基本塊的分發(fā),分發(fā)通過改變switch變量來實現(xiàn)。后繼模塊也可用于更新switch變量的值,并跳轉到switch開始處。詳細的概念可以參考文獻[1]。
其模型如下圖:
下面用代碼來說明,左邊方法是沒有采用控制流平坦化之前的效果,右邊是采用了控制流平坦化的效果。
2.1.2 開源項目
目前用的最多的是OLLVM(Obfuscator-LLVM)的開源混淆方案,很多國內加固廠商都可以看到使用它的身影。它提供了3中保護方式:控制流平坦化、虛假控制流和指令替換。其項目地址如下:
https://github.com/Fuzion24/AndroidObfuscation-NDK
https://github.com/obfuscator-llvm/obfuscator
2.1.3 對抗
目前,對于ollvm的反混淆思路,多采用基于符號執(zhí)行的方法來消除控制流平坦化,這里不做詳細分析,詳細的分析思路,可以參考quarkslab寫的文章[3]。
2.2 花指令
2.2.1 概念和思路
花指令也叫垃圾指令,是指在原始程序中插入一組無用的字節(jié),但又不會改變程序的原始邏輯,程序仍然可以正常運行,然而反匯編工具在反匯編這些字節(jié)時會出錯,由此造成反匯編工具失效,提高破解難度。
花指令的主要思想是,當花指令跟正常指令的開始幾個字節(jié)被反匯編工具識別成一條指令的時候,才可以使得反匯編工具報錯。因此插入的花指令都是一些隨機的但是不完整的指令。但是這些花指令必須要滿足兩個條件:
在程序運行時,花指令是位于一個永遠也不會被執(zhí)行的路徑中。
這些花指令也是合法指令的一部分,只不過它們是不完整指令而已。
也就是說,我們只需要在每個要保護的代碼塊之前插入無條件分支語句和花指令,如下圖所示。無條件分枝是保證程序在運行的時候不會運行到花指令的位置。而反匯編工具在再反匯編時由于會執(zhí)行到花指令,所以就會報錯。
那么目前的反匯編工具所使用的反匯編算法,主要分為兩類:線性掃描算法和遞歸掃描算法。
線性掃描:依次按順序逐個地將每一條指令反匯編成匯編指令
例如下面的指令:
如果是反匯編工具使用線性掃描算法,就會把花指令錯誤識別,導致反匯編出錯。如下:
Dalvik Bytecode Obfuscation on Android[5]這篇文章就利用線性掃描的特點,插入fill-array-data-payload花指令,導致反編譯工具失效。
遞歸掃描:按順序逐個反匯編指令,如果某個地方出現(xiàn)了分支,就會把這個分支地址記錄下來,然后對這些反匯編過程中碰到的分支進行反匯編。
可見,遞歸掃描的算法反匯編能力更強。我們常用的Android逆向工具里面,使用的反匯編算法如下:
2.2.2 開源項目
(1) https://github.com/thuxnder/dalvik-obfuscator
可以結合文章[5]一起看。
(2) https://github.com/faber03/AndroidMalwareEvaluatingTools
這個工具是意大利薩尼奧大學的laswatlab團隊打造出來的惡意程序免殺工具,其實就是使用各種混淆技術,其中也包括花指令插入,在AndroidMalwareEvaluatingTools/transformations sources/JunkInsertion/目錄下。該工具的使用報告可以參考文獻[4]。
(3) https://github.com/strazzere/APKfuscator
APKFuscator通過插入下圖的垃圾指令使得反匯編器出錯。
2.2.3 對抗
檢測出花指令的位置和長度,然后用NOP指令替換即可。
2.3 標識符混淆
2.3.1 概念和思路
標識符混淆就是對源程序中的包名、類名、方法名和變量名進行重命名,用無意義的標識符來替換,使得破解這分析起來更困難。最常用的就是ProGuard開源工具,其混淆后效果如右圖所示。
甚至通過定制混淆字典,可以達到下面這種混淆效果,參考開源項目[7]:
那么這個標識符混淆的原理是怎樣的呢?要了解這個原理,我們得事先對dex文件格式有一定了解,這個資料大家可以在網(wǎng)上找,很多,這里就不詳細說了。
我們知道dex文件中的類名、方法名、變量名其實都對應的一個string_id的字符串索引,如下圖。每一個類對應著class_def_item結構體,其中class_idx就是指向類名的字符串索引。
同樣,每個方法也是對應一個method_id_item的結構體,其中name_idx就是指向方法名的字符串索引。
字段名也一樣,對應著一個field_id_item的結構體,其中name_idx是指向字段名的字符串索引。
也就是說,我們只要修改相應的string_id索引表中的字符串,就可以達到標識符混淆的目的。
具體的實現(xiàn)可以參考文章[10],它還提供了一個dex混淆器的簡單原型:DexConfuse。
2.3.2 開源項目
(1) ProGuard
(2) https://github.com/burningcodes/DexConfuse
DexConfuse是一個簡單的dex混淆器,可以混淆類名、方法名和字段名。
(3) https://github.com/strazzere/APKfuscator
APKFuscator作者通過解析修改Dex文件格式,修改類名,使類名字符個數(shù)超過255個,使得反匯編器報錯。
修改類名使得字符個數(shù)超過255個
2.3.3 對抗
文獻[8]采用的一種反混淆方式就是通過大規(guī)模的學習為混淆的APK,然后總結出一個概率模型,通過這個概率模型來識別混淆后的代碼。其反混淆流程如下圖分為3個步驟:
Step1:生成一個依賴關系圖,每個節(jié)點代表要重命名的元素,每條線代表依賴關系。
Step2:導出一些限制規(guī)則,這些規(guī)則可以保證回復的APK是個正常的APK,且和原APK語義相同。
Step3:根據(jù)概率模型提供的權重,對混淆的元素的原始名稱進行預測和恢復。
作者將論文中的反混淆方法做成了一個在線的反混淆工具提供使用:
http://apk-deguard.com/
2.4 字符串混淆
2.4.1 概念和思路
很多時候,為了避免反匯編后的代碼容易被破解者分析讀懂,往往會源程序中一些比較關鍵的字符串變量進行混淆,使得破解者分析成本提高。這里的字符串混淆有兩種,一種是Java層的字符串混淆,另一種是native層的字符串混淆,也就是so文件中的字符串混淆。上面我們介紹了Proguard免費混淆工具,它可以混淆類名、方法名和變量名,但是不支持字符串混淆,要使用字符串混淆就需要使用DexGuard商業(yè)版混淆器。
實現(xiàn)思路如下:
(1) 編碼混淆
編碼混淆就是先將字符串轉換成16進制的數(shù)組或者Unicode編碼,在使用的時候才恢復成字符串。這樣破解者在逆向后看到的是一串數(shù)字或者亂碼,很難直接分析。
如下代碼所示,其實就是輸出一個Hello World。但是我們硬編碼的時候是保存它的ASCII對應的十六進制,在使用的時候才轉換成字符。在反編譯成smali后,就看不到任何的有意義的字符串了。
Java代碼:
apktool反編譯后的smali代碼:
同樣的在native層的代碼也可以使用類似的方式實現(xiàn)對C或C 中的字符串進行混淆。
(2) 加密處理
加密處理就是實現(xiàn)在本地將字符串加密,然后將密文硬編碼到源程序中,再實現(xiàn)一個解密函數(shù),在引用密文的地方調用解密函數(shù)解密即可。如下圖。
還有一種方式是我們可以修改dex文件。對于Java層的字符串加密,我們可以在dex文件中,找到要加密的字符串在字符串常量表中的位置,然后對它用加密算法加密。然后在自定義Application的attachBaseContext方法中在運行時對密文進行解密,或者可以在native層加密,提高破解難度。
同樣的,我們也可以修改SO文件。SO文件中也存在只讀常量區(qū)”.rodata”,如下圖所示。我們可以根據(jù)section header table來找到”.rodata”的位置和大小,然后實現(xiàn)對只讀常量區(qū)進行加密混淆,在運行的時候再調用相應的解密算法解密即可。
2.4.2 開源項目
https://github.com/ysrc/obfuseSmaliText
obfuseSmaliText是國內同程安全的一個員工實現(xiàn)免費字符串混淆工具,它是通過apktool反編譯安裝包,在smali層對字符串進行混淆,目前采用的是異或 十六進制的方式進行混淆。效果如下圖:
2.4.3 對抗
對于使用了字符串混淆,只能找到響應的解密函數(shù),調用解密函數(shù)去解密就可以恢復明文。
三、結束
這次就寫到這里,后面我還會繼續(xù)補充其他的混淆技術,包括控制流變換、模擬器檢測、反調試、Java代碼native化等。
文章轉自安全客