Author:楊空明 Date:2018-8-17
一、前言
Android開發(fā)者常常面臨的一個(gè)問題就是防破解、 防二次打包?,F(xiàn)如今安全問題越來越重要,越來越多的Android開發(fā)者也開始尋求安全的保護(hù)方案。請看一下下面的幾張圖片:
1.1

1.2

1.3
二、什么是加殼?
移動(dòng)平臺攻防技術(shù)的發(fā)展基本是沿著PC端發(fā)展軌跡在進(jìn)行,從windows平臺的加殼脫殼反調(diào)試到Andriod的平臺apk加固,反調(diào)試代碼混淆等。
加殼是在二進(jìn)制的程序中植入一段代碼,在運(yùn)行的時(shí)候優(yōu)先取得程序的控制權(quán),做一些額外的工作。大多數(shù)病毒就是基于此原理。PC EXE文件加殼的過程如下:

三、加殼作用和分類
作用:
加殼的程序可以有效阻止對程序的反匯編分析,以達(dá)到它不可告人的目的。這種技術(shù)也常用來保護(hù)軟件版權(quán),防止被軟件破解。
分類:
從App的加固技術(shù)來看:主流分為dex加密和so加密,目前來看保護(hù)dex文件更為重要,應(yīng)為dex反編譯后的java代碼可讀性更強(qiáng)。
四、Android Dex文件加殼原理
4.1.APK文件結(jié)構(gòu)

每個(gè)文件及文件夾的作用如下表所示。
| 文件或目錄 | 說明 |
|---|---|
| assets文件夾 | 存放資源文件的目錄 |
| lib文件夾 | 存放ndk編譯出來的so文件 |
| META-INF文件夾 | 1.該目錄下存放的是簽名信息,用來保證apk包的完整性和系統(tǒng)的安全性 2.CERT.RS 保存著該應(yīng)用程序的證書和授權(quán)信息 3.CERT.SF 保存著SHA-1信息資源列表 4.MANIFEST.MF 清單信息 |
| res文件夾 | 存放資源文件的目錄 |
| AndroidManifest.xml | 一個(gè)清單文件,它描述了應(yīng)用的名字、版本、權(quán)限、注冊的服務(wù)等信息。 |
| classes.dex | java源碼編譯經(jīng)過編譯后生成的dalvik字節(jié)碼文件,主要在Dalvik虛擬機(jī)上運(yùn)行的主要代碼部分 |
| resources.arsc | 編譯后的二進(jìn)制資源文件。 |
4.2DEX文件格式
4.2.1什么是DEX文件?
他是Android系統(tǒng)的可執(zhí)行文件,包含應(yīng)用程序的全部操作指令以及運(yùn)行時(shí)數(shù)據(jù)
由于dalvik是一種針對嵌入式設(shè)備而特殊設(shè)計(jì)的java虛擬機(jī),所以dex文件與標(biāo)準(zhǔn)的class文件在結(jié)構(gòu)設(shè)計(jì)上有著本質(zhì)的區(qū)別
當(dāng)java程序編譯成class后,還需要使用dx工具將所有的class文件整合到一個(gè)dex文件,目的是其中各個(gè)類能夠共享數(shù)據(jù),在一定程度上降低了冗余,同時(shí)也是文件結(jié)構(gòu)更加經(jīng)湊,實(shí)驗(yàn)表明,dex文件是傳統(tǒng)jar文件大小的50%左右

4.2.2dex文件結(jié)構(gòu)
Dex文件整體結(jié)構(gòu)如下:

Dex文件整體結(jié)構(gòu)說明:
| 數(shù)據(jù)名稱 | 解釋 |
|---|---|
| dex_header | dex文件頭部記錄整個(gè)dex文件的相關(guān)屬性 |
| string_table | 字符串?dāng)?shù)據(jù)索引,記錄了每個(gè)字符串在數(shù)據(jù)區(qū)的偏移量 |
| type_table | 類似數(shù)據(jù)索引,記錄了每個(gè)類型的字符串索引 |
| proto_table | 原型數(shù)據(jù)索引,記錄了方法聲明的字符串,返回類型字符串,參數(shù)列表 |
| field_table | 字段數(shù)據(jù)索引,記錄了所屬類,類型以及方法名 |
| method_table | 類方法索引,記錄方法所屬類名,方法聲明以及方法名等信息 |
| class_def | 類定義數(shù)據(jù)索引,記錄指定類各類信息,包括接口,超類,類數(shù)據(jù)偏移量 |
| data_section | 數(shù)據(jù)區(qū),保存了各個(gè)類的真是數(shù)據(jù) |
下面是DEX文件目錄:

這里面,有3個(gè)成員我們需要特別關(guān)注,這在后面加固里會(huì)用到,它們分別是checksum、signature和fileSize。
checksum字段
checksum是校驗(yàn)碼字段,占4bytes,主要用來檢查從該字段(不包含checksum字段,也就是從12bytes開始算起)開始到文件末尾,這段數(shù)據(jù)是否完整,也就是完整性校驗(yàn)。它使用alder32算法校驗(yàn)。
signature字段
signature是SHA-1簽名字段,占20bytes,作用跟checksum一樣,也是做完整性校驗(yàn)。之所以有兩個(gè)完整性校驗(yàn)字段,是由于先使用checksum字段校驗(yàn)可以先快速檢查出錯(cuò)的dex文件,然后才使用第二個(gè)計(jì)算量更大的校驗(yàn)碼進(jìn)行計(jì)算檢查。
fileSize字段
占4bytes,保存classes.dex文件總長度。
這3個(gè)字段當(dāng)我們修改dex文件的時(shí)候,這3個(gè)字段的值是需要更新的,否則在加載到Dalvik虛擬機(jī)的時(shí)候會(huì)報(bào)錯(cuò)。
為什么說我們只需要關(guān)注這三個(gè)字段呢?
因?yàn)槲覀冃枰獙⒁粋€(gè)文件(加密之后的源Apk)寫入到Dex中,那么我們肯定需要修改文件校驗(yàn)碼(checksum).因?yàn)樗菣z查文件是否有錯(cuò)誤。那么signature也是一樣,也是唯一識別文件的算法。還有就是需要修改dex文件的大小。
不過這里還需要一個(gè)操作,就是標(biāo)注一下我們加密的Apk的大小,因?yàn)槲覀冊诿摎さ臅r(shí)候,需要知道Apk的大小,才能正確的得到Apk。那么這個(gè)值放到哪呢?這個(gè)值直接放到文件的末尾就可以了。
所以總結(jié)一下我們需要做:修改Dex的三個(gè)文件頭,將源Apk的大小追加到殼dex的末尾就可以了。
我們修改之后得到新的Dex文件樣式如下:
[圖片上傳失敗...(image-831de6-1534489556791)]
4.3APK打包流程

上圖中涉及到的工具及其作用如下:
| 名稱 | 功能介紹 |
|---|---|
| aapt | 打包資源文件,包括res和assets文件夾下的資源、AndroidManifest.xml文件、Android基礎(chǔ)類庫 |
| aidl | 將.aidl接口文件轉(zhuǎn)換成.java文件 |
| javaComiler | 編譯java文件,生成.class字節(jié)碼文件 |
| dex | 將所有的第三方libraries和.class文件轉(zhuǎn)換成Dalvik虛擬機(jī)支持的.dex文件 |
| apkbuilder | 打包生成apk文件,但未簽名 |
| jarsigner | 對未簽名的apk文件進(jìn)行簽名 |
| zipalign | 對簽名后的apk文件進(jìn)行對其處理 |
4.4加固原理

Dex文件整體加固原理如下:

在該過程中涉及到三個(gè)對象,分別如下:
l 源程序
源程序也就是我們的要加固的對象,這里面主要修改的是原apk文件中的classes.dex文件和AndroidManifest.xml文件。
l 殼程序
殼程序主要用于解密經(jīng)過加密了的dex文件,并加載解密后的原dex文件,并正常啟動(dòng)原程序。
l 加密程序
加密程序主要是對原dex文件進(jìn)行加密,加密算法可以是簡單的異或操作、反轉(zhuǎn)、rc4、des、rsa等加密算法。
該加固過程可以分為如下4個(gè)階段:
(1) 加密階段
(2)合成新的dex文件
(3)修改原apk文件并重打包簽名
(4)運(yùn)行殼程序加載原dex文件
4.4.1 加密階段
加密階段主要是講把原apk文件中提取出來的classes.dex文件通過加密程序進(jìn)行加密。加密的時(shí)候如果使用des對稱加密算法,則需要注意處理好密鑰的問題。同樣的,如果采用非對稱加密,也同樣存在公鑰保存的問題。

4.4.2 合成新的dex文件
這一階段主要是講上一步生成的加密的dex文件和我們的殼dex文件合并,將加密的dex文件追加在殼dex文件后面,并在文件末尾追加加密dex文件的大小數(shù)值

在殼程序里面,有個(gè)重要的類:ProxyApplication類,該類繼承Application類,也是應(yīng)用程序最先運(yùn)行的類。所以,我們就是在這個(gè)類里面,在原程序運(yùn)行之前,進(jìn)行一些解密dex文件和加載原dex文件的操作。
4.4.3 修改原apk文件并重打包簽名
在這一階段,我們首先將apk解壓,會(huì)看到如下圖的6個(gè)文件和目錄。其中,我們需要修改的只有2個(gè)文件,分別是classes.dex和AndroidManifest.xml文件,其他文件和文件加都不需要改動(dòng)。
首先,我們把解壓后apk目錄下原來的classes.dex文件替換成我們在0x02上一步合成的新的classes.dex文件。然后,由于我們程序運(yùn)行的時(shí)候,首先加載的其實(shí)是殼程序里的ProxyApplication類。所以,我們需要修改AndroidManifest.xml文件,指定application為ProxyApplication,這樣才能正常找到識別ProxyApplication類并運(yùn)行殼程序。

4.4.4運(yùn)行殼程序加載原dex文件
Dalvik虛擬機(jī)會(huì)加載我們經(jīng)過修改的新的classes.dex文件,并最先運(yùn)行ProxyApplication類。在這個(gè)類里面,有2個(gè)關(guān)鍵的方法:attachBaseContext和onCreate方法。ProxyApplication顯示運(yùn)行attachBaseContext再運(yùn)行onCreate方法。
在attachBaseContext方法里,主要做兩個(gè)工作:
讀取classes.dex文件末尾記錄加密dex文件大小的數(shù)值,則加密dex文件在新classes.dex文件中的位置為:len(新classes.dex文件) – len(加密dex文件大小)。然后將加密的dex文件讀取出來,加密并保存到資源目錄下
然后使用自定義的DexClassLoader加載解密后的原dex文件
在onCreate方法中,主要做兩個(gè)工作:
通過反射修改ActivityThread類,并將Application指向原dex文件中的Application
創(chuàng)建原Application對象,并調(diào)用原Application的onCreate方法啟動(dòng)原程序

五、加殼代碼實(shí)現(xiàn)
5.1、加殼程序項(xiàng)目:

5.2、核心代碼

六、常見加固平臺
梆梆加固,愛加密,360加固,騰訊加固
市面上常見的加固工具加固之后,他會(huì)把你的dex,so 加密存在apk中,然后運(yùn)行過程會(huì)先運(yùn)行殼的代碼,殼的代碼再把原來的這個(gè)dex、so 解出來加載,不同的廠商有自己的方案,略有差距,但目前多數(shù)都是這個(gè)思路.
七、App加固的利弊
正面:
1.保護(hù)自己核心代碼算法,提高破解/盜版/二次打包的難度
2.緩解代碼注入/動(dòng)態(tài)調(diào)試/內(nèi)存注入攻擊
負(fù)面:
1.影響兼容性
2.影響程序運(yùn)行效率.
3.部分流氓、病毒也會(huì)使用加殼技術(shù)來保護(hù)自己
4.部分應(yīng)用市場會(huì)拒絕加殼后的應(yīng)用上架
八、App安全未來展望

一款app的流水線,從開發(fā)到內(nèi)測到平臺到消費(fèi)者再到破解者再到平臺再到消費(fèi)者,所以每一個(gè)環(huán)節(jié)都不可輕視?。?!
九、App安全總結(jié)
| 風(fēng)險(xiǎn)名稱 | 風(fēng)險(xiǎn) | 解決方案 |
|---|---|---|
| 1.App防止反編譯 | 被反編譯的暴露客戶端邏輯,加密算法,密鑰,等等 | 加固 |
| 2.java層代碼源代碼反編譯風(fēng)險(xiǎn) | 被反編譯的暴露客戶端邏輯,加密算法,密鑰,等等 | 加固 ,混淆 |
| 3.so文件破解風(fēng)險(xiǎn) | 導(dǎo)致核心代碼泄漏。 | so文件加固 |
| 4.篡改和二次打包風(fēng)險(xiǎn) | 修改文件資源等,二次打包的添加病毒,廣告,或者竊取支付密碼,攔截短信等 | 資源文件混淆和校驗(yàn)簽名的hash值 |
| 5.資源文件泄露風(fēng)險(xiǎn) | 獲取圖片,js文件等文件,通過植入病毒,釣魚頁面獲取用戶敏感信息 | 資源混淆,加固等等 |
| 6.應(yīng)用簽名未交驗(yàn)風(fēng)險(xiǎn) | 反編譯或者二次打包,添加病毒代碼,惡意代碼,上傳盜版App | 對App進(jìn)行簽名證書校驗(yàn) |
| 7.代碼為混淆風(fēng)險(xiǎn) | 業(yè)務(wù)邏輯暴露,加密算法,賬號信息等等。 | 混淆(中文混淆) |
| 8.webview明文存儲(chǔ)密碼風(fēng)險(xiǎn) | 用戶使用webview默認(rèn)存儲(chǔ)密碼到databases/webview.db root的手機(jī)可以產(chǎn)看webview數(shù)據(jù)庫,獲取用戶敏感信息 | 關(guān)閉wenview存儲(chǔ)密碼功能 |
| 9.明文數(shù)字證書風(fēng)險(xiǎn) | APK使用的數(shù)字證書用來校驗(yàn)服務(wù)器的合法性,保證數(shù)據(jù)的保密性和完成性 明文存儲(chǔ)的證書被篡改造成數(shù)據(jù)被獲取等 | 客戶端校驗(yàn)服務(wù)器域名和數(shù)字證書等 |
| 10.調(diào)試日志函數(shù)調(diào)用風(fēng)險(xiǎn) | 日志信息里面含有用戶敏感信息等 | 關(guān)閉調(diào)試日志函數(shù),刪除打印的日志信息 |
| 11.AES/DES加密方法不安全使用風(fēng)險(xiǎn) | 在使用AES/DES加密使用了ECB或者OFB工作模式,加密數(shù)據(jù)被選擇明文攻擊破解等 | 使用CBC和CFB工作模式等 |
| 12.RSA加密算法不安全風(fēng)險(xiǎn) | 密數(shù)據(jù)被選擇明文攻擊破解和中間人攻擊等導(dǎo)致用戶敏感信息泄露 | 密碼不要太短,使用正確的工作模式 |
| 13.密鑰硬編碼風(fēng)險(xiǎn) | 用戶使用加密算法的密鑰設(shè)置成一個(gè)固定值導(dǎo)致密鑰泄漏 | 動(dòng)態(tài)生成加密密鑰或者將密鑰進(jìn)程分段存儲(chǔ)等 |
| 14.動(dòng)態(tài)調(diào)試攻擊風(fēng)險(xiǎn) | 攻擊者使用GDB,IDA調(diào)試追蹤目標(biāo)程序,獲取用戶敏感信息等 | 在so文件里面實(shí)現(xiàn)對調(diào)試進(jìn)程的監(jiān)聽 |
| 15.應(yīng)用數(shù)據(jù)任意備份風(fēng)險(xiǎn) | AndroidMainfest中allowBackup=true 攻擊者可以使用adb命令對APP應(yīng)用數(shù)據(jù)進(jìn)行備份造成用戶數(shù)據(jù)泄露 | allowBackup=false |
| 16.全局可讀寫內(nèi)部文件風(fēng)險(xiǎn)。 | 實(shí)現(xiàn)不同軟件之間數(shù)據(jù)共享,設(shè)置內(nèi)部文件全局可讀寫造成其他應(yīng)用也可以讀取或者修改文件等 | (1).使用MODE_PRIVATE模式創(chuàng)建內(nèi)部存儲(chǔ)文件(2).加密存儲(chǔ)敏感數(shù)據(jù)3.避免在文件中存儲(chǔ)明文和敏感信息 |
| 17.SharedPrefs全局可讀寫內(nèi)部文件風(fēng)險(xiǎn)。 | 被其他應(yīng)用讀取或者修改文件等 | 使用正確的權(quán)限 |
| 18.Internal Storage數(shù)據(jù)全局可讀寫風(fēng)險(xiǎn) | 當(dāng)設(shè)置MODE_WORLD_READBLE或者設(shè)置android:sharedUserId導(dǎo)致敏感信息被其他應(yīng)用程序讀取等 | 設(shè)置正確的模式等 |
| 19.getDir數(shù)據(jù)全局可讀寫風(fēng)險(xiǎn) | 當(dāng)設(shè)置MODE_WORLD_READBLE或者設(shè)置android:sharedUserId導(dǎo)致敏感信息被其他應(yīng)用程序讀取等 | 設(shè)置正確的模式等 |
| 20.java層動(dòng)態(tài)調(diào)試風(fēng)險(xiǎn) | AndroidManifest中調(diào)試的標(biāo)記可以使用jdb進(jìn)行調(diào)試,竊取用戶敏感信息。 | android:debuggable=“false” |
| 21.內(nèi)網(wǎng)測試信息殘留風(fēng)險(xiǎn) | 通過測試的Url,測試賬號等對正式服務(wù)器進(jìn)行攻擊等 | 講測試內(nèi)網(wǎng)的日志清除,或者測試服務(wù)器和生產(chǎn)服務(wù)器不要使用同一個(gè) |
| 22.隨機(jī)數(shù)不安全使用風(fēng)險(xiǎn) | 在使用SecureRandom類來生成隨機(jī)數(shù),其實(shí)并不是隨機(jī),導(dǎo)致使用的隨機(jī)數(shù)和加密算法被破解。 | (1)不使用setSeed方法(2)使用/dev/urandom或者/dev/random來初始化偽隨機(jī)數(shù)生成器 |
| 23.Http傳輸數(shù)據(jù)風(fēng)險(xiǎn) | 未加密的數(shù)據(jù)被第三方獲取,造成數(shù)據(jù)泄露 | 使用Hpps |
| 24.Htpps未校驗(yàn)服務(wù)器證書風(fēng)險(xiǎn),Https未校驗(yàn)主機(jī)名風(fēng)險(xiǎn),Https允許任意主機(jī)名風(fēng)險(xiǎn) | 客戶端沒有對服務(wù)器進(jìn)行身份完整性校驗(yàn),造成中間人攻擊 | (1).在X509TrustManager中的checkServerTrusted方法對服務(wù)器進(jìn)行校驗(yàn)(2).判斷證書是否過期(3).使用HostnameVerifier類檢查證書中的主機(jī)名與使用證書的主機(jī)名是否一致 |
| 25.webview繞過證書校驗(yàn)風(fēng)險(xiǎn) | webview使用https協(xié)議加密的url沒有校驗(yàn)服務(wù)器導(dǎo)致中間人攻擊 | 校驗(yàn)服務(wù)器證書時(shí)候正確 |
| 26.界面劫持風(fēng)險(xiǎn) | 用戶輸入密碼的時(shí)候被一個(gè)假冒的頁面遮擋獲取用戶信息等 | (1).使用第三方專業(yè)防界面劫持SDK(2).校驗(yàn)當(dāng)前是否是自己的頁面 |
| 27.輸入監(jiān)聽風(fēng)險(xiǎn) | 用戶輸入的信息被監(jiān)聽或者按鍵位置被監(jiān)聽造成用戶信息泄露等 | 自定義鍵盤 |
| 28.截屏攻擊風(fēng)險(xiǎn) | 對APP運(yùn)行中的界面進(jìn)行截圖或者錄制來獲取用戶信息 | 添加屬性getWindow().setFlags(FLAG_SECURE)不讓用戶截圖和錄屏 |
| 29.動(dòng)態(tài)注冊Receiver風(fēng)險(xiǎn) | 當(dāng)動(dòng)態(tài)注冊Receiver默認(rèn)生命周期是可以導(dǎo)出的可以被任意應(yīng)用訪問 | 使用帶權(quán)限檢驗(yàn)的registerReceiver API進(jìn)行動(dòng)態(tài)廣播的注冊 |
| 30.Content Provider數(shù)據(jù)泄露風(fēng)險(xiǎn) | 權(quán)限設(shè)置不當(dāng)導(dǎo)致用戶信息 | 正確的使用權(quán)限 |
| 31.Service ,Activity,Broadcast,content provider組件導(dǎo)出風(fēng)險(xiǎn) | Activity被第三方應(yīng)用訪問導(dǎo)致被任意應(yīng)用惡意調(diào)用 | 自定義權(quán)限 |
| 32.PendingIntent錯(cuò)誤使用Intent風(fēng)險(xiǎn) | 使用PendingIntent的時(shí)候,如果使用了一個(gè)空Intent,會(huì)導(dǎo)致惡意用戶劫持修改Intent的內(nèi)容 | 禁止使用一個(gè)空Intent去構(gòu)造PendingIntent |
| 33.Intent組件隱式調(diào)用風(fēng)險(xiǎn) | 使用隱式Intent沒有對接收端進(jìn)行限制導(dǎo)致敏感信息被劫持 | 1.對接收端進(jìn)行限制 2.建議使用顯示調(diào)用方式發(fā)送Intent |
| 34.Intent Scheme URL攻擊風(fēng)險(xiǎn) | webview惡意調(diào)用App | 對Intent做安全限制 |
| 35.Fragment注入攻擊風(fēng)險(xiǎn) | 出的PreferenceActivity的子類中,沒有加入isValidFragment方法,進(jìn)行fragment名的合法性校驗(yàn),攻擊者可能會(huì)繞過限制,訪問未授權(quán)的界面 | (1).如果應(yīng)用的Activity組件不必要導(dǎo)出,或者組件配置了intent filter標(biāo)簽,建議顯示設(shè)置組件的“android:exported”屬性為false(2).重寫isValidFragment方法,驗(yàn)證fragment來源的正確性 |
| 36.webview遠(yuǎn)程代碼執(zhí)行風(fēng)險(xiǎn) | 風(fēng)險(xiǎn):WebView.addJavascriptInterface方法注冊可供JavaScript調(diào)用的Java對象,通過反射調(diào)用其他java類等 | 建議不使用addJavascriptInterface接口,對于Android API Level為17或者以上的Android系統(tǒng),Google規(guī)定允許被調(diào)用的函數(shù),必須在Java的遠(yuǎn)程方法上面聲明一個(gè)@JavascriptInterface注解 |
| 37.zip文件解壓目錄遍歷風(fēng)險(xiǎn) | Java代碼在解壓ZIP文件時(shí),會(huì)使用到ZipEntry類的getName()方法,如果ZIP文件中包含“../”的字符串,該方法返回值里面原樣返回,如果沒有過濾掉getName()返回值中的“../”字符串,繼續(xù)解壓縮操作,就會(huì)在其他目錄中創(chuàng)建解壓的文件 | (1). 對重要的ZIP壓縮包文件進(jìn)行數(shù)字簽名校驗(yàn),校驗(yàn)通過才進(jìn)行解壓。 (2). 檢查Zip壓縮包中使用ZipEntry.getName()獲取的文件名中是否包含”../”或者”..”,檢查”../”的時(shí)候不必進(jìn)行URI Decode(以防通過URI編碼”..%2F”來進(jìn)行繞過),測試發(fā)現(xiàn)ZipEntry.getName()對于Zip包中有“..%2F”的文件路徑不會(huì)進(jìn)行處理。 |
| 38.Root設(shè)備運(yùn)行風(fēng)險(xiǎn) | 已經(jīng)root的手機(jī)通過獲取應(yīng)用的敏感信息等 | 檢測是否是root的手機(jī)禁止應(yīng)用啟動(dòng) |
| 39.模擬器運(yùn)行風(fēng)險(xiǎn) | 刷單,模擬虛擬位置等 | 禁止在虛擬器上運(yùn)行 |
| 40.從sdcard加載Dex和so風(fēng)險(xiǎn) | 未對Dex和So文件進(jìn)行安全,完整性及校驗(yàn),導(dǎo)致被替換,造成用戶敏感信息泄露 | (1).放在APP的私有目錄 (2).對文件進(jìn)行完成性校驗(yàn)。 |