Android開發學習之路-加固實踐

前言

  • 起因也是想要看一些優秀的程序某些內容是怎麼實現的,所以需要脫殼,但是對於怎麼加固也還是比較感興趣的,加固涉及到的安全的內容很多很多,這裏也只是用個簡單的例子來過把癮,參考了些文章,只是爲了理順思路,也爲之後的脫殼做個準備。
  • 其實加固可以理解爲,一個應用程序,利用了插件化的功能,啓動了另一個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工程的效果。

參考:

發佈了201 篇原創文章 · 獲贊 501 · 訪問量 121萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章