簡介
基本概念
APK結構
Dex結構
APK打包過程
APK加載過程
Android JNI機制
常用破解手段
靜態破解
反編譯
靜態分析
動態破解
二次打包
本地數據竊取
通信抓取
加固途徑
防靜態破解
防反編譯
防靜態分析
資源文件保護
防二次打包保護
防動態破解
SO文件保護
本地存儲數據保護
ART模式下的加固
常用第三方加固平臺
騰訊雲-樂固
簡介
加固項目
DEX文件加固
資源文件保護
防二次打包保護
防調試器保護
內存防dump保護
高級內存保護
so文件保護
使用客戶
收費情況
加固過程
加固APK反編譯測試
SO
DEX反編譯
安全鍵盤
接入效果
安全數據庫
兼容性測試
加固平臺開發
隨着Android應用破解的愈發猖獗,Android應用安全越來越受到重視, Apk加固作爲一種防止Apk被反編譯的一種強力手段,越來越受到apk開發者的青睞。
說道APK加固,需要和混淆有所區別,由於Java代碼在APK沒有做過混淆處理時,很容易通過反編譯得到源碼的類,變量,函數名,數據結構等信息。Android應用的混淆是通過將Java代碼中的類,變量,函數名用a,b,c等無意義的字母代替。這樣逆向得到的代碼變得不可讀。從一定程度上保護源碼
APK結構
apk文件實際是一個壓縮包,使用解壓軟件即可解壓縮,解壓後的文件一般如下表所示:
路徑 |
作用 |
assets目錄 |
存放一些配置文件,原assets目錄 |
lib目錄 |
armeabi子目錄下存放so庫 |
META-INF目錄 |
簽名信息和證書 |
res目錄 |
資源文件 |
AndroidManifest |
應用配置文件 |
classes.dex (classes2.dex) |
dex執行文件(dalvik vm executes) |
resources.arsc |
記錄資源文件和資源ID之間的映射關係 |
Dex結構
Dex文件結構如下:
其中header是dex文件的頭部,包含了dex文件的檢查簽名,和數據結構偏移地址結構如下
typedef struct DexHeader {
u1 magic[8]; /* includes version number */
u4 checksum; /* adler32 checksum */
u1 signature[kSHA1DigestLen]; /* SHA-1 hash */
u4 fileSize; /* length of entire file */
u4 headerSize; /* offset to start of next section */
u4 endianTag; /*字節序標號*/
u4 linkSize;
u4 linkOff;
u4 mapOff;
u4 stringIdsSize;
u4 stringIdsOff;
u4 typeIdsSize;
u4 typeIdsOff;
u4 protoIdsSize;
u4 protoIdsOff;
u4 fieldIdsSize;
u4 fieldIdsOff;
u4 methodIdsSize;
u4 methodIdsOff;
u4 classDefsSize;
u4 classDefsOff;
u4 dataSize;
u4 dataOff;
} DexHeader;
String table -Class Def table是偏移索引的區域。
Data區域是真正的數據保存位置。
這裏面需要特殊說明的是Header的前幾個量:
- magic value是一個常量,用來識別dex文件,其值爲:"dex\n035\0"
- checksum 文件校驗碼 ,使用 alder32 算法校驗文件除去 maigc ,checksum 外餘下的所有文件區域 ,用於檢查文件錯誤 。
- signature , 使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外餘下的所有文件區域 ,用於唯一識別本文件 。
- fileSize 文件大小
之後說的dex加殼之後重新打包的時候就需要將這幾個值修改了。
APK打包過程
dex文件是供dalvik虛擬機運行的可執行文件,在編譯的時候將java源碼編譯成標準的java字節碼文件,然後通過DX工具轉爲dex文件,最後通過aapt(Android Asset Packaging Tool)工具將dex和其他資源文件打包爲apk文件。
APK加載過程
Android系統zygote進程,通過fork新建一個Dalvik虛擬機實例,來執行apk,Dalvik虛擬機運行流程如下:
和其他Java虛擬機一樣,Android也可以通過JNI機制調用native層代碼,用來達到提高效率,複用代碼,提高安全性等目的。
JNI代碼的實現有兩種方式:
- 傳統的javah工具,生成一組帶簽名的函數,然後再native層實現這些函數。
- 在native的JNI_OnLoad方法中通過RegisterNatives動態註冊native方法。
通過第二種方法,可以實現運行時動態調整本地函數和Java函數直接的映射關係。
通過反編譯工具將dex文件反編譯爲smali代碼和java源碼,然後通過閱讀源碼獲取原應用的實現。
常用工具:
- apktool GOOGLE提供的APK編譯工具,能夠反編譯及回編譯apk
- Dex2jar 將dex文件反編譯爲java源碼,生成的jar包通過jd-gui查看
- Smali2Java 將smali代碼反編譯成java代碼的工具
- smali/backsmali 也是實現dex和smali直接的相互轉化工具
- AXMLPrinter2 批量反編譯xml文件
靜態分析就是直接對反編譯的到的smali代碼和java源碼進行分析。
當然so庫也可以進行反編譯和彙編分析,不過其難度要比java層的分析要困難的多
通過掛在debug調試器到運行的apk,來獲取運行時的數據,
使用的工具比如:zjdroid,IDA,smalidea,Andbug
通過使用IDA,甚至可以在apk加載到內存後,從dump出內存中的dex文件
畢竟不管上層怎麼加固加密,最終加載到內存的dex肯定不是加固的,通過IDA來動態調試libdvm.so中的dvmDexFileOpenPartial函數就獲取內存中的dex內容。而這個dex文件就是完全沒有加殼的源程序dex。
修改APK中的資源文件,修改smali中的代碼,插入自己的惡意代碼或者導流代碼,重新打包後發佈。
竊取APK中的SharePreference和緩存文件,數據庫數據。
通過代理抓包等方式,抓取apk運行過程中的通信數據
防靜態反編譯一般是根據反編譯工具的流程找起漏洞
常用方式其中一種就是在使用花指令,比如利用jd-gui反編譯時的原理,添加一些特殊代碼,讓反編譯工具出錯。
在類中加入如下字段:
private static final char[] wJ = "0123456789abcdef".toCharArray();
public static String imsi = "204046330839890";
public static String p = "0";
public static String keyword = "電話";public static String tranlateKeyword = "%E7%94%B5%E8%AF%9D";
就會讓jd-gui反編譯出錯(最新版jd-gui已修復該問題)
另外一種方式就是對DEX加殼保護,讓反編譯得到的源碼只是一堆無用的代碼,真實的代碼是加密的。
加殼原理:
加固過程中涉及3個對象:
- 需要加密的Apk(源Apk)
- 殼程序Apk(負責解密Apk)
- 加密工具(將源Apk進行加密和殼Dex合併成新的Dex)--加密工具可以用任意語言實現
流程:
- 通過加密工具,對源apk的dex進行加密,得到加密後的無意義內容
- 將加密後的內容append到殼程序apk的後面,並修改殼程序apk的頭文件內容。得到最終apk。
- 殼程序中原有的dex內容是用來對源apk的加密內容進行解密。在運行時,通過DexClassLoader動態加載解密後的源apk,運行源apk內容。
通過上面的流程得到的最終apk由於實際dex已經被加密,反編譯的得到的只是殼apk 的內容。
不過反編譯之後,他人可以得到殼apk中是如何對源apk進行解密的代碼。所以實際上只是加大了反編譯難度而已。爲了進一步增加反編譯難度。一般的殼程序中都會把解密部分放在native代碼中。
代碼混淆--代碼混淆是Android常用的一種代碼加密方式,混淆過後的代碼中類名方法名等都會變成abc等無意義的字符。讓閱讀者沒法直接從名稱中直接分析出源碼目的。增加靜態分析難度。
把資源放在assets目錄下,必要情況對圖片等資源的文件名和內容進行加密。
保護應用在被非法二次打包後不能正常運行:
- Java 代碼中加入簽名校驗
- NDK 中加入簽名校驗
- 利用二次打包工具本身的缺陷阻止打包(manifest 欺騙,圖片修改後綴等等)
- 通過Server認證校驗。
但是實際上不論java層面或者ndk層面的簽名校驗,都可以通過修改smali或者native源碼來繞過。
原理:當檢測到apk被調試器連接時,終止apk的運行。
方式有如下幾種:
1、多進程ptrace反調試---創建子進程。在子進程中獲取父進程pid,並通過ptrace() 方法附着父進程。如果附着失敗,說明進程已經被調試
2、獲取/proc/$pid/status中TracerPid行,能夠顯示調試程序的pid, 可以寫一個方法檢查下這個值, 如果!=0就退出程序。參考Android Native反調試,用JNI實現APK的反調試。
3、檢查代碼執行的間隔時間。
4、檢測手機上的一些硬件信息,判斷是否在模擬器中,如果是模擬器則無法運行。
SO文件保護
對so文件進行優化壓縮、源碼加密隱藏、防止調試器逆向分析
某種so加密結構:
應用加載過程:
本地存儲數據的保護一般是對本地數據內容進行加密,讀取時進行解密,數據庫和SP存儲都有一些開源方案。
數據庫保護:SQLCipher
SharePreference:Secure-Preferences
文件:對稱加密,或者底層對文件操作加一層,對所有文件操作都進行統一加解密
ART模式下的加固
上面提到過的apk加固方案實際是針對dex的,但是在Android4.4之後,Android引入了新的運行模式ART,ART模式相比Dalvik,引入了AOT(ahead of time)編譯,改進了垃圾回收機制,對取樣分析的支持等。在安裝時,ART模式使用dex2oat將apk的dex文件重新編譯成oat文件。oat文件是一種可執行文件(ELF)。這樣ART模式就可以在應用安裝時,將apk轉爲機器碼的可執行文件保存在本地,在每次啓動時加快啓動速度。
Android5.0之後,ART模式就是默認的運行模式。其流程
這就導致了原有的加固方案只能準對Dalvik虛擬機的運行模式,無法再ART模式下使用。
Art模式下的加固,需要用到Hook技術,所謂hook就是通過一些手段改變一個函數的執行邏輯,比如在函數調用前更改一下參數或者在調用後修改返回值,甚至直接返回,不執行原函數。
在ART中類方法的執行要比在Dalvik中要複雜得多,Dalvik如果除去JIT部分,可以理解爲是一個解析執行的虛擬機,而ART則同時包含本地指令執行和解析執行兩種模式,同時所生成的oat文件也包含兩種類型,分別是portable和quick。portable和quick的主要區別是對於方法的加載機制不相同,quick大量使用了Lazy Load機制,因此應用的啓動速度更快,但加載流程更復雜。其中quick是作爲默認選項,這裏涉及的技術分析都是基於quick類型的。
由於ART存在本地指令執行和解析執行兩種模式,因此類方法之間並不是能直接跳轉的,而是通過一些預先定義的bridge函數進行狀態和上下文的切換,如下圖:
Hook方法涉及到ART底層的C代碼和彙編代碼,這裏不列舉分析了,其基本原理是:
修改ArtMethod中的entrypoint,通過中轉處理,讓ART中間執行一段我們的自定義代碼,然後再繼續執行原代碼。而自定義代碼部分可以做一些加密解密等操作。
市面上的第三方加固平臺很多,加固用到的手段基本上已經涵蓋了上文提到的所有手段,並且還有一些其他保密的加固方式,畢竟對於破解者來說,如果知道了其加固或者加密的方法,就更加容易找到相應的破解方法。
市面上比較常見的加固平臺:
- 愛加密
- 梆梆加固
- 360加固保
- 騰訊雲應用樂固
- 阿里聚安全
時間關係,暫時我並沒有針對性的使用各個加固平臺進行一個對比測試,下面放一個其他人做的幾個平臺的對比測試:
|
加固前 |
360 |
阿里 |
騰訊 |
梆梆 |
加固時間 |
|
1'20" |
35" |
1'1" |
2'14" |
apk體積 |
16M |
16M |
15M |
17M |
16.5M |
啓動速度 |
2.52s |
1.77s |
5.02 |
3.48 |
5.57 |
兼容性 |
100% |
99% |
100% |
100% |
99% |
騰訊雲-樂固
應用樂固(Cloud Reinforcement)是移動應用一站式安全服務平臺,涵蓋應用加固、安全評測、渠道監控、安全SDK、適配分析、質量跟蹤等服務。可防止應用被盜版破解、及時發現應用漏洞、監控應用正盜版分發等,有效捍衛應用開發者利益。
DEX文件加固
對DEX文件進行專業加殼加花保護,防止利用調試器對應用進行逆向破解。
資源文件被非法篡改、刪除後,程序將無法正常運行。
應用內任意文件被修改、替換後,都將無法正常運行。
防止使用各類靜、動態調試工具影響應用運行。
內存防dump保護
防止通過動態調試、dump形式獲取應用部分代碼。
可對內存數據進行高強度防護,有效防止內存調試、內存dump等方式竊取源碼。
so文件保護
可對指定so文件進行安全防護,防止被逆向工具破解,暴露核心敏感邏輯。
大衆點評,駕考寶典,餓了麼,四維圖新
目前樂固在線提供的基礎服務免費,定製化需求需要和客服溝通
兩種方式:
1,在線上傳應用加固
上傳目標apk進行加固,加固完成後,只需要將加固後的apk,使用樂固提供的簽名工具,使用原應用的簽名重新進行一次簽名即可。同時支持多渠道打包功能。
2,下載使用PC加固工具
使用加固PC工具能夠一站式的加固應用,本地首先需要配置好簽名信息。然後選擇應用上傳進行加固。
文件上傳成功並加固完成後,會自己根據配置中的簽名,進行簽名並輸出一個加固後的包
加固APK反編譯測試
對網遊開發平臺中下載的網遊sdk demo進行加固測試,測試前後的apk進行解壓後對比可以發現:
加固後的lib庫中,增加了幾個so文件
其中libbugly.so應該騰訊的bugly異常上報庫,用來監控應用的crash,anr等錯誤。
liblegudb.so和libshella.so則
其中liblegudb.so看起來是數據庫加密的native庫
生成的數據庫如下圖所示:(注意,該數據庫和應用本身的數據庫並不相同,原應用數據庫內容依舊是明文的)
進入某一個具體的數據表,看到的數據也是加密後的。
libshella.so則是解dex殼的native庫,使用ida反編譯時,ida報錯無法反編譯。
DEX反編譯
加固後的apk的dex文件相比原dex文件大小略有增加,應該是增加了殼dex導致的。
對加固後的dex進行反編譯後得到的只是殼程序的smali代碼,而沒有原應用dex文件的內容,這也符合上文中的加殼保護的現象。
大部分的代碼進行了混淆操作,對以上的smali文件內容進行初步分析可知:
TxAppEntry:繼承了Application類,其實現對樂固框架的初始化,我們從Manifest也可以看到,application已經改成了TxAppEntry。其attachBaseContext中做了如下事情:
protected void attachBaseContext(Context paramContext)
{
super.attachBaseContext(paramContext);
Log.d("SecShell", "attach start");
if (!b(this)) {//將libshella,libshellb,libshellc三個so庫的路徑初始化給靜態全局變量
return;
}
LeguDB.init(this, null, null);//加載liblegudb.so
d(paramContext);//加載nfix和ufix SO庫,修復本地資源文件和Unity資源文件
this.h = new Handler(getMainLooper());
paramContext = new CrashReport.UserStrategy(this);
paramContext.setAppVersion("2.10.1");
CrashReport.setSdkExtraData(this, "900015015", "2.10.1");
CrashReport.initCrashReport(this, "900015015", false, paramContext);
new Thread(new b(this)).start();
a(this);
paramContext = new IntentFilter(TxReceiver.TX_RECIEVER);
registerReceiver(new TxReceiver(), paramContext);
Log.d("SecShell", "attach end");
}
LeguModuleInitializer:通用的so加載器
LeguDB:數據庫so加載初始化,調用LeguModuleInitializer進行本地庫加載
正常的一個反編譯流程,應該是獲知shell程序是如何從加殼後的dex文件中,獲取原始dex的解密流程。然後寫一個小程序用同樣的方法,解析出原dex。
不同於上文“防反編譯”章節中對dex整體進行簡單整體加密,目前很多的加密手段是對原dex進行解析,並針對每個方法都進行不同內容的插入,從而增加解殼部分的複雜度。
同時也會在解殼代碼的加載過程,增加其複雜度,從靜態分析中很難看到其解殼代碼。
由於並不擅長反編譯相關的手段和知識,對樂固加固後的應用和原應用的對比也僅限於此。
一般來說,通過Android系統自帶的鍵盤可以通過以下幾種方式竊取用戶信息:
- 鍵盤記錄--通過讀取dev/input/event1(需要root) 中的信息獲取到手機屏幕事件,從而獲取輸入的點擊,進而得到輸入內容。
- 屏幕錄像--屏幕錄像同樣需要root權限,然後根據錄像中的按鈕點擊動作來獲取輸入內容
- 屏幕截屏--類似屏幕錄像
- 第三方輸入法漏洞--利用第三方輸入法的漏洞截取輸入
樂固本身提供了一個安全鍵盤的sdk,該sdk使用如下方式來保證輸入安全:
- 使用隨機分佈的數字鍵盤,對底層獲取按鍵事件位置的方式進行防護,即使獲取到點擊位置,也無法獲取按鍵信息
- 使用自定義的鍵盤和EditText輸入空間,避免調用系統的鍵盤,這樣可以避免系統鍵盤或者第三方鍵盤有漏洞的影響
- 防屏幕截屏--這個就是在用戶點擊輸入的時候,不回顯輸入的數據
- 密碼全程動態加密-對每一次用戶輸入和刪除操作都會對密碼動態加密,內存中不會出現明文密碼
- 高強度加密算法
- sdk的自身防護---自研保護+so保護機制,防止鍵盤本身被攻擊
通過接入該sdk,在需要輸入加密內容的部分使用SafeEditText代替EditText,在輸入內容時,SafeEditText並不會顯示你輸入的內容(連閃一下也沒有),規避截屏獲取密碼的可能。下方的鍵盤是自定義的密碼鍵盤。
不過看起來這個鍵盤有個小bug:調起鍵盤界面時,下面的導航欄變成透明,並且和鍵盤下面的部分按鍵衝突。
目前樂固還沒有上線安全數據庫的sdk,並且樂固加固過程本身也是不會對原應用的數據庫進行什麼加密操作。所以這裏不存在替換加固方案後,數據庫升級問題。
單就數據庫來說:
由於sqlite數據庫是明文存儲,root用戶很容易拿到數據庫的db文件,並查看其中保存的數據內容:
下圖是查看樂固加固後的應用的數據庫的數據:
其數據內容和原數據沒有區別,並且都是明文的。
所以本地存儲安全--包括數據庫安全,sharepreerence安全和本地緩存文件安全需要應用本身去實現。這塊可以使用已有的解決方案如:
數據庫保護:SQLCipher---一方面對數據通過用戶提供的key進行加密,另一方面其加密後的數據庫無法打開無法看到其中存儲的已加密數據
SharePreference:Secure-Preferences---其對保存的SP數據對,通過AES進行加密,加密的密鑰是一個生成的僞隨機數。加密後的數據
當然也可以自己選擇加密算法,在寫入本地存儲前進行加密,讀取時進行解密。
從樂固加固升級到 |
覆蓋安裝 |
啓動 |
數據庫讀寫 |
SP讀寫 |
原應用 |
正常 |
正常 |
正常 |
正常 |
360加固 |
正常 |
正常 |
正常 |
正常 |
某開源加固方案 |
正常 |
正常 |
正常 |
正常 |
不過覆蓋安裝後,原有的樂固加固本身創建的bugly的數據依舊存在,因爲升級安裝本身並不會刪除原因的data目錄下文件,只會新增或者升級。
同樣的,從樂固升級到360加固保。則應用的data目錄下,就會存在兩個平臺產生的文件(360並沒有新的數據庫,但是有新的sp文件)
覆蓋安裝後原有的lib目錄會被刪除並覆蓋,不會遺留原有的so文件,但是data下本應用的根目錄還殘留有樂固加固使用的文件(mix.dex,mix.so,tx_shell目錄)。不過由於升級後的應用使用其他加固平臺或者自由加固,這些文件只是會增加包體積而已,並不會從代碼層面產生影響。
上表中的開源加固方案,使用ApkToolPlus集成的
。比第三方加固方式要簡單得多。僅爲測試加固兼容性需要。
綜上可以看出:
各種加固方式基本是針對apk本身做的反編譯,反調試等做的加固。從應用啓動過程來看,覆蓋安裝後,啓動過程就由新的應用所控制,原加固手段中在Application中所作的解密,hook等操作都已無效。只要新覆蓋安裝的apk的啓動過程中需要的文件不存在異常即可。
加固本身並沒有干涉應用本身內部使用的緩存文件,數據庫,sp等(實際上如果不植入加固平臺的sdk的話,加固平臺是沒法干涉這些代碼層的操作的)。
安全領域一句比較有名的觀點是---“未知攻焉知防”。
對Android加固來說,爲了其加固效果,開發者首先需要對常用的破解工具和破解手段比較熟悉。這也是爲何大多數的安全專家本身就具有很高的軟件破解能力的原因。
在對各種破解手段有一定的實踐基礎上,再針對性的對每一種破解手段進行防護,就是一個加固平臺需要完成的任務。下表總結了針對市面上常規的破解方式的幾種加固方式及其開發的難點。
項目 |
方式 |
難點 |
dex文件加固 |
加殼 |
ART模式下的兼容,各Android版本的適配,解殼算法的隱藏 |
|
加花指令 |
花指令的獲取收集 |
資源文件保護 |
資源文件加密 |
|
|
資源文件篡改識別 |
|
反調試 |
防止調試器掛載調試 |
針對各種調試器的反調試實踐 |
|
防止內存dump |
調試器dump時如何觸發應用退出 |
防二次打包 |
java層簽名校驗 |
|
|
native層簽名校驗 |
|
|
利用二次打包工具缺陷 |
|
|
服務器校驗 |
|
so保護 |
針對某so文件進行防護 |
加密後so文件的加載時機 |
本地數據保護 |
數據庫保護 |
數據庫整體的防加載 |
|
緩存文件保護 |
|
|
SP內容保護 |
|