有test

维度

什么是加壳

再二进制的程序中植入一段代码,在运行时优先获得程序的控制权,做一些额外的工作。

DEX文件格式

DEX文件

他是Android系统的可执行文件,包含应用程序的全部操作指令以及运行时数据

由于dalvik是一种针对嵌入式设备而特殊设计的java虚拟机,所以dex文件与标准的class文件在结构设计上有着本质的区别

当java程序编译成class后,还需要使用dx工具将所有的class文件整合到一个dex文件,目的是其中各个类能够共享数据,在一定程度上降低了冗余,同时也是文件结构更加经凑,实验表明,dex文件是传统jar文件大小的50%左右

dex文件结构

img

文件整体结构说明:

数据名称 解释
dex_header dex文件头部记录整个dex文件的相关属性
string_table 字符串数据索引,记录了每个字符串在数据区的偏移量
type_table 类似数据索引,记录了每个类型的字符串索引
proto_table 原型数据索引,记录了方法声明的字符串,返回类型字符串,参数列表
field_table 字段数据索引,记录了所属类,类型以及方法名
method_table 类方法索引,记录方法所属类名,方法声明以及方法名等信息
class_def 类定义数据索引,记录指定类各类信息,包括接口,超类,类数据偏移量
data_section 数据区,保存了各个类的真是数据

加壳作用

加壳的程序可以有效阻止对程序的反汇编分析,以达到它不可告人的目的。这种技术也常用来保护软件版权,防止被软件破解。

加固流程

1.生成 originalAPK.apk

2.编写 addShell.java 程序(注:虽然这里没有所要操作的 shellDex.dex 文件,但其文件格式都已知晓,不会影响程序的实现)

3.编写 shellAPK.apk,并从中提取 classes.dex 文件改名为 shellDex.dex(注:虽然这里 originalAPK.apk 都没有被加密,也没有与 shellDex.dex 合并,但解密算法和文件格式都已知晓,不会影响程序的实现)

4.运行 addShell.java 程序,生成 classes.dex

5.修改相关文件,如 Manifest 文件,将修改后的文件和第四步生成的classes.dex 文件替换 shellAPK.apk 中相应的文件。最终获得的 shellAPK.apk 就是我们加固后的 originalAPK.apk。

原理

Dex文件整体加固原理如下:

img

Android Dex文件加壳原理

在这个过程中,牵扯到三个角色:

​ 1、加壳程序:加密源程序为解壳数据、组装解壳程序和解壳数据

​ 2、解壳程序:解密解壳数据,并运行时通过DexClassLoader动态加载

​ 3、源程序:需要加壳处理的被保护代码

拼接步骤如下:

• 获取脱壳 DEX (二进制数据)

• 计算新 DEX 的大小(脱壳 DEX 的大小 + 加密 APK 的大小 + 4)

• 依次拼接脱壳 DEX、加密 APK、加密 APK 的大小,得到新 DEX

• 修改新 DEX 头的三个字段

• 输出新 DEX

根据解壳数据在解壳程序DEX文件中的不同分布,本文将提出两种Android Dex加壳的实现方案。

(一)解壳数据位于解壳程序文件尾部

该种方式简单实用,合并后的DEX文件结构如下。

img

加壳程序工作流程:

1、加密源程序APK文件为解壳数据

2、把解壳数据写入解壳程序Dex文件末尾,并在文件尾部添加解壳数据的大小。

3、修改解壳程序DEX头中checksum、signature 和file_size头信息。

4、修改源程序AndroidMainfest.xml文件并覆盖解壳程序AndroidMainfest.xml文件。

解壳DEX程序工作流程:

1、读取DEX文件末尾数据获取借壳数据长度。

2、从DEX文件读取解壳数据,解密解壳数据。以文件形式保存解密数据到a.APK文件

3、通过DexClassLoader动态加载a.apk。

(二)解壳数据位于解壳程序文件头

该种方式相对比较复杂, 合并后DEX文件结构如下:

img

加壳程序工作流程:

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文件。

解壳DEX程序工作流程:
1、从0x70处读取解壳数据长度。

2、从DEX文件读取解壳数据,解密解壳数据。以文件形式保存解密数据到a.APK

3、通过DexClassLoader动态加载a.APK。

加壳及脱壳代码实现

本程序基于Android2.3代码实现,因为牵扯到系统代码的反射修改,本程序不保证在其它android版本正常工作,可以根据实现原理,自行实现对其它Android版本的兼容性开发。

1、 加壳程序流程及代码实现

​ 1、加密源程序APK为解壳数据

​ 2、把解壳数据写入解壳程序DEX文件末尾,并在文件尾部添加解壳数据的大小。

​ 3、修改解壳程序DEX头中checksum、signature 和file_size头信息。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package com.android.dexshell;
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;

public class DexShellTool {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
File payloadSrcFile = new File("g:/payload.apk");
File unShellDexFile = new File("g:/unshell.dex");
byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));
byte[] unShellDexArray = readFileBytes(unShellDexFile);
int payloadLen = payloadArray.length;
int unShellDexLen = unShellDexArray.length;
int totalLen = payloadLen + unShellDexLen +4;
byte[] newdex = new byte[totalLen];
//添加解壳代码
System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);
//添加加密后的解壳数据
System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);
//添加解壳数据长度
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";
File file = new File(str);
if (!file.exists()) {
file.createNewFile();
}

FileOutputStream localFileOutputStream = new FileOutputStream(str);
localFileOutputStream.write(newdex);
localFileOutputStream.flush();
localFileOutputStream.close();


} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

//直接返回数据,读者可以添加自己加密方法
private static byte[] encrpt(byte[] srcdata){
return srcdata;
}


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]));
}
System.arraycopy(recs, 0, dexBytes, 8, 4);
System.out.println(Long.toHexString(value));
System.out.println();
}


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;
}


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();
System.arraycopy(newdt, 0, dexBytes, 12, 20);
String hexstr = "";
for (int i = 0; i < newdt.length; i++) {
hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16)
.substring(1);
}
System.out.println(hexstr);
}


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]));
}
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 localByteArrayOutputStream.toByteArray();
}
}
}


}

解壳待补充

原因:暂时还没用到,看得懂

案例尝试

总体流程:

对源 APK 进行加密、拼接到脱壳 DEX,得到新 DEX。

一、创建源APK

代码:

1
2
3
4
5
6
7
8
9
public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("hello Ckcah", "I am bosch");
}
}

1.新建工程,默认模板即可

因为暂时还没深入到,所以。。。。

二、加密和拼接

三、脱壳DEX

四、新APK

参考

Android APK加壳技术方案【1】

Android APK加壳技术方案【2】

Android安全专项-Apk加固

apk加壳去壳

Android加固

Android应用加固原理