日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

APK加壳【1】初步方案实现详解

發布時間:2025/3/15 编程问答 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 APK加壳【1】初步方案实现详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

來源與原理

本文是嘗試對CSDN大牛 Jack_Jia 的博客 Android APK加殼技術方案【2】 進行實現的過程記錄,該文介紹了一種對源程序APK加殼的思路并提供了對應的源碼。

所謂加殼,就是通過給目標APK加一層保護程序,把需要保護的內容加密、隱藏起來,來防止反編譯的一種方法。說到底我們要做的是這樣一個事情,首先把要加殼的APK用自己的加密算法加個密(實驗過程中這步可以省掉),然后藏在另一個APK中(就是殼工程)發布出去,這樣防止破解者直接拿到源程序的APK去反編譯。不好處理的是還需要殼工程在各種版本的Android系統里運行時,要把源程序解密出來還要跟直接裝源程序有同樣的運行效果才行。如何實現原文都已經寫清楚了:

  • 通過反射置換android.app.ActivityThread 中的mClassLoader為加載解密出APK的DexClassLoader,該DexClassLoader一方面加載了源程序、另一方面以原mClassLoader為父節點,這就保證了即加載了源程序又沒有放棄原先加載的資源與系統代碼;
  • 找到源程序的Application,通過反射建立并運行;
  • 方案

    整個方案里面涉及到三種角色:

  • 源程序——等待被加殼的目標程序,一個APK;【原文中的加密工具代碼、DexShellTool中的g:/payload.apk】
  • 加密工具——這是一個工具程序,用什么語言實現都是可以的。用來給源程序加密,這段功能對應的解密則在殼程序中實現;【原文中的DexShellTool代碼,是一個java程序】
  • 殼程序——它實際上也是一個Android工程,經過加殼發布出去的APK就是殼程序經過特殊處理之后生成的。它內部保存著已被加密的源程序(apk、dex或者odex),在啟動后第一時間將加密后的源程序解出來,通過類加載器動態加載運行;【原文中Menifest、ProxyApplication.java、RefInvoke.java部分】
  • 所以檢查這個方案需要有個DEMO APK、有個加密工具JAVA工程DexShellTool 和一個Android殼工程UnShell,最后加殼后的APK實際上是殼工程編譯出的、并且把其中的dex文件替換為經過加密工具處理生成的新dex、最后重新打包簽名的APK。

    源程序

    源程序其實沒什么好講的,最好是有個帶有服務、廣播、網絡操作什么的基礎功能比較全面的示例程序,這樣測試可行性更加有說服力一些。

    加密工具

    加密工具其實原文中給出的很容易看懂,因為沒有涉及到加密算法,所以不到兩百行?;咀隽诉@樣一件事:把源程序加密之后接到殼工程的dex文件尾,然后修改dex文件的文件長度、校驗和什么的。這種隱藏方式略詭異。

    殼工程

    殼工程既是殼又要有解殼功能,原文只給了兩個類,實際上也只需要這兩個類。ProxyApplication里有解殼與反射實現動態加載源程序的代碼邏輯、RefInvoke則是反射工具。許多童鞋表示反射不好理解,一開始我也是這么覺得。不過經過一行行注釋下來、對比系統源碼,其實也沒有多難。這里要說,靜下心來分析,不到三百行的代碼,能有多復雜呢?

    protected void attachBaseContext(Context base) {super.attachBaseContext(base);Log.d(TAG, "attachBaseContext hello world~");try {File odex = this.getDir("payload_odex", MODE_PRIVATE);File libs = this.getDir("payload_lib", MODE_PRIVATE);odexPath = odex.getAbsolutePath();libPath = libs.getAbsolutePath();apkFileName = odex.getAbsolutePath() + "/payload.apk";File dexFile = new File(apkFileName);if (!dexFile.exists())dexFile.createNewFile();// 讀取程序classes.dex文件byte[] dexdata = this.readDexFileFromApk();// 分離出解殼后的apk文件已用于動態加載this.splitPayLoadFromDex(dexdata);// 配置動態加載環境Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread",new Class[] {}, new Object[] {});String packageName = this.getPackageName();HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread,"mPackages");WeakReference wr = (WeakReference) mPackages.get(packageName);//換Loader操作 動態加載如被加密又裝換回來的apk文件DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,libPath, (ClassLoader) RefInvoke.getFieldOjbect("android.app.LoadedApk", wr.get(), "mClassLoader"));RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",wr.get(), dLoader);} catch (Exception e) {e.printStackTrace();}}public void onCreate() {Log.d(TAG, "on create hello world~");// 如果源應用配置有Appliction對象,則替換為源應用Applicaiton,以便不影響源程序邏輯。String appClassName = null;try {ApplicationInfo ai = this.getPackageManager().getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA);Bundle bundle = ai.metaData;if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {appClassName = bundle.getString("APPLICATION_CLASS_NAME");} else {return;}} catch (NameNotFoundException e) {e.printStackTrace();}Log.d(TAG, "the app aplication name is " + appClassName);/*** 調用靜態方法android.app.ActivityThread.currentActivityThread* 獲取當前activity所在的線程對象*/Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread",new Class[] {}, new Object[] {});/*** 獲取currentActivityThread中的mBoundApplication屬性對象,該對象是一個* AppBindData類對象,該類是ActivityThread的一個內部類*/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*/RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",loadedApkInfo, null);/*** 獲取currentActivityThread對象中的mInitialApplication屬性* 這貨是個正牌的 Application*/Object oldApplication = RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread,"mInitialApplication");/*** 獲取currentActivityThread對象中的mAllApplications屬性* 這貨是 裝Application的列表*/ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke.getFieldOjbect("android.app.ActivityThread",currentActivityThread, "mAllApplications");//列表對象終于可以直接調用了 remove調了之前獲取的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");//把這兩個對象的className屬性設置為從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 });RefInvoke.setFieldOjbect("android.app.ActivityThread","mInitialApplication", currentActivityThread, app);HashMap mProviderMap = (HashMap) RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread,"mProviderMap");Iterator it = mProviderMap.values().iterator();while (it.hasNext()) {Object providerClientRecord = it.next();Object localProvider = RefInvoke.getFieldOjbect("android.app.ActivityThread$ProviderClientRecord",providerClientRecord, "mLocalProvider");RefInvoke.setFieldOjbect("android.content.ContentProvider","mContext", localProvider, app);}if(null == app){Log.e(TAG, "application get is null !");}else{app.onCreate();}}

    輔助源碼看實際上還是很好理解的,不多說。

    順手推薦個android在線源碼瀏覽網址 http://androidxref.com/

    為了方便后續調試代碼,弄了個shell腳本,同時也可以基本解釋整個加殼的流程:

    #!/bin/bash ENCRYPT_PATH="/home/kf2lc/develop/apk_encrypt" UNSHELL_PATH="/home/kf2lc/develop/workspace/BFC/UnShell" DEX_SHELL_TOOL_PATH="/home/kf2lc/develop/workspace/BFC/DexShellTool" DEMO_TEMP_PATH="./demopac" TEMP_PATH="./apk" ARM_SO_PATH="/libs/armeabi-v7a/libNativeTool.so" ARM_MIPS_PATH="/libs/mips/libNativeTool.so"echo "清理中間文件..." cd $ENCRYPT_PATH rm Demo-*.apk rm *.dex rm UnShell.apkecho "編譯解殼工程..." cd $UNSHELL_PATH rm -rf gen bin android update project -p . ant clean debugecho "拷貝殼工程dex文件到工作目錄..." cp $UNSHELL_PATH"/bin/classes.dex" $ENCRYPT_PATH"/unshell.dex" cp $UNSHELL_PATH"/bin/BlankActivity-debug-unaligned.apk" $ENCRYPT_PATH"/UnShell.apk"echo "編譯加殼工程... 生成新的classes.dex文件到工作目錄..." cd $DEX_SHELL_TOOL_PATH ant clean compile jar runecho "解壓待加密apk... 替換classes.dex文件為加殼.dex文件..." cd $ENCRYPT_PATH unzip -d $TEMP_PATH UnShell.apk rm $TEMP_PATH"/classes.dex" mv ./classes.dex $TEMP_PATHecho "刪除簽名文件夾 重新打包apk..." cd $TEMP_PATH rm -rf ./META-INF zip -r ../Demo-encrypt-unsign.apk ./* cd ../ echo "清理中間目錄..." rm -rf $TEMP_PATHecho "為加殼后的apk重新簽名..." jarsigner -verbose -keystore bfc.keystore -signedjar Demo-encrypt.apk Demo-encrypt-unsign.apk bfc.keystore

    注意事項

  • 無論是虛擬機還是手機、平板,測試時一定要統一使用一個簽名,否則很容易出無簽名的安裝錯誤;
  • 由于本方案使用DexClassLoader作為動態加載的方案,從接口上看:
    很明顯,這貨是需要一個文件路徑的,這意味著如果直接使用該類,就必須要有個解密好的文件老老實實的躺在存儲器上,這樣一來無論你放在什么地方、該文件存在的時間有多短,破解者都有可能繞過殼、直接拿到解密的文件,這明顯不科學;
  • 資源加載,這里面我偷了個懶,殼工程的資源文件和源程序的資源文件是完全一致的,所以加載起來沒有問題。但是這樣一來整個加殼的APK實際上內部有兩份資源文件了,示例APK還好,碰見圖片多的那這個數據增量完全無法接受;
  • 本文僅是個人理解,雖然跑通了但是也不免有瞎貓撞上死耗子的幾率,錯誤加上錯誤產生正確也是可能的,僅供參考,歡迎質疑。
  • 其實原文所述的方案是加殼的一個基本思路,具體要預防反編譯實現起來肯定不會如此簡略、加殼也只是預防破解的各路招式之一。但還是要感謝大牛的蕓蕓分享,使我輩菜鳥有了一條入門之路。


    原文地址: http://taoyuanxiaoqi.com/2015/01/12/apkshell1/

    總結

    以上是生活随笔為你收集整理的APK加壳【1】初步方案实现详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。