APK 文件加固

本文最后更新于:1 年前

加固原理

源码:
1、ProtectApp
2、ShellAddProject


APK 文件打包流程


创建 APK 文件、壳

1
gradlew assembleDebug


加壳流程

加密 DEX 文件

将 APK 文件解压缩

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
public static void unZip(File srcFile, File dstDir) {
try {
dstDir.delete();
ZipFile zipFile = new ZipFile(srcFile);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
String name = zipEntry.getName();
if (name.equals("META-INF/CERT.RSA") || name.equals("META-INF/CERT.SF")
|| name.equals("META-INF/MANIFEST.MF")) {
continue;
}

if (!zipEntry.isDirectory()) {
File file = new File(dstDir, name);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
FileOutputStream fos = new FileOutputStream(file);
InputStream is = zipFile.getInputStream(zipEntry);
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
is.close();
fos.close();
}
}
zipFile.close();
} catch (Exception e) {
e.printStackTrace();
}
}

将解压缩出来的 DEX 文件加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
File[] DEXFiles = dstDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String s) {
return s.endsWith(".dex");
}
});
for (File file : DEXFiles) {
// 读数据
byte[] buffer = Utils.getBytes(file);
// 加密
byte[] encryptBytes = encrypt(buffer);
// 写数据, 替换原来的数据
FileOutputStream fos = new FileOutputStream(file);
fos.write(encryptBytes);
fos.flush();
fos.close();
}
1
2
3
4
5
6
7
8
9
10
public static byte[] encrypt(byte[] content) {
try {
return encryptCipher.doFinal(content);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return null;
}

将 DEX 文件重命名

classes.dex -> classes_.dex

获取壳的 DEX 文件

将 AAR 文件解压缩

根据解压缩出来的 JAR 文件使用 dx 命令生成 DEX 文件

1
2
3
4
5
6
7
8
9
10
11
12
File[] files = dir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String s) {
return s.equals("classes.jar");
}
});
if (files == null || files.length <= 0) {
throw new RuntimeException("the aar is invalidate");
}
File classesJARFile = files[0];
File classesDEXFile = new File(classesJARFile.getParentFile(), "classes.dex");
dxCommand(classesDEXFile, classesJARFile);
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
public static void dxCommand(File DEXFile, File JARFile) throws IOException, InterruptedException {
Runtime runtime = Runtime.getRuntime();
Process process = runtime
.exec("cmd.exe /C dx --dex --output=" + DEXFile.getAbsolutePath() + " " + JARFile.getAbsolutePath());
System.out.println("start dx...");

BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
try {
process.waitFor();
System.out.println("waiting...");
} catch (InterruptedException e) {
e.printStackTrace();
throw e;
}

if (process.exitValue() != 0) {
InputStream inputStream = process.getErrorStream();
int len;
byte[] buffer = new byte[2048];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while ((len = inputStream.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
System.out.println(new String(bos.toByteArray(), "GBK"));
throw new RuntimeException("dx failed!");
}
System.out.println("finish dx!");
process.destroy();
}

合成新的 APK 文件

打包

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
public static void zip(File srcDir, File dstFile) throws Exception {
dstFile.delete();
// 对输出文件做 CRC32 校验
CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream(dstFile), new CRC32());

ZipOutputStream zos = new ZipOutputStream(cos);
compress(srcDir, zos, "");
zos.flush();
zos.close();
}

private static void compress(File srcFile, ZipOutputStream zos, String basePath) throws Exception {
if (srcFile.isDirectory()) {
compressDir(srcFile, zos, basePath);
} else {
compressFile(srcFile, zos, basePath);
}
}

private static void compressDir(File dir, ZipOutputStream zos, String basePath) throws Exception {
File[] files = dir.listFiles();
if (files.length < 1) {
ZipEntry entry = new ZipEntry(basePath + dir.getName() + "/");
zos.putNextEntry(entry);
zos.closeEntry();
}
for (File file : files) {
compress(file, zos, basePath + dir.getName() + "/");
}
}

private static void compressFile(File file, ZipOutputStream zos, String basePath) throws Exception {
String name = basePath + file.getName();
String[] names = name.split("/");

StringBuilder builder = new StringBuilder();
if (names.length > 1) {
for (int i = 1; i < names.length; i++) {
builder.append("/");
builder.append(names[i]);
}
} else {
builder.append("/");
}

ZipEntry entry = new ZipEntry(builder.toString().substring(1));
zos.putNextEntry(entry);
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
int count;
byte data[] = new byte[1024];
while ((count = bis.read(data, 0, 1024)) != -1) {
zos.write(data, 0, count);
}
bis.close();
zos.closeEntry();
}

使用 jarsigner 命令签名

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
public static void signature(File unsignedAPKFile, File signedAPKFile) throws InterruptedException, IOException {
// .keystore
// String cmd[] = { "cmd.exe", "/C ", "jarsigner", "-sigalg", "MD5withRSA", "-digestalg", "SHA1", "-keystore",
// "source/apk/debug.keystore", "-storepass", "android", "-keypass",
// "android", "-signedjar", signedApk.getAbsolutePath(), unsignedApk.getAbsolutePath(),
// "android" };
// .jks
String cmd[] = { "cmd.exe", "/C ", "jarsigner", "-verbose", "-keystore", "source/apk/debug.jks", "-storepass",
"aaaaaa", "-keypass", "aaaaaa", "-signedjar", signedAPKFile.getAbsolutePath(),
unsignedAPKFile.getAbsolutePath(), "aaaaaa" };
Process process = Runtime.getRuntime().exec(cmd);
System.out.println("start sign...");

BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
try {
process.waitFor();
System.out.println("waiting...");
} catch (InterruptedException e) {
e.printStackTrace();
throw e;
}

if (process.exitValue() != 0) {
InputStream inputStream = process.getErrorStream();
int len;
byte[] buffer = new byte[2048];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while ((len = inputStream.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
System.out.println(new String(bos.toByteArray(), "GBK"));
throw new RuntimeException("sign failed!");
}
System.out.println("finish sign!");
process.destroy();
}


脱壳流程

将 APK 文件解压缩

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
public static void unZip(File srcFile, File dstDir) {
try {
dstDir.delete();
ZipFile zipFile = new ZipFile(srcFile);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
String name = zipEntry.getName();
if (name.equals("META-INF/CERT.RSA") || name.equals("META-INF/CERT.SF")
|| name.equals("META-INF/MANIFEST.MF")) {
continue;
}

if (!zipEntry.isDirectory()) {
File file = new File(dstDir, name);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
FileOutputStream fos = new FileOutputStream(file);
InputStream is = zipFile.getInputStream(zipEntry);
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
is.close();
fos.close();
}
}
zipFile.close();
} catch (Exception e) {
e.printStackTrace();
}
}

将解压缩出来的 DEX 文件解密

1
2
3
4
5
6
7
8
9
10
11
12
try {
decryptCipher = Cipher.getInstance(ALGORITHM);
byte[] keyStr = password.getBytes();
SecretKeySpec key = new SecretKeySpec(keyStr, "AES");
decryptCipher.init(Cipher.DECRYPT_MODE, key);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
1
2
3
4
5
6
7
8
9
10
public static byte[] decrypt(byte[] content) {
try {
return decryptCipher.doFinal(content);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return null;
}

加载解密后的 DEX 文件(插件化原理)


验证



APK 文件加固
https://weichao.io/a391c2121958/
作者
魏超
发布于
2020年8月11日
更新于
2022年12月4日
许可协议