概述
众所周知,Android 应用开发完成后,除了使用 Google 官方的混淆外,还需要使用一些第三方的安全软件的加壳处理,比较出名的有腾讯乐固、360 加固和爱加密等。我之前所在的公司,就是使用爱加密进行加壳处理的。
虽然加密后,让软件的安全性更高了,但并不是无懈可击,一些反加固技术和脱壳技术应运而生。今天要说的就是腾讯乐固、360 加固一键脱壳。
工程,经过加固后的 apk,通过 dex2jar 反编译效果是下面这样的:
腾讯乐固加固:
360 加固
可以发现,经过加固处理由,反编译是无法直接获取到源码的,代码的结构如下图所示:
工具
要对 Android 的 apk 文件进行脱壳,需要使用的软件有:
- FDex2
- VirtualXposed
不过,需要说明的是,此技术在 Android9.0 及以上版本是行不通的,并且 VirtualXposed 有软件版本限制。
FDex2
下载地址:
链接: https://pan.baidu.com/s/10ZfD… 提取码: asu1
VirtualXposed
VirtualXposed:无需 root 手机即可使用 Xposed 框架
下载链接:
https://vxposed.com/
脱壳
首先,将 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 的原理。