apk加固的目的其實就是對app的核心代碼做防護工作,避免被其他人反編譯;
廢話不多說了,直接開始!
加殼
首先,要想對apk加固的話,需要以下幾個步驟:
- 解壓原apk
- 對原apk裏面的classes.dex 進行AES加密
- 重命名classes.dex爲classes_.dex
- 對殼(aar)文件解壓
- 用dx工具將jar 轉成classes.dex文件
- 將殼dex文件複製到原apk的解壓目錄下
- 打包壓縮原apk爲xxx.apk文件
- 對打包後的apk進行簽名
解壓原apk並加密
public static File encryptAPKFile(File srcAPKfile, File dstApkFile) throws Exception {
if (srcAPKfile == null) {
System.out.println("encryptAPKFile :srcAPKfile null");
return null;
}
// File disFile = new File(srcAPKfile.getAbsolutePath() + "unzip");
// Zip.unZip(srcAPKfile, disFile);
Zip.unZip(srcAPKfile, dstApkFile);
//������е�dex ����Ҫ����ְ������
File[] dexFiles = dstApkFile.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String s) {
return s.endsWith(".dex");
}
});
File mainDexFile = null;
byte[] mainDexData = null;
for (File dexFile: dexFiles) {
//������
byte[] buffer = Utils.getBytes(dexFile);
//����
byte[] encryptBytes = AES.encrypt(buffer);
if (dexFile.getName().endsWith("classes.dex")) {
mainDexData = encryptBytes;
mainDexFile = dexFile;
}
//д���� �滻ԭ��������
FileOutputStream fos = new FileOutputStream(dexFile);
fos.write(encryptBytes);
fos.flush();
fos.close();
}
return mainDexFile;
}
重命名dex文件
if(newApkFile.isDirectory()){
for(File newApkDirFile: Objects.requireNonNull(newApkFile.listFiles())){
if(newApkDirFile.isFile()){
if(newApkDirFile.getName().endsWith(".dex")){
String dexName = newApkDirFile.getName();
int cursor = dexName.indexOf(".dex");
String reName = newApkDirFile.getParent() + File.separator + dexName.substring(0, cursor) + "_" + ".dex";
System.out.println("reName value:" + reName);
newApkDirFile.renameTo(new File(reName));
}
}
}
}
對殼文件操作
File aarFile = new File(AAR_FILE_DIR + File.separator + "mylibrary-debug.aar");
if(aarFile.exists()){
File dexFile = Dx.jar2Dex(aarFile);
if(!dexFile.exists()){
System.out.println("dex file no exit xxxxxxxxxxxxxx");
return;
}
File newDexFile = new File(apkTempFileDir.getPath() + File.separator + "classes.dex");
if(!newDexFile.exists()){
newDexFile.createNewFile();
}
FileOutputStream dexStream = new FileOutputStream(newDexFile);
byte[] aarBytes = Utils.getBytes(dexFile);
dexStream.write(aarBytes);
dexStream.flush();
dexStream.close();
打包壓縮成apk文件
File unsignApkFile = new File(PROPATH + File.separator + "apk-unsign.apk");
if(!unsignApkFile.exists()){
unsignApkFile.createNewFile();
}
//將apk/temp目錄下的文件進行打包壓縮
Zip.zip(apkTempFileDir, unsignApkFile);
簽名
//簽名
Signature.signature(unsignApkFile, new File(PROPATH + File.separator + "apk-signed.apk"));
脫殼運行
所謂的脫殼其實就是將在apk安裝運行的時候先運行殼文件中的dex, 然後在殼文件中的Application裏面做解密處理,解密完了之後將解密後的原dex文件用BaseDexClassLoader加載到內存中;加載原dex文件的原理是仿照的tinker 框架來做的,這裏只適配的android 19版本的加載方法,其他的可參照tinker 的方法或者源碼進行適配, 主要原理其實就是通過反射BaseDexClassLoader中的DexPathList變量實現的, 具體流程如下:
從圖中可以看到它最終是將要加載dex文件設置到了pathList對象裏面的dexElements數組變量裏面,這個dexElements就是虛擬機加載dex文件的
解壓原apk, 解密原dex文件
AES.init(getPassword());
File apkFile = new File(getApplicationInfo().sourceDir);
//data/data/包名/files/fake_apk/
File unZipFile = getDir("fake_apk", MODE_PRIVATE);
File app = new File(unZipFile, "app");
if (!app.exists()) {
Zip.unZip(apkFile, app);
File[] files = app.listFiles();
for (File file : files) {
String name = file.getName();
if (name.equals("classes.dex")) {
} else if (name.endsWith(".dex")) {
try {
byte[] bytes = getBytes(file);
FileOutputStream fos = new FileOutputStream(file);
byte[] decrypt = AES.decrypt(bytes);
// fos.write(bytes);
fos.write(decrypt);
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
List list = new ArrayList<>();
Log.d("FAKE", Arrays.toString(app.listFiles()));
for (File file : app.listFiles()) {
if (file.getName().endsWith(".dex")) {
list.add(file);
}
}
加載原dex文件
private static final class V19 {
private V19() {
}
private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
File optimizedDirectory) throws IllegalArgumentException,
IllegalAccessException, NoSuchFieldException, InvocationTargetException,
NoSuchMethodException {
Field pathListField = findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList suppressedExceptions = new ArrayList();
Log.d(TAG, "Build.VERSION.SDK_INT " + Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT >= 23) {
expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList, new
ArrayList(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
} else {
expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new
ArrayList(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
}
if (suppressedExceptions.size() > 0) {
Iterator suppressedExceptionsField = suppressedExceptions.iterator();
while (suppressedExceptionsField.hasNext()) {
IOException dexElementsSuppressedExceptions = (IOException)
suppressedExceptionsField.next();
Log.w("MultiDex", "Exception in makeDexElement",
dexElementsSuppressedExceptions);
}
Field suppressedExceptionsField1 = findField(loader,
"dexElementsSuppressedExceptions");
IOException[] dexElementsSuppressedExceptions1 = (IOException[]) ((IOException[])
suppressedExceptionsField1.get(loader));
if (dexElementsSuppressedExceptions1 == null) {
dexElementsSuppressedExceptions1 = (IOException[]) suppressedExceptions
.toArray(new IOException[suppressedExceptions.size()]);
} else {
IOException[] combined = new IOException[suppressedExceptions.size() +
dexElementsSuppressedExceptions1.length];
suppressedExceptions.toArray(combined);
System.arraycopy(dexElementsSuppressedExceptions1, 0, combined,
suppressedExceptions.size(), dexElementsSuppressedExceptions1.length);
dexElementsSuppressedExceptions1 = combined;
}
suppressedExceptionsField1.set(loader, dexElementsSuppressedExceptions1);
}
}
private static Object[] makeDexElements(Object dexPathList,
ArrayList<File> files, File
optimizedDirectory,
ArrayList<IOException> suppressedExceptions) throws
IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Method makeDexElements = findMethod(dexPathList, "makeDexElements", new
Class[]{ArrayList.class, File.class, ArrayList.class});
return ((Object[]) makeDexElements.invoke(dexPathList, new Object[]{files,
optimizedDirectory, suppressedExceptions}));
}
}