安卓應用加固之四代加殼保護技術詳解

来源:https://www.cnblogs.com/victor-paladin/archive/2019/08/01/11283472.html
-Advertisement-
Play Games

#0x00 前言 安卓應用加固技術是伴隨安卓應用破解技術一同發展起來的。 安卓應用加固技術的發展可以劃分為以下幾代技術: 第一代殼:Dex混淆、Dex加密、資源加密、反調試及自定義DexClassLoader 第二代殼:Dex抽取與so加固、Dex Method代碼抽取、Dex動態載入及so加固 第 ...


#0x00 前言

安卓應用加固技術是伴隨安卓應用破解技術一同發展起來的。加固技術分為加殼保護和反反彙編保護技術。市面上成熟的加固廠商一般會使用加殼保護技術配合反反彙編技術對安卓應用進行加固。

安卓反反彙編技術的總結在我另一篇博客中將進行詳細探討,鏈接:

http://.................(更新中,未完待續)

 

安卓應用加殼技術的發展可以劃分為以下幾代殼技術:

第一代殼——動態載入殼:對用戶端的Dex、so、資源進行混淆及加密,在運行時再解密,最後通過自定義ClassLoader動態載入源APK

第二代殼——代碼抽取殼:抽取Dex中方法的實現代碼加密存儲在用戶端,APK運行時調用殼的Native方法解密Dex中被加密的方法,執行完後再加密

第三代殼——代碼混淆殼:指令變換、花指令混淆、指令膨脹、代碼流程混淆等

 

#0x01 第一代殼

一、Dex、so混淆

1.代碼混淆

即將源程式代碼中的類名、方法名修改為a、b、c()的形式,讓破解這不能通過類名、方法名輕易地尋找/猜測到函數所實現的功能,增加逆向分析的難度

 

2.編譯期混淆

若APK在開發時使用的是LLVM編譯套件進行編譯,則可以在編譯期間對代碼進行混淆。分為前端混淆IR混淆

前端混淆:指編譯器前端在進行原生代碼分析時採取的混淆技術

IR混淆:指在編譯原生代碼時對生成的中間代碼IR進行混淆的技術

 

3.二進位混淆

在生成APK文件後,提取出其中的二進位文件,對二進位文件再進行混淆。典型的技術有Dex二次混淆,即對生成的Dex文件進行反編譯後,再進行混淆。使得Dex的執行流程被混淆和字元串被混淆。

 

 1 //以字元串混淆為例
 2 
 3 //混淆前
 4 void methodA()
 5 {
 6     clz.method1("abc");
 7     clz.method3(clz.method2("def"),"ghi");
 8 }
 9 
10 //使用ProGuard進行代碼混淆後的反彙編代碼可能如下
11 void methodA()
12 {
13     a.a("abc");
14     a.aaa(a.aa("def"),"ghi");
15 }
16 
17 //使用DexGuard進行代碼混淆後的反彙編代碼可能如下,b.a()方法
18 void methodA()
19 {
20     a.a(b.a("xxxxxxxxx"));
21     a.aaa(a.aa(b.a("yyyyyyyyy")),b.a("zzzzzzzzz"));
22 }

 

 

二、Dex加密

 Dex加密指將APK的DEX文件進行加密,在運行時再解密載入到記憶體中執行,防止Dex文件被逆向的一種保護技術。

Dex加密APK的執行流程如下圖

 

加固過程

1.需要編寫三個項目:

(1)源APK(真正的APK程式)

(2)加殼程式(Java項目,用於對源APK進行加密,並將加密後的源APK與脫殼程式的dex進行合併)

(3)脫殼程式(Android項目,用於對加密後的源APK文件進行解密,並將其載入到記憶體中去)

2.加殼程式將源APK加密成所有操作系統都不能正確讀取的APK文件。

3.加殼程式再提取出解密APK的classes.dex文件,將脫殼程式的dex文件與加密後的源apk進行合併,得到新的dex文件

4.在新dex文件的末尾添加一個長度為4位的欄位,用於標識加密apk的大小

5.修改新dex文件的頭部三個欄位:checksum,signature,file_size,以保證新dex文件的正確性

6.生成新的classes.dex文件,打包,生成加固APK

 

源APK加密原理流程如下圖

 

 

加殼程式主要邏輯代碼分析:

 1 public static void main(String args[])
 2 {
 3     try
 4     {
 5         //---------獲取文件-------------------------------------------
 6         //獲取需要加密的源apk程式
 7         File payloadSrcFile = new File("xxxx/abc.apk");
 8         System.out.println("SrcAPK size:"+payloadSrcFile.length());
 9         //獲取脫殼(解密)程式的DEX文件,需要提前把脫殼程式的DEX文件解壓出來
10         File unshellDexFile = new File("yyyy/classes.dex");
11 
12         //---------加密源apk文件----------------------------------------
13         /*
14         以二進位形式讀取apk,並加密,存到payloadArray數組中
15         readFileBytes是以二進位形式讀取文件
16         encrpt()是自定義的對二進位流加密的函數
17         */
18         byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));
19         //以二進位形式讀取脫殼dex
20         byte[] unshellDexArray = readFileBytes(unshellDexFile);
21 
22         //---------合併------------------------------------------------
23         //***1.計算出新Dex文件的總長度
24         int payloadLen = payloadArray.length();    //加密APK的長度
25         int unshellDexLen = unshellDexArray.length();    //脫殼Dex的長度
26         int totalLen = payloadLen+unshellDexLen=4    //新的Dex文件長度,最後+4是為了用於存儲payloadLen值的
27         //***2.定義新dex文件
28         //先定義新dex文件的長度
29         byte[] newDex = new byte[totalLen];
30         //***3.開始合併
31         //將脫殼dex文件的內容寫入新dex
32         System.arraycopy(unshellDexArray,0,newDex,0,unshellDexLen);
33         //再將加密後的源APK內容連接在後面
34         /*
35         arraycopy(arr1,x,arr2,y,m)表示將數組arr1從索引為x開始的元素,複製m個元素到數組arr2中去,從arr2的y索引開始粘貼
36         */
37         System.arraycopy(payloadArray,0,newDex,unshellDexLen,payloadLen);
38         //最後將將加密後的apk長度記錄在新dex文件末尾
39         /*
40         intToByte()將int類型數據轉換成Byte
41         total是int類型的,newDex是Byte類型
42         total是脫殼dex長度+加密apk長度+4,所以,最後是從newDex的第total-4索引開始寫入payload的長度
43         一個byte是1個位元組,即8位;一個int是4個位元組,即32位;所以最後的4表示寫入intToByte(payloadLen)的4個元素
44         */
45         System.arraycopy(intToByte(payloadLen),0,newDex,totalLen-4,4);
46 
47         //---------修改新dex文件的頭部信息----------------------------------
48         //修改file_size欄位
49         fixFileSizeHeader(newDex);
50         //修改signature欄位
51         fixSHA1Header(newDex);
52         //修改checksum欄位
53         fixCheckSumHeader(newDex);
54 
55         //---------生成新dex文件------------------------------------------
56         //定義要生成文件的路徑及名稱並創建空文件
57         String str = "zzzz/classes.dex";
58         File file = new File(str);
59         if (!file.exists())
60         {
61             file.createNewFile();
62         }
63         //將合併好的數據寫入文件
64         FileOutputStream localFileOutputStream = new FileOutputStream(str);
65         localFileOutputStream.write(newDex);
66         localFileOutputStream.flush();
67         localFileOutputStream.close();
68     }catch (Exception e) {
69         e.printStackTrace();
70     }
71 }
72     
73 //加密函數encrpt()的定義
74 private static byte[] encrpt(byte data[]) 
75 {
76     //加密演算法實現,這裡就不寫了
77     return data;
78 }

 

脫殼原理流程如下圖:

用戶執行加固APP,其實進入的是脫殼APK的代碼,脫殼APK會先提取並解密出真正的APK,但此時真正的APK並沒有被載入到手機的記憶體中。因此需要在一個恰當的時期,在用戶交互界面進入脫殼APK之前將真正的APK解密提取出來並載入到記憶體中去執行起來。到底在何時進行提取、解密和載入真正APK?如下圖分析所示即最佳時機:

 如上圖所示,

(1)提取和解密APK的最佳時機則是在脫殼APK的Activity被拉起之前,Activity是通過onCreate()方法拉起來的,因此在onCreate()方法之前,應將源APK提取並解密。

(2)提取並解密出源APK之後,通過改寫脫殼APK的onCreate()方法,讓其拉起源APK。

 

脫殼程式主要邏輯代碼分析:

 

  1 //********重寫attachBaseContext()函數***********
  2 protected void attachBaseContext(Context base)
  3 {
  4     super.attachBaseContext(base);
  5     try
  6     {
  7         //---------提取加密apk---------------------------------------------
  8         //創建兩個私有、可寫的目錄payload_odex和payload_lib
  9         File odex = this.getDir("payload_odex",MODE_PRIVATE);    //用於存放源APK的dex文件
 10         File libs = this.getDir("payload_lib",MODE_PRIVATE);    //用於存放源APK的so文件
 11         odexPath = odex.getAbsolutePath();
 12         libPath = libs.getAbsolutePath();
 13         apkFileName = odex.getAbsolutePath()+"/abc.apk";
 14         File dexFile = new File(apkFileName);
 15         Log.i("demo","apk size:"+dexFile.length());
 16         if (!dexFile.exists())
 17         {
 18             //在payload_odex文件目錄下,創建abc.apk空文件
 19             dexFile.createNewFile();
 20             //讀取出加殼後的整個dex文件
 21             //readDexFileFromApk()方法在attachBaseContext()方法外面實現
 22             byte[] dexdata = this.readDexFileFromApk();
 23             //脫殼(分離出加密的源apk併進行解密),分離出apk文件以用於動態載入
 24             //splitPayloadFromDex()方法在attachBaseContext()方法外面實現
 25             this.splitPayloadFromDex(dexdata);
 26         }
 27         
 28         //---------載入解密之後的源apk程式------------------------------------
 29         //配置動態載入環境
 30         //通過反射機制獲取當前activity的線程對象
 31         Object currentActivityThread = RefInvoke.invokeStaticMethod(
 32             "android.app.ActivityThread",
 33             "currentActivityThread",
 34             new Class[] {},
 35             new Object[] {});
 36 
 37         //獲取加殼apk的包名
 38         String packageName = this.getPackageName();
 39         //通過反射獲取和設置當前APK的各種信息
 40         ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldObject(
 41             "android.app.ActivityThread",
 42             currentActivityThread,
 43             "mPackages");
 44         WeakReference wr = (WeakReference) mPackages.get(packageName);
 45 
 46         //自定義一個DexClassLoader對象,用於載入被載入解密後的源apk和源apk內的類和C/C++代碼
 47         DexClassLoader dLoader = new DexClassLoader(
 48             apkFileName,
 49             odexPath,
 50             libPath,
 51             (ClassLoader) RefInvoke.getFieldObject(
 52                 "android.app.LoadedApk",
 53                 wr.get(),
 54                 "mClassLoader"));
 55         //把當前進程的DexClassLoader()設置成脫殼apk在上一行自定義的DexClassLoader()
 56         RefInvoke.setFiedObject(
 57             "android.app.LoadedApk",
 58             "mClassLoader",
 59             wr.get(),
 60             dLoader);
 61         
 62         Log.i("demo","classloader:"+dLoader);
 63 
 64         try
 65         {
 66             Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");
 67             Log.i("demo","actObj:"+actObj);
 68         }
 69         catch (Exception e)
 70         {
 71             Log.i("demo","activity:"+Log.getStackTraceString(e));
 72         }
 73     }catch (Exception e)
 74     {
 75         Log.i("demo","error:"+Log.getStackTraceString(e));
 76         e.printStackTrace();
 77     }
 78 }
 79 
 80 
 81 /*
 82 *@name :readDexFileFromApk()
 83 *@function :從脫殼程式中提取脫殼DEX文件內容
 84 *@param     :無
 85 *@throws :IOException
 86 *@return :byte[]
 87 */
 88 private byte[] readDexFileFromApk() throws IOException
 89 {
 90     //定義一個Byte類型的數組對象,用於存儲dex文件的內容
 91     ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
 92     //定義一個ZIP對象,用於解壓APK
 93     ZipInputStream localZipInputStream = new ZipInputStream(
 94         new BufferedInputStream(new FileInputStream(
 95             this.getApplicationInfo().sourceDir)));
 96 
 97     while (true)
 98     {    
 99         ////解壓並遍歷ZIP文件,以二進位流形式讀入數組arrayOfByte[]
100         ZipEntry localZipEntry = localZipInputStream.getNextEntry();
101         if (localZipEntry == null)
102         {
103             localZipInputStream.close();
104             break;
105         }
106         //找到脫殼dex文件
107         if (localZipEntry.getName().equals("classes.dex"))
108         {
109             
110             byte[] arrayOfByte = new byte[1024];
111             while (true)
112             {
113                 int i = localZipInputStream.read(arrayOfByte);
114                 if (i == -1)
115                 {
116                     break;
117                 }
118                 dexByteArrayOutputStream.write(arrayOfByte,0,i);
119             }
120         }
121         localZipInputStream.closeEntry();
122     }
123     localZipInputStream.close();
124     return dexByteArrayOutputStream.toByteArray();
125 }
126 
127 /*
128 *@name :splitPayloadFromDex(byte apkdata[])
129 *@function :從脫殼dex文件中提取出被加密的apk文件和so文件,並解密
130 *@param     :apkdata -要解密的DEX文件二進位流
131 *@throws :IOException
132 *@return :無
133 */
134 private void splitPayloadFromDex(byte apkdata[]) throws IOException
135 {
136     //---------剝離出加密源APK---------------------------------------------
137     //獲取整個dex文件的長度
138     int ablen = apkdata.length();
139     //獲取脫殼dex最後4位的數據,即獲取源加密APK的長度
140     //ablen-4在arraycopy()中即表示從apkdata的第倒數第四位開始
141     byte[] dexlen = new byte[4];
142     System.arraycopy(apkdata,ablen-4,dexlen,0,4);
143 
144     //對獲取到的4位元組長度值進行轉換,將二進位數組類型轉換成可以計算的int類型
145     ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
146     DataInputStream in = new DataInputStream(bais);
147     int readInt = in.readInt();
148     System.out.println(Integer.toHexString(readInt));
149 
150     //定義一個二進位流數組用來存儲即將提取出來的加密apk
151     byte[] newdex = new byte[readInt];
152     //把加密apk存到newdex中
153     /*
154     apkdata是整個dex文件的數據內容
155     ablen是整個dex文件的長度-4即表示整個dex文件從頭到加密apk的末尾的位置
156     readInt表示加密apk的長度
157     ablen-4再-readInt即表示從整個dex從頭到脫殼dex的末尾的位置
158     */
159     System.arraycopy(apkdata,ablen-4-readInt,newdex,0,readInt);
160 
161     //---------解密源APK-------------------------------------------------------
162     //decrypt()函數在splitPayloadFromDex()函數外實現
163     newdex = decrypt(newdex);
164 
165     //---------釋放解密後的源APK到磁碟-----------------------------------------
166     File file = new File(apkFileName);
167     try
168     {
169         FileOutputStream localFileOutputStream = new FileOutputStream(file);
170         localFileOutputStream.write(newdex);
171         localFileOutputStream.close();
172     }catch (IOException e)
173     {
174         throw new RuntimeException(localIOException);
175     }
176     
177     //---------分析釋放出來的源APK----------------------------------------------
178     //定義一個ZIP對象
179     ZipInputStream localZipInputStream = new ZipInputStream(
180         new BufferedInputStream(new FileOutputStream(file)));
181     while (true)
182     {
183         //解壓並遍歷ZIP包
184         ZipEntry localZipEntry = localZipInputStream.getNextEntry();
185         if (localZipInputStream == null)
186         {
187             localZipInputStream.close();
188             break;
189         }
190         //提取出APK中的so文件,並釋放到脫殼程式一開始在磁碟新建的payload_lib目錄中去中去
191         String name = localZipEntry.getName();
192         if (name.startWith("lib/") && name.endwith(".so"))    //確認遍歷到的文件是否是so文件
193         {
194             File storeFile = new File(libPath+"/"+name.substring(name.lastIndexOf('/')));
195             storeFile.createNewFile();
196             FileOutputStream fos = new FileOutputStream(storeFile);
197             byte[] arrayOfByte = new byte[1024];
198             while (true)
199             {
200                 int i = localZipInputStream.read(arrayOfByte);
201                 if (i == -1)
202                 {
203                     break;
204                 }
205                 fos.write(arrayOfByte,0,i);
206             }
207             fos.flush();
208             fos.close();
209         }
210         localZipInputStream.closeEntry();
211     }
212     localZipInputStream.close();
213 }
214 
215 /*
216 *@name :decrypt(byte srcdata[])
217 *@function :對被加密的apk文件進行解密
218 *@param     :srcdata -要解密的apk文件二進位流
219 *@throws :無byte
220 *@return :srcdata -byte[]類型
221 */
222 private byte[] decrypt(byte[] srcdata)
223 {
224     //解密演算法,根據加密演算法編寫對應的解密過程
225     return srcdata;
226 }
227 
228 
229 //**********重寫onCreate()方法***********
230 public void onCreate ()
231 {
232     //未完待續
233 }

 

 

 

#0x02 第二代殼

 更新中......

 

 

 

#0x03 第三代殼

 更新中.......

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在應用系統開發初期,由於開發資料庫數據比較少,對於查詢SQL語句,複雜視圖的的編寫等體會不出SQL語句各種寫法的性能優劣,但是如果將應用 系統提交實際應用後,隨著資料庫中數據的增加,系統的響應速度就成為目前系統需要解決的最主要的問題之一。系統優化中一個很重要的方面就是SQL語句的優 化。對於海量數據... ...
  • 子查詢:又分為where型子查詢,from型子查詢,exists型子查詢這三類。 where型子查詢:指把內層查詢的結果作為外層查詢的比較條件: 舉個例子: 我們想查出goods_id最大的商品,要求不能用排序: 我們還想查出每個欄目下goods_id最大的商品,要求使用where型子查詢: fro ...
  • 在這篇文章,我們一起瞭解 Redis 使用中非常重要的兩個機制:Reids 持久化和主從複製。 什麼是 Redis 持久化? Redis 作為一個鍵值對記憶體資料庫(NoSQL),數據都存儲在記憶體當中,在處理客戶端請求時,所有操作都在記憶體當中進行,如下所示 這樣做有什麼問題呢?其實,只要稍微有點電腦 ...
  • MySQL INNER JOIN子句介紹 MySQL 子句將一個表中的行與其他表中的行進行匹配,並允許從兩個表中查詢包含列的行記錄。 子句是 語句的可選部分,它出現在 "FROM子句" 之後。 在使用 子句之前,必須指定以下條件: 首先,在FROM子句中指定主表。 其次,表中要連接的主表應該出現在 ...
  • 一、問題 問題1 場景:如果你未來的丈母娘要求你,第1天給她1分錢,第2天給2分錢,第3天給4分錢,以此類推,每天給前一天的2倍,給1個月(按30天)算就行。問:第30天給多少錢,總共給多少錢? 問題1 問題2 場景:如果有兩份工作。 第1份:第1天給你1分錢,第2天給你2分錢,第3天給你4分錢,以 ...
  • 表單組件是個包含表單元素的區域,表單元素允許用戶輸入內容,比如:文本區域,下拉表單,單選框、覆選框等,常見的應用場景有:登陸、註冊、輸入信息等。表單里有兩個重要的組件,一個是Form組件用來做整個表單提交使用的,另一個是TextFormField組件用來做用戶輸入的。 ...
  • 在android源碼的驅動目錄下,一般會有共用記憶體的相關實現源碼,目錄是:kernel\drivers\staging\android\ashmem.c。但是本篇文章不是講解android共用記憶體的功能實現原理,而是講怎麼運用它。 1. 在linux中,不同進程間擁有自己獨立的記憶體空間,32位操作系 ...
  • 這是我收藏的一部分關於影音播放類的安卓源碼,有興趣的朋友們可以點擊下列標題下載學習。 安卓RhymeMusic(韻)音樂播放器應用源碼 ↓ 效果圖 用Flutter創建Android簡約本地音樂播放器↓ 效果圖 一款酷炫的音樂播放器↓ 效果圖 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...