前言
- 起因也是想要看一些優秀的程序某些內容是怎麼實現的,所以需要脫殼,但是對於怎麼加固也還是比較感興趣的,加固涉及到的安全的內容很多很多,這裏也只是用個簡單的例子來過把癮,參考了些文章,只是爲了理順思路,也爲之後的脫殼做個準備。
- 其實加固可以理解爲,一個應用程序,利用了插件化的功能,啓動了另一個apk,而這個apk是經過加密的,殼應用會在加載需要啓動的apk的時候去解密。這樣即使逆向了dex文件,也看不出來原先apk的代碼。
- 爲此準備了源程序,殼工程,加固工具以及一些腳本來實現。
1.ApkTest
首先我們準備下需要被加固的文件apk,這裏我們自己新建工程寫一個例子,ApkTestApplication如下:
這裏就打印下ApkTestApplication的onCreate
override fun onCreate() {
super.onCreate()
Log.i("ApkTestApplication", "Apk Test Application onCreate:$this")
}
主工程的MainActivity如下,主要就是顯示下是ApkTest這個工程,並且可以跳轉到第二個頁面
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("MainActivity", "onCreate")
setContentView(R.layout.activity_test)
tvName.text = showStr()
btNext.setOnClickListener {
gotoSecondActivity()
}
}
private fun showStr(): String {
return "Hello,I am ApkTest!"
}
private fun gotoSecondActivity() {
startActivity(Intent(this, SecondActivity::class.java))
}
}
這個工程沒有什麼好講的了,很普通的一個Android工程。
詳細代碼可以參考:ApkTest
2.ShellApk
既然是加固,那麼就需要有個殼,可以把待加固的應用程序經過加密後放到殼app裏面,這樣,即便被反編譯後還是看不到原應用的dex文件內容。
-
App啓動
防止殼程序運行自己的代碼,所以需要在attachBaseContext中實現加載源程序。
-
解密源程序
新建payload_odex文件,並在其中創建playload.apk文件用於存放源程序的apk,從殼程序的dex文件中讀取源程序的apk文件,解密存放。
-
初始化源程序的ClassLoader,設置殼程序的classLoader爲源程序ClassLoader
獲取主線程對象,創建源程序的DexClassLoader對象,加載apk內的類和本地代碼,然後把當前進程的mClassLoader設置成源程序apk的DexClassLoader。嘗試檢測下源程序的MainActivity存不存在判斷classLoader是否替換完成。
-
反射生成正確的Application對象
讀取Manifest文件中定義的源程序的application類,設置源程序的Application,並把當前進程的application設置爲null,刪除老的application,使用源程序的application。反射設置ActivityThread中的Application信息
-
設置provider相關的配置
-
調用源程序application的onCreate方法
-
源程序正常運行
AndroidManifest.xml代碼如下:
<application
android:name="com.jared.shellapk.ShellApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.jared.apktest.ApkTestApplication"/>
<activity android:name="com.jared.apktest.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name="com.jared.apktest.SecondActivity" />
</application>
這裏定義了APPLICATION_CLASS_NAME爲源程序的application類,埋坑了MainActivty和SecondActivity用於ApkTest程序啓動後的調用。
詳細代碼可以參考: ShellApk
3.DexPackTool
加固工具其實就是在殼程序的dex文件中加入了源程序的apk文件,這裏就是在ShellApk的class.dex文件中加入了ApkTest.apk文件。
首先我們瞭解下Dex文件的結構:
- Dex文件
Dex文件結構 |
---|
Dex文件頭部 |
字符串索引區 |
類型索引區 |
方法索引區 |
原型索引區 |
類定義區 |
數據區 |
鏈接數據區 |
我們如果要修改dex文件,就需要更新Dex文件頭部信息,其中頭部信息包括了:
Dex文件頭部 | |
---|---|
magic[8] | dex的文件標識,一般稱爲魔術 |
checksum | dex文件的校驗和,通過它可以判斷dex文件是否被損壞或者被篡改 |
signature[kSHA1DigestLen] | 檢驗dex文件,其實就是把整個dex文件用SHA-1簽名得到的一個值 |
fileSize | 整個文件的大小,佔用4個字節 |
headerSize | 頭結構的大小,佔用4個字節 |
- 加固後的文件結構
加固後Dex文件 | |
---|---|
ShellApk的Dex文件 | 修改了文件頭的ShellApk的classes.dex |
加密的ApkTest的Apk | 經過了異或加密後的ApkTest的apk文件 |
加密的ApkTest的大小 | 爲了解密讀取對應的大小,需要知道加密後的ApkTest的文件大小 |
- 工具代碼如下
File payloadSrcFile = new File("files/SourceApk.apk"); // 需要加殼的源程序
System.out.println("apk size:"+payloadSrcFile.length( ));
File packDexFile = new File("files/SourceApk.dex"); // 殼程序dex
byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile)); // 以二進制形式讀出源apk,並進行加密處理
byte[] packDexArray = readFileBytes(packDexFile); // 以二進制形式讀出dex
int payloadLen = payloadArray.length;
int packDexLen = packDexArray.length;
int totalLen = payloadLen + packDexLen + 4; // 多出4字節是存放長度的
byte[] newdex = new byte[totalLen]; // 申請了新的長度
// 添加解殼代碼
System.arraycopy(packDexArray, 0, newdex, 0, packDexLen); // 先拷貝dex內容
// 添加加密後的解殼數據
System.arraycopy(payloadArray, 0, newdex, packDexLen, payloadLen); // 再在dex內容後面拷貝apk的內容
// 添加解殼數據長度
System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4); // 最後4字節爲長度
// 修改DEX file size文件頭
fixFileSizeHeader(newdex);
// 修改DEX SHA1 文件頭
fixSHA1Header(newdex);
// 修改DEX CheckSum文件頭
fixCheckSumHeader(newdex);
String str = "files/classes.dex"; // 創建一個新文件
File file = new File(str);
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream localFileOutputStream = new FileOutputStream(str);
localFileOutputStream.write(newdex); // 將新計算出的二進制dex數據寫入文件
localFileOutputStream.flush();
localFileOutputStream.close();
這裏,我們運行打包了一個DexPackTool.jar文件,用以後續的加固工作。
詳細代碼可以參考: DexPackTool
4.腳本執行加固
以上我們可以生成三個文件
- 源程序ApkTest工程的,命名爲SourceApk.apk,
- 殼程序ShellApk工程,名爲app-debug.apk
- 加固工具, 名爲DexPackTool.jar
爲了方便我們寫一個腳本來實現加固
rm app-test.apk
python getDex.py
java -jar DexPackTool.jar
cp files/classes.dex .
# exchange dex
aapt r app-debug.apk classes.dex
aapt a app-debug.apk classes.dex
cp app-debug.apk app-test.apk
#sign
./resign.sh
adb install app-test.apk
- 拷貝SourceApk.apk到files文件夾下
- 通過getDex.py從app-debug.apk中獲取classes.dex文件,拷貝到files文件夾下
- DexPackTool把SourceApk.apk加密後加入到classes.dex文件末尾,重新生成新的classes.dex文件
- 通過aapt命令刪除app-debug.apk的classes.dex文件,並加入加固後新生成的classes.dex文件
- 重新簽名apk,安裝
詳細代碼可以參考: ShellTools
5.驗證
我們運行ShellApk程序可以看下加入的log信息:
12-25 14:30:22.062 19460-19460/? D/ResourcesManager: creating new AssetManager and set to /data/app/com.jared.shellapk-1/base.apk
12-25 14:30:22.082 19460-19460/? I/ShellApplication: apk size:0
12-25 14:30:22.593 19460-19460/? D/ShellApplication: apk size: 6103481
12-25 14:30:22.593 19460-19460/? I/System.out: 22b245
12-25 14:30:26.887 19460-19460/com.jared.shellapk I/ShellApplication: classloader:dalvik.system.DexClassLoader[DexPathList[[zip file "/data/data/com.jared.shellapk/app_payload_odex/payload.apk"],nativeLibraryDirectories=[/data/data/com.jared.shellapk/app_payload_lib, /vendor/lib, /system/lib]]]
12-25 14:30:26.887 19460-19460/com.jared.shellapk I/ShellApplication: actObj:class com.jared.apktest.MainActivity
12-25 14:30:26.887 19460-19460/com.jared.shellapk I/ShellApplication: onCreate
12-25 14:30:26.897 19460-19460/com.jared.shellapk I/ShellApplication: app:com.jared.apktest.ApkTestApplication@3839a4b9
12-25 14:30:26.907 19460-19460/com.jared.shellapk I/ApkTestApplication: Apk Test Application onCreate:com.jared.apktest.ApkTestApplication@3839a4b9
12-25 14:30:26.917 19460-19460/com.jared.shellapk D/MainActivity: onCreate
運行的是ShellApk,但是實際上最後都是運行ApkTest工程的效果。
參考: