天天看點

Android Apk加強的初步實作思路(dex整體加強)

一、前 言

Android Apk加強的發展已經有一段時間了,相對來說本篇部落格要記錄的Android加殼的實作思路是4年的東西了,已經被老鳥玩爛了,Android加強的安全廠商也不會采用這麼粗犷的方式來進行Android Apk的加強處理。早期Android加強聚焦的兩個點主要是在DexClassLoader和Android類加載這兩條代碼執行流程上去下功夫,後期Android加強會在Android動态庫so的加強處理和Android應用程式的自定義smali位元組碼虛拟機上下功夫來實作與逆向分析的對抗。本篇部落格記錄主要學習參考的博文是​​Android APK加殼技術方案【1】​​​、​​Android APK加殼技術方案【2】​​​、​​APK加殼【1】初步方案實作詳解​​ 這3篇。Android加強的關注點主要集中在兩個方面:dex檔案的隐藏和dex檔案的加載運作,針對Android應用dex檔案的加強也有兩種方案即,dex檔案的整體加強方案、dex檔案的部分加強方案。本篇部落格提到的Android Apk的加強屬于dex整理加強的方案。

源程式:需要被加殼保護的Android應用程式的代碼,可以是Android應用的apk也可以是Android應用的dex檔案和so庫檔案。

加密工具:對源程式進行加密的工具即對Android應用的apk或者Android應用的dex檔案和so庫檔案進行加密的工具,為解殼程式提供源程式的解殼資料,語言和平台不限,加密處理方式根據加殼的需求來處理。

解殼程式:解密解殼資料并通過DexClassLoader動态加載和運作源程式的代碼,實作Android解殼程式與Android源程式的無縫替換,Android源程式apk得到執行,Android解殼程式又被稱作外殼程式。

二、Android 源程式的加殼思路

1.解殼資料位于解殼程式檔案尾部

先将Android源程式的Apk使用加密工具進行加密處理得到需要解密的解殼資料,然後将該解殼資料放到Android解殼程式的dex檔案的後面進行隐藏。

加殼源程式工作流程:

1. 加密源程式apk檔案為解殼資料。

2. 把解殼資料寫入解殼程式dex檔案末尾,并在檔案尾部添加解殼資料的長度大小。

3. 修改解殼程式dex頭中checksum、signature 和file_size頭資訊。

4. 修改源程式AndroidMainfest.xml檔案并覆寫解殼程式AndroidMainfest.xml檔案。

源程式Apk加殼處理後,解殼程式dex檔案的示意圖如下:

Android Apk加強的初步實作思路(dex整體加強)

解殼程式工作流程:

1.讀取解殼程式dex檔案末尾的資料,擷取解殼資料長度。

2.從解殼程式dex檔案中讀取解殼資料,解密解殼資料,以檔案形式儲存解密資料到xx.apk檔案。

3.擷取到釋放出的源程式xx.apk,解殼程式apk應用通過DexClassLoader動态加載xx.apk進行運作。

2.解殼資料位于解殼程式檔案頭後面

先将Android源程式的Apk使用加密工具進行加密處理得到需要解密的解殼資料,然後将該解殼資料放到Android解殼程式的dex檔案的檔案頭的後面。

源程式Apk加殼處理後,解殼程式dex檔案的示意圖如下:

Android Apk加強的初步實作思路(dex整體加強)

加殼源程式工作流程:

1. 加密源程式apk檔案為解殼資料。

2. 計算解殼資料長度,添加該資料長度到解殼dex檔案頭的末尾(插入資料的位置為0x70處),并繼續添加解殼資料到檔案頭末尾。

3. 修改解殼程式dex檔案頭中checksum、signature、file_size、header_size、string_ids_off、type_ids_off、proto_ids_off、field_ids_off、method_ids_off、class_defs_off和data_off相關項,分析map_off 資料修改相關的資料偏移量。

4. 修改源程式AndroidMainfest.xml檔案并覆寫解殼程式AndroidMainfest.xml檔案。

解殼程式工作流程:

1.從解殼程式的dex檔案位置偏移0x70處讀取解殼資料的長度。

2.從解殼程式的dex檔案中讀取解殼資料并解密解殼資料,以檔案形式儲存解密資料到xx.apk。

3.擷取到釋放出的源程式xx.apk,解殼程式apk應用通過DexClassLoader動态加載xx.apk進行運作。

提示:

上面的示意圖和加殼步驟是搬運自​​​Jack_Jia​​大牛的部落格修改而來,知道自己語言組織能力不行也不獻醜了。盡管現在很多Android加強廠商加密和隐藏源程式apk都不會采取上面的方法,但是對于學習Android的加強拓展思路還是有幫助的。當然了Android加強廠商也不會簡單粗暴的直接加密整個源程式的apk,這樣做效率不高也沒有必要,将源程式的apk進行解包拆分為dex檔案、so庫檔案和資源,然後分别對dex檔案、so庫檔案和資源進行有針對性的加強處理,例如:dex檔案的記憶體加載、so庫檔案的自定義加載、資源的弱加強處理等等,上面提到的Android加殼方法僅僅是一個大緻的實作思路,離商業應用還有一段不小的距離。

三、Android 源程式加殼的代碼實作

Jack_Jia提供了兩種加密隐藏Android源程式的方法,第1種相對來說比較簡單,第2種比較複雜,涉及到Android解殼程式dex檔案的修改地方比較多,是以以第1種對整個Android源程式APK包進行加密隐藏處理的方案來實作。Jack_Jia提供的加殼代碼是基于Android 2.3的系統代碼來實作的,如果要該加殼代碼在Android系統以後版本上運作成功,還需參考相關Android系統源碼APK運作的流程來進行對應的反射修改。
(1).解殼資料位于解殼程式dex檔案尾部的加殼流程:

1.加密源程式apk為解殼資料,加密源程式apk的算法需要自己自定義。

2.把解殼資料寫入到解殼程式dex檔案末尾,并在檔案尾部添加解殼資料的長度大小。

3.修正解殼程式dex檔案頭中checksum、signature 和file_size字段。

對Android源程式apk進行加密隐藏的功能由java代碼編寫的加密工具DexShellTool來實作,其中[ g:/payload.apk ]為被加密隐藏保護的源程式apk,[ g:/unshell.dex ]為Android解殼程式apk的原dex檔案,[ g:/classes.dex ]為Android解殼程式apk被修改後帶有解殼資料的新dex檔案。

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.Adler32;


// 對加殼的Apk進行加密隐藏處理的工具代碼
public class DexShellTool


    public static void main(String[] args) {

        try {

            // 打開被加殼的apk檔案"g:/payload.apk"
            File payloadSrcFile = new File("g:/payload.apk");
            // 打開加殼apk程式的外殼apk的dex檔案"g:/unshell.dex"
            File unShellDexFile = new File("g:/unshell.dex");

            // 讀取payload.apk檔案的資料内容進行加密處理
            byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));
            // 讀取外殼apk的dex檔案"g:/unshell.dex"的資料到位元組數組中
            byte[] unShellDexArray = readFileBytes(unShellDexFile);

            // 加密後payload.apk檔案的資料長度
            int payloadLen = payloadArray.length;
            // 外殼程式apk的dex檔案"g:/unshell.dex"的資料長度
            int unShellDexLen = unShellDexArray.length;

            // 擷取加密後"g:/payload.apk"檔案隐藏到"g:/unshell.dex"中所需的位元組長度
            int totalLen = payloadLen + unShellDexLen +4;
            // 申請記憶體空間
            byte[] newdex = new byte[totalLen];


            // 添加解殼代碼"g:/unshell.dex"到申請記憶體緩沖區中
            System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);
            // 添加加密後"g:/payload.apk"檔案的解殼資料到申請記憶體緩沖區中
            System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);
            // 添加加密後"g:/payload.apk"檔案的解殼資料長度到末尾的4位元組
            System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);

             // 修正DEX file size檔案頭
            fixFileSizeHeader(newdex);
             // 修正DEX SHA1簽名 檔案頭
            fixSHA1Header(newdex);
             //修改DEX CheckSum檔案頭
            fixCheckSumHeader(newdex);


            String str = "g:/classes.dex";
            // 建立或者打開檔案"g:/classes.dex"
            File file = new File(str);
            // 删除已存在舊的檔案
            if (!file.exists()) {

                file.createNewFile();
            }

            // 建構新的加殼的外殼dex檔案"g:/classes.dex"
            FileOutputStream localFileOutputStream = new FileOutputStream(str);
            localFileOutputStream.write(newdex);
            // 重新整理檔案流
            localFileOutputStream.flush();
            // 關閉檔案即建構檔案完成
            localFileOutputStream.close();

        } catch (Exception e) {

            e.printStackTrace();
        }
    }

    // 直接傳回資料,讀者可以添加自己加密方法
    private static byte[] encrpt(byte[] srcdata){

        // 自定義的加密算法,對原apk檔案的資料和apk檔案的長度進行加密處理
        return srcdata;
    }


    // 修正dex檔案頭的校驗碼checksum字段
    private static void fixCheckSumHeader(byte[] dexBytes) {

        Adler32 adler = new Adler32();
        adler.update(dexBytes, 12, dexBytes.length - 12);
        long value = adler.getValue();
        int va = (int) value;
        byte[] newcs = intToByte(va);
        byte[] recs = new byte[4];

        for (int i = 0; i < 4; i++) {

            recs[i] = newcs[newcs.length - 1 - i];
            System.out.println(Integer.toHexString(newcs[i]));
        }
        // 修正dex檔案的校驗碼checksum
        System.arraycopy(recs, 0, dexBytes, 8, 4);

        System.out.println(Long.toHexString(value));
        System.out.println();
    }


    // 将int型資料轉換成位元組數組的形式存放
    public static byte[] intToByte(int number) {

        byte[] b = new byte[4];
        for (int i = 3; i >= 0; i--) {

            b[i] = (byte) (number % 256);
            number >>= 8;
        }

        return b;
    }


    // 修正dex檔案頭存放dex檔案signature(SHA-1簽名)的字段
    private static void fixSHA1Header(byte[] dexBytes) throws NoSuchAlgorithmException {

        MessageDigest md = MessageDigest.getInstance("SHA-1");
        md.update(dexBytes, 32, dexBytes.length - 32);
        byte[] newdt = md.digest();
        // 修正dex檔案的SHA-1簽名
        System.arraycopy(newdt, 0, dexBytes, 12, 20);

        // 列印SHA-1簽名
        String hexstr = "";
        for (int i = 0; i < newdt.length; i++) {

            hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16)
                    .substring(1);
        }
        System.out.println(hexstr);
    }


    // 修正dex檔案頭存放dex檔案大小的字段file_size
    private static void fixFileSizeHeader(byte[] dexBytes) {

        byte[] newfs = intToByte(dexBytes.length);
        System.out.println(Integer.toHexString(dexBytes.length));

        byte[] refs = new byte[4];
        for (int i = 0; i < 4; i++) {

            refs[i] = newfs[newfs.length - 1 - i];
            System.out.println(Integer.toHexString(newfs[i]));
        }

        // 修正dex檔案的大小
        System.arraycopy(refs, 0, dexBytes, 32, 4);
    }


    // 讀取檔案的資料内容到位元組數組中
    private static byte[] readFileBytes(File file) throws IOException {

        byte[] arrayOfByte = new byte[1024];
        ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
        FileInputStream fis = new FileInputStream(file);

        while (true) {

            // 讀取檔案中的資料内容
            int i = fis.read(arrayOfByte);
            if (i != -1) {

                // 将讀取的檔案資料寫入到記憶體緩沖區中
                localByteArrayOutputStream.write(arrayOfByte, 0, i);

            } else {

                // 傳回讀取的檔案的資料的位元組流數組
                return      
(2).加殼的Android源程式被Android解殼程式解殼的流程和代碼實作:
  1. Android應用程式由不同的元件構成,Android系統在有需要的時候才會啟動Android程式的元件。是以,解殼程式必須在Android系統啟動元件之前運作,完成對解殼資料的解殼以及解殼後對源程式apk檔案的動态加載,否則會使程式出現加載類失敗的異常。Android開發者都知道Applicaiton做為整個應用的上下文,會被Android系統第一時間調用,這也是Android應用開發者程式代碼的第一執行點。是以,通過對解殼程式AndroidMainfest.xml中的 application 進行配置即繼承實作Application類,就可以實作解殼代碼第一時間運作,如下圖所示:
  2. Android Apk加強的初步實作思路(dex整體加強)
  3. 如何替換回源程式Apk原有的Application? 當在解殼程式Apk的AndroidMainfest.xml檔案配置為解殼代碼的Application類時,源程式Apk原有的Applicaiton将被替換。為了不影響源程式Apk的代碼邏輯,我們需要在解殼代碼運作完成後,替換回源程式Apk原有的Application類對象,我們通過在解殼程式apk的AndroidMainfest.xml檔案中配置源程式apk原有Applicaiton類的資訊 來達到我們的目的,解殼程式Apk要在運作完畢後,通過建立meta-data配置的Application對象并通過反射修改回源程式Apk的原Application。在解殼程式apk的AndroidMainfest.xml檔案中配置源程式apk原有Applicaiton類的資訊如下圖所示,通過反射的修改見後面的實作代碼:
  4. Android Apk加強的初步實作思路(dex整體加強)
  5. 如何通過DexClassLoader實作對apk代碼的動态加載? 我們知道DexClassLoader加載的類是沒有元件生命周期的,也就是說即使DexClassLoader通過對APK的動态加載完成了對元件類的加載,當系統啟動該元件時,還會出現加載類失敗的異常。為什麼Android元件類被動态加載入Android虛拟機,但Android系統卻出現加載類失敗呢? 通過檢視Android源代碼知道Android元件類的加載是由另一個ClassLoader來完成的,DexClassLoader和系統元件ClassLoader并不存在關系,系統元件ClassLoader當然找不到由DexClassLoader加載的類,如果把系統元件ClassLoader的parent修改成DexClassLoader,就可以實作對apk代碼的動态加載。
  6. 如何使解殼後的源程式apk的資源檔案被代碼動态引用? 代碼預設引用的資源檔案在最外層的解殼程式中,是以,我們要增加系統的資源加載路徑來實作對解殼後APK檔案資源的加載,比較簡單的做法是 Android解殼程式在代碼上隻有繼承重寫的Application類而沒有其他的代碼實作,圖示什麼的使用源程式Apk的圖示;将源程式apk的全部資源拷貝到解殼程式apk中并将源程式apk的activity、service、content provider、broadcast receiver等的聲明描述添加到解殼程式apk的AndroidMainfest.xml檔案中。

源程式Apk的解殼代碼在解殼程式Apk繼承重寫Application類的ProxyApplication中實作,在ProxyApplication類的attachBaseContext方法中實作對源程式apk(這裡為 payload.apk)的解殼釋放和源程式apk的DexClassLoader加載以及将源程式的DexClassLoader與解殼程式的ClassLoader引用關聯起來,在ProxyApplication類的onCreate方法中通過反射修改替換掉解殼程式apk的Application為源程式apk的Application并調用執行。

解殼程式的解殼代碼 ProxyApplication.java 的實作

package com.example.androidshell;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import dalvik.system.DexClassLoader;
import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;



public class ProxyApplication extends Application

    private static final String appkey = "APPLICATION_CLASS_NAME";
    private String apkFileName;
    private String odexPath;
    private String libPath;


    protected void attachBaseContext(Context base) {

        super.attachBaseContext(base);
        try {

            // 在目前apk目錄下建立檔案夾"payload_odex"
            File odex = this.getDir("payload_odex", MODE_PRIVATE);
            // 在目前apk目錄下建立檔案夾"payload_lib"存放被加密隐藏的apk的so庫檔案
            File libs = this.getDir("payload_lib", MODE_PRIVATE);

            // 擷取檔案的全路徑
            odexPath = odex.getAbsolutePath();
            libPath = libs.getAbsolutePath();

            // 拼接建構檔案路徑字元串
            apkFileName = odex.getAbsolutePath() + "/payload.apk";
            // 建立檔案"payload.apk"
            File dexFile = new File(apkFileName);
            // 删除舊的檔案
            if (!dexFile.exists())
                dexFile.createNewFile();

            // 讀取加殼外殼apk程式的classes.dex檔案資料到位元組數組中
            byte[] dexdata = this.readDexFileFromApk();

            // 解密釋放被加密隐藏的apk檔案并擷取該apk的so庫檔案
            this.splitPayLoadFromDex(dexdata);


            // 配置動态加載環境,用以後面擷取目前apk的父ClassLoader
            Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread",
                    new Class[] {}, new Object[] {});
            // 擷取目前外殼apk的包名
            String packageName = this.getPackageName();
            HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mPackages");

            // 擷取目前外殼apk的引用
            WeakReference wr = (WeakReference) mPackages.get(packageName);
            // public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) 
            // 動态加載被加隐藏釋放的原apk程式
            DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, libPath, 
                    (ClassLoader)RefInvoke.getFieldOjbect("android.app.LoadedApk", wr.get(), "mClassLoader"));

            // 将動态加載的加密隐藏釋放的原apk的ClassLoader和目前外殼apk的引用關聯起來
            RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", wr.get(), dLoader);

        } catch (Exception e) {

            e.printStackTrace();
        }
    }


    // 先執行的attachBaseContext再執行的onCreate函數
    public void onCreate() {

        // 如果源應用配置有Appliction對象,則替換為源應用Applicaiton,以便不影響源程式邏輯。
        String appClassName = null;
        try {

            // 擷取meta-data中繼資料即擷取被加密隐藏的原apk的Application
            // 其實APPLICATION_CLASS_NAME不一定要配置在AndroidMainfest.xml檔案中,寫在代碼裡也是可以的,隻不過通用性不是那麼好。
            ApplicationInfo ai = this.getPackageManager().getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA);
            Bundle bundle = ai.metaData;
            if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {

                // 擷取被加密隐藏的原apk的Application
                appClassName = bundle.getString("APPLICATION_CLASS_NAME");

            } else {

                return;
            }

        } catch (NameNotFoundException e) {

            e.printStackTrace();
        }

        // 調用靜态方法android.app.ActivityThread.currentActivityThread 擷取目前activity的線程對象
        //      public static ActivityThread currentActivityThread() {
        //          return sCurrentActivityThread;
        //      }
        Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread", 
                new Class[] {}, new Object[] {});

        // 擷取目前activity的線程對象sCurrentActivityThread中mBoundApplication成員變量,該對象是一個AppBindData類對象
        Object mBoundApplication = RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mBoundApplication");
        // 擷取mBoundApplication中的info成員變量,其中info是LoadedApk類對象
        Object loadedApkInfo = RefInvoke.getFieldOjbect("android.app.ActivityThread$AppBindData", mBoundApplication, "info");
        // 設定loadedApkInfo對象的mApplication成員變量為null(因為不是通過系統加載apk的方式加載的)
        RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication", loadedApkInfo, null);

        // 擷取currentActivityThread對象sCurrentActivityThread中的mInitialApplication成員變量(存放目前Application)
        Object oldApplication = RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mInitialApplication");
        // 擷取currentActivityThread對象sCurrentActivityThread中的mAllApplications成員變量(存放Application的清單)
        ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke.getFieldOjbect("android.app.ActivityThread", 
                currentActivityThread, "mAllApplications");
        // 從目前mAllApplications中移除目前(舊的)Application
        mAllApplications.remove(oldApplication);

        // 擷取上面得到LoadedApk對象中的mApplicationInfo成員變量,是個ApplicationInfo對象
        ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke.getFieldOjbect("android.app.LoadedApk", 
                loadedApkInfo, "mApplicationInfo");
        // 擷取上面得到AppBindData對象中的appInfo成員變量,也是個ApplicationInfo對象
        ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke.getFieldOjbect("android.app.ActivityThread$AppBindData", 
                mBoundApplication, "appInfo");
        // 把上面兩個對象的classNam成員變量設定為從meta-data中擷取的被隐藏加密的apk的Application路徑
        appinfo_In_LoadedApk.className = appClassName;
        appinfo_In_AppBindData.className = appClassName;

        // 調用LoadedApk中的makeApplication 方法,構造一個新的application
        Application app = (Application) RefInvoke.invokeMethod("android.app.LoadedApk", "makeApplication",
                loadedApkInfo, new Class[] { boolean.class, Instrumentation.class }, new Object[] { false, null });
        // 設定currentActivityThread對象sCurrentActivityThread中的mInitialApplication成員變量的值為新構造的Application
        RefInvoke.setFieldOjbect("android.app.ActivityThread", "mInitialApplication", currentActivityThread, app);

        // 擷取目前activity的線程對象sCurrentActivityThread中的成員變量mProviderMap的值
        HashMap mProviderMap = (HashMap) RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mProviderMap");
        // 周遊該對象mProviderMap
        Iterator it = mProviderMap.values().iterator();
        while (it.hasNext()) {

            Object providerClientRecord = it.next();
            // 擷取ProviderClientRecord類對象的成員變量mLocalProvider
            Object localProvider = RefInvoke.getFieldOjbect("android.app.ActivityThread$ProviderClientRecord",
                    providerClientRecord, "mLocalProvider");

            // 設定mLocalProvider中的成員變量mContext的值為新建構的Application即app
            RefInvoke.setFieldOjbect("android.content.ContentProvider", "mContext", localProvider, app);
        }

        // 新建構Application調用onCreate()方法
        app.onCreate();
    }


    // 解密釋放被加密隐藏的apk檔案并擷取該apk的so庫檔案
    private void splitPayLoadFromDex(byte[] data) throws IOException {

        // 擷取被加密apk解密後的apk檔案相關的資料(包括長度和原apk檔案的資料)
        byte[] apkdata = decrypt(data);
        // 擷取解密後apk檔案相關的資料長度(存放在最後4位元組)
        int ablen = apkdata.length;

        // 擷取被加密原apk檔案的資料長度的位元組數組
        byte[] dexlen = new byte[4];
        System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);

        // 擷取到被加密原apk檔案的int型的資料長度
        ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
        DataInputStream in = new DataInputStream(bais);
        int readInt = in.readInt();
        // 列印原apk檔案的資料長度
        System.out.println(Integer.toHexString(readInt));

        // 擷取原apk檔案的資料釋放出被隐藏的apk檔案
        byte[] newdex = new byte[readInt];
        System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);

        // 釋放被隐藏的apk檔案
        File file = new File(apkFileName);
        try {

            // 釋放被加密隐藏的原apk檔案
            FileOutputStream localFileOutputStream = new FileOutputStream(file);
            localFileOutputStream.write(newdex);
            localFileOutputStream.close();

        } catch (IOException localIOException) {

            throw new RuntimeException(localIOException);
        }

        // 解析被加密的原apk檔案擷取so庫檔案
        ZipInputStream localZipInputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(file)));
        while (true) {

            ZipEntry localZipEntry = localZipInputStream.getNextEntry();
            if (localZipEntry == null) {

                localZipInputStream.close();
                break;
            }

            // 被加密apk的so庫檔案儲存到指定的檔案路徑下
            String name = localZipEntry.getName();
            if (name.startsWith("lib/") && name.endsWith(".so")) {

                File storeFile = new File(libPath + "/" + name.substring(name.lastIndexOf('/')));
                storeFile.createNewFile();
                FileOutputStream fos = new FileOutputStream(storeFile);
                byte[] arrayOfByte = new byte[1024];

                while (true) {

                    int i = localZipInputStream.read(arrayOfByte);
                    if (i == -1)
                        break;

                    fos.write(arrayOfByte, 0, i);
                }
                fos.flush();
                fos.close();
            }
            localZipInputStream.closeEntry();
        }
        localZipInputStream.close();
    }


    // 讀取加殼外殼apk即目前apk的"classes.dex"檔案到位元組記憶體緩沖區中
    private byte[] readDexFileFromApk() throws IOException {

        ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
        // 讀取目前外殼apk檔案的資料
        ZipInputStream localZipInputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(this.getApplicationInfo().sourceDir)));
        while (true) {

            // 周遊外殼apk壓縮檔案
            ZipEntry localZipEntry = localZipInputStream.getNextEntry();
            if (localZipEntry == null) {

                localZipInputStream.close();
                break;
            }

            // 判斷是否是加殼外殼apk的"classes.dex"即目前apk的"classes.dex"檔案
            if (localZipEntry.getName().equals("classes.dex")) {

                byte[] arrayOfByte = new byte[1024];
                while (true) {

                    int i = localZipInputStream.read(arrayOfByte);
                    if (i == -1)
                        break;

                    // 把讀取的dex檔案的資料寫到到位元組記憶體緩沖區中
                    dexByteArrayOutputStream.write(arrayOfByte, 0, i);
                }
            }

            localZipInputStream.closeEntry();
        }
        localZipInputStream.close();

        return dexByteArrayOutputStream.toByteArray();
    }


    // 直接傳回資料,讀者可以添加自己解密方法
    // 傳回被加密apk解密後的apk檔案的資料,例如:payload.apk
    private byte[] decrypt(byte[] data) {

        // 對加密的apk程式的資料進行解密處理
        return      

上面的代碼中使用到的反射工具類 RefInvoke.java 的實作

package com.example.androidshell;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


// Android的類反射調用的工具
public class RefInvoke


    // 通過類反射調用調用類的靜态方法
    public static  Object invokeStaticMethod(String class_name, String method_name, Class[] pareTyple, Object[] pareVaules) {

        try {

            Class obj_class = Class.forName(class_name);
            Method method = obj_class.getMethod(method_name, pareTyple); 

            return method.invoke(null, pareVaules);

        } catch (SecurityException e) {

            e.printStackTrace();
        }  catch (IllegalArgumentException e) {

            e.printStackTrace();
        } catch (IllegalAccessException e) {

            e.printStackTrace();
        } catch (NoSuchMethodException e) {

            e.printStackTrace();
        } catch (InvocationTargetException e) {

            e.printStackTrace();
        } catch (ClassNotFoundException e) {

            e.printStackTrace();
        }
        return null;

    }


    // 通過類反射調用調用類的執行個體方法
    public static  Object invokeMethod(String class_name, String method_name, Object obj ,Class[] pareTyple, Object[] pareVaules){

        try {

            Class obj_class = Class.forName(class_name);
            Method method = obj_class.getMethod(method_name, pareTyple);

            return method.invoke(obj, pareVaules);

        } catch (SecurityException e) {

            e.printStackTrace();
        }  catch (IllegalArgumentException e) {

            e.printStackTrace();
        } catch (IllegalAccessException e) {

            e.printStackTrace();
        } catch (NoSuchMethodException e) {

            e.printStackTrace();
        } catch (InvocationTargetException e) {

            e.printStackTrace();
        } catch (ClassNotFoundException e) {

            e.printStackTrace();
        }
        return null;

    }


    // 通過類反射調用擷取類的執行個體成員變量
    public static Object getFieldOjbect(String class_name, Object obj, String filedName){

        try {

            Class obj_class = Class.forName(class_name);
            Field field = obj_class.getDeclaredField(filedName);
            field.setAccessible(true);

            return field.get(obj);

        } catch (SecurityException e) {

            e.printStackTrace();
        } catch (NoSuchFieldException e) {

            e.printStackTrace();
        } catch (IllegalArgumentException e) {

            e.printStackTrace();
        } catch (IllegalAccessException e) {

            e.printStackTrace();
        } catch (ClassNotFoundException e) {

            e.printStackTrace();
        }
        return null;

    }


    // 通過類反射調用擷取類的靜态成員變量
    public static Object getStaticFieldOjbect(String class_name, String filedName){

        try {

            Class<?> obj_class = Class.forName(class_name);
            Field field = obj_class.getDeclaredField(filedName);
            field.setAccessible(true);

            return field.get(null);

        } catch (SecurityException e) {

            e.printStackTrace();
        } catch (NoSuchFieldException e) {

            e.printStackTrace();
        } catch (IllegalArgumentException e) {

            e.printStackTrace();
        } catch (IllegalAccessException e) {

            e.printStackTrace();
        } catch (ClassNotFoundException e) {

            e.printStackTrace();
        }
        return null;

    }


    // 通過類反射調用設定類的執行個體成員變量的值
    public static void setFieldOjbect(String classname, String filedName, Object obj, Object filedVaule){

        try {

            Class<?> obj_class = Class.forName(classname);
            Field field = obj_class.getDeclaredField(filedName);
            field.setAccessible(true);
            field.set(obj, filedVaule);

        } catch (SecurityException e) {

            e.printStackTrace();
        } catch (NoSuchFieldException e) {

            e.printStackTrace();
        } catch (IllegalArgumentException e) {

            e.printStackTrace();
        } catch (IllegalAccessException e) {

            e.printStackTrace();
        } catch (ClassNotFoundException e) {

            e.printStackTrace();
        }   
    }


    // 通過類反射調用設定類的靜态成員變量的值
    public static void setStaticOjbect(String class_name, String filedName, Object filedVaule) {

        try {

            Class<?> obj_class = Class.forName(class_name);
            Field field = obj_class.getDeclaredField(filedName);
            field.setAccessible(true);
            field.set(null, filedVaule);

        } catch (SecurityException e) {

            e.printStackTrace();
        } catch (NoSuchFieldException e) {

            e.printStackTrace();
        } catch (IllegalArgumentException e) {

            e.printStackTrace();
        } catch (IllegalAccessException e) {

            e.printStackTrace();
        } catch      

四、後 記