热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

安卓应用加固之各代加壳保护技术详解

#0x00前言安卓应用加固技术是伴随安卓应用破解技术一同发展起来的。加固技术分为加壳保护和反反汇编保护技术。市面上成熟的加固厂商一般会使用加壳保护技术配合反反汇编技术对安卓应用进行

#0x00 前言

安卓应用加固技术是伴随安卓应用破解技术一同发展起来的。加固技术分为加壳保护和反反汇编保护技术。市面上成熟的加固厂商一般会使用加壳保护技术配合反反汇编技术对安卓应用进行加固。

安卓反反汇编技术的总结在我以下博客中将进行详细探讨(未写完),链接:

反反汇编:

https://www.cnblogs.com/victor-paladin/p/11406646.html

反动态调试:

https://www.cnblogs.com/victor-paladin/p/11406740.html

 

安卓应用加壳技术的发展可以划分为以下几代壳技术:

第一代壳——动态加载壳:对用户端的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.#(a.aa("def"),"ghi"); 15 } 16 17 //使用DexGuard进行代码混淆后的反汇编代码可能如下,b.a()方法 18 void methodA() 19 { 20 a.a(b.a("xxxxxxxxx")); 21 a.#(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 第三代壳

 更新中…….

 

转:https://www.cnblogs.com/victor-paladin/p/11283472.html


推荐阅读
  • 1.File类:文件和目录路径名的抽象表现形式2.创建对象:File(Stringpathname)通过给定的路径创建文件对象File(Stringpa ... [详细]
  • 在实际开发中,现在安卓端和后台之间的数据交互,一般都是用JSON来传递数据信息。JSON大家一般都比较熟悉。我这边就以实际项目中的后台传过来的情况和大家分析下及如何处理。比如后台返 ... [详细]
  • Java的核心库提供了大量的现成的类供我们使用。本节我们介绍几个常用的工具类。Math顾名思义,Math类就是用来进行数学计算的,它提供了大量的静态 ... [详细]
  • JNI技术实践小结转自http:sett ... [详细]
  • PIMPL 是 C++ 中的一个编程技巧,意思为指向实现的指针。具体操作是把类的实现细节放到一个单独的类中,并用一个指针进行访问 ... [详细]
  • Java中的FileStoregetUsableSpace()方法,带示例 ... [详细]
  • 最近想用js做一个简单的计算器,不过网上的例子好像大部分都是直接从左到右挨个计算,就好像1+2*5,就会先计算1+2,再计算3*5,并没有实现运算符的优先级,这里找到了一种方法实现,来总结一下。不过这 ... [详细]
  • DFS基本概念步骤优缺点典型例题递推基本概念直接或者间接调用自身的算法称为递归算法一般数据n ... [详细]
  • 编程语言是从哪蹦出来的——大型伦理寻根现场
    Hello,我是Alex007,一个热爱计算机编程和硬件设计的小白,为啥是007呢?因为叫Alex的人太多了,再加上每天007的生活,Alex007就诞生了。聊一聊编程到底是啥,怎 ... [详细]
  • 我正在使用数组列表通过构建一个交互式菜单供用户选择来存储来自用户输入的值。到目前为止,我的两个选择是为用户提供向列表输入数据和读取列表的全部内容。到目前为止,我创建的代码由两个类组成。 ... [详细]
  • IDEA实用插件Lombok
    LombokLombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法。通常,我们所定义的对象和b ... [详细]
  • 字符串匹配: BF与KMP算法
    文章目录一.BF算法1.算法思想2.代码实现二.KMP算法1.算法思想概述2.理解基于最长相等前后缀进行匹配3.代码中如何实现next数组5.代码实现6.next数组的优化一.BF ... [详细]
  • 重学数据结构之链表篇
    本文是重学数据结构系列文章的第二篇,本文和大家一起探讨链表的相关知识。重学数据结构之数组篇文章目录链表是怎么样的数据结构链表的特点常见的链表结构单链表双向链表循环链表链表or数组链 ... [详细]
  • (一)javax.mail.Session:Session类代表JavaMail中的一次邮件会话.每个基于JavaMail的应用程序至少有一次会话,也可以产生多次会话.发送邮件之前 ... [详细]
  • Android 自定义控件基础 canvas paint
    1、首先说一下canvas类:ClassOverviewTheCanvasclassholdsthedrawcalls.Todrawsomething,youne ... [详细]
author-avatar
蜀南竹海的天空
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有