轉https://zhuanlan.zhihu.com/p/70894166
概述
眾所周知,Android應用開發完成后,除了使用Google官方的混淆外,還需要使用一些第三方的安全軟件的加殼處理,比較出名的有騰訊樂固、360加固和愛加密等。我之前所在的公司,就是使用愛加密進行加殼處理的。
雖然加密后,讓軟件的安全性更高了,但並不是無懈可擊,一些反加固技術和脫殼技術應運而生。今天要說的就是騰訊樂固、360加固一鍵脫殼。
工程,經過加固后的apk,通過dex2jar反編譯效果是下面這樣的:
騰訊樂固加固:
360加固
可以發現,經過加固處理由,反編譯是無法直接獲取到源碼的,代碼的結構如下圖所示:
工具
要對Android的apk文件進行脫殼,需要使用的軟件有:
- FDex2
- VirtualXposed 不過,需要說明的是,此技術在Android9.0及以上版本是行不通的,並且VirtualXposed有軟件版本限制。
FDex2
下載地址: 鏈接: https://pan.baidu.com/s/10ZfD2MSfukuLdxvUZIAyjA 提取碼: asu1
備用下載:https://089u.com/dir/3843664-40606878-902c5f
VirtualXposed
VirtualXposed:無需root手機即可使用Xposed框架 下載鏈接(國內需要梯子): https://vxposed.com/
備用下載地址:https://github.com/android-hacker/VirtualXposed/releases/tag/0.18.2
脫殼
首先,將VirtualXposed、FDex2和需要脫殼的應用都安裝到手機上。然后,啟動VirtualXposed,並在VirtualXposed中安裝FDex2。

然后,在VirtualXposed中選擇模塊管理激活FDex2。

在VirtualXposed中安裝要脫殼的應用,具體和上面的步驟一樣。然后,啟動VirtualXposed中的FDex2,並配置要脫殼的應用。
在VirtualXposed中運行要脫殼的應用,脫殼后的dex文件如下圖:
然后,使用adb pull命令將脫殼后的dex文件導出到電腦。
adb pull /data/user/0/iv.va.exposed/virtual/data/user/0/{packageName}
最后,再通過dex2jar對 脫殼的dex進行反編譯。
FDex2核心代碼
package com.ppma.xposed;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XSharedPreferences;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class MainHook implements IXposedHookLoadPackage {
XSharedPreferences xsp;
Class Dex;
Method Dex_getBytes;
Method getDex;
String packagename;
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
xsp = new XSharedPreferences("com.ppma.appinfo", "User");
xsp.makeWorldReadable();
xsp.reload();
initRefect();
packagename = xsp.getString("packagename", null);
XposedBridge.log("設定包名:"+packagename);
if ((!lpparam.packageName.equals(packagename))||packagename==null) {
XposedBridge.log("當前程序包名與設定不一致或者包名為空");
return;
}
XposedBridge.log("目標包名:"+lpparam.packageName);
String str = "java.lang.ClassLoader";
String str2 = "loadClass";
XposedHelpers.findAndHookMethod(str, lpparam.classLoader, str2, String.class, Boolean.TYPE, new XC_MethodHook() {
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Class cls = (Class) param.getResult();
if (cls == null) {
//XposedBridge.log("cls == null");
return;
}
String name = cls.getName();
XposedBridge.log("當前類名:" + name);
byte[] bArr = (byte[]) Dex_getBytes.invoke(getDex.invoke(cls, new Object[0]), new Object[0]);
if (bArr == null) {
XposedBridge.log("數據為空:返回");
return;
}
XposedBridge.log("開始寫數據");
String dex_path = "/data/data/" + packagename + "/" + packagename + "_" + bArr.length + ".dex";
XposedBridge.log(dex_path);
File file = new File(dex_path);
if (file.exists()) return;
writeByte(bArr, file.getAbsolutePath());
}
} );
}
public void initRefect() {
try {
Dex = Class.forName("com.android.dex.Dex");
Dex_getBytes = Dex.getDeclaredMethod("getBytes", new Class[0]);
getDex = Class.forName("java.lang.Class").getDeclaredMethod("getDex", new Class[0]);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public void writeByte(byte[] bArr, String str) {
try {
OutputStream outputStream = new FileOutputStream(str);
outputStream.write(bArr);
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
XposedBridge.log("文件寫出失敗");
}
}
}
通過Hook ClassLoader的loadClass方法,反射調用getDex方法取得Dex(com.android.dex.Dex類對象),再將里面的dex寫出,這就是Hook的原理。