Commit 33c76c49 authored by Administrator's avatar Administrator

脱壳组件功能基本完善

parent c41ec9fe
package com.virjar.ratel.api;
import java.io.File;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.List;
......@@ -10,14 +11,24 @@ import java.util.List;
* 不支持vmp、不支持主动调用(可以在业务层模拟主动调用)
*/
public interface RatelUnpack {
/**
* 获取当前的工作目录,在enableUnPack调用之后才会有值
*
* @return 脱壳机工作目录
*/
File getWorkDir();
/**
* 开启脱壳机,ratel框架将会影响虚拟机代码执行流程。这可能导致框架不稳定,已经影响app执行性能<br>
* 一般情况不建议随时开启脱壳机<br>
* 请注意,脱壳机开启需要在app运行前执行,否则错过dump时间
*
* @param workDir 需要指定一个工作目录,让脱壳机dump相关加密的指令.参数可以为空,为空系统自动分配
* @param dumpMethod 是否需要dump指令,大部分情况下dex整体dump就可以,此时不存在dex修复过程,相对来说性能更好
*/
void enableUnPack(File workDir);
void enableUnPack(File workDir, boolean dumpMethod);
/**
* 根据一个className搜索dex,在存在热修复等场景下可能有多个dex,每个dex使用字节数组传递二进制内容
......@@ -34,5 +45,5 @@ public interface RatelUnpack {
* @param method 一个特定的方法,可以通过各种hook手段、反射手段获取到
* @return 内存数据
*/
byte[] methodDex(Method method);
byte[] methodDex(Member method);
}
package com.virjar.ratel.api;
import android.app.Activity;
import android.util.Log;
import com.virjar.ratel.api.inspect.ClassLoadMonitor;
import com.virjar.ratel.api.rposed.RC_MethodHook;
import com.virjar.ratel.api.rposed.RposedHelpers;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import external.org.apache.commons.io.IOUtils;
import external.org.apache.commons.lang3.NumberUtils;
import external.org.apache.commons.lang3.StringUtils;
/**
* 脱壳工具类封装
*
* @author virjar
* @since 1.3.5
*/
public class UnPackerToolKit {
private static final String TAG_UNPACK = "unpack";
/**
* 开启脱壳机,ratel框架将会影响虚拟机代码执行流程。这可能导致框架不稳定,已经影响app执行性能<br>
* 一般情况不建议随时开启脱壳机<br>
* 请注意,脱壳机开启需要在app运行前执行,否则错过dump时间
*
* @param workDir 需要指定一个工作目录,让脱壳机dump相关加密的指令.参数可以为空,为空系统自动分配
* @param dumpMethod 是否需要dump指令,大部分情况下dex整体dump就可以,此时不存在dex修复过程,相对来说性能更好
*/
public void enableUnPack(File workDir, boolean dumpMethod) {
RatelToolKit.ratelUnpack.enableUnPack(workDir, dumpMethod);
}
public void enableUnPack(File workDir) {
RatelToolKit.ratelUnpack.enableUnPack(workDir, false);
}
public void enableUnPack() {
RatelToolKit.ratelUnpack.enableUnPack(new File(RatelToolKit.sContext.getFilesDir(), "ratel_unpack"), false);
}
/**
* 自动开启脱壳组件,他在
*/
public static void autoEnable() {
autoEnable(false);
}
/**
* 自动开启脱壳组件
*
* @param dumpMethod 标志进行整体dump还是方法粒度dump。方法粒度dump可能引发更多不稳定性
*/
public static void autoEnable(boolean dumpMethod) {
if (RatelToolKit.processName.equals(RatelToolKit.packageName)) {
RposedHelpers.findAndHookMethod(Activity.class, "onResume", new RC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
RatelToolKit.ratelUnpack.enableUnPack(
new File(RatelToolKit.sContext.getFilesDir(), "ratel_unpack"),
dumpMethod
);
}
});
}
}
/**
* 确保某个特定的class被脱壳。
*
* @param className 一个特定的class
*/
public static void ensureClassDump(String className) {
if (StringUtils.isBlank(className)) {
Log.e(TAG_UNPACK, "empty class:" + className);
return;
}
ClassLoadMonitor.addClassLoadMonitor(className, clazz -> {
//make sure unpack component enable
RatelToolKit.ratelUnpack.enableUnPack(
new File(RatelToolKit.sContext.getFilesDir(), "ratel_unpack"),
false
);
// force set dex image valid
Method[] declaredMethods = clazz.getDeclaredMethods();
if (declaredMethods.length > 0) {
RatelToolKit.ratelUnpack.methodDex(declaredMethods[0]);
return;
}
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
if (declaredConstructors.length > 0) {
RatelToolKit.ratelUnpack.methodDex(declaredConstructors[0]);
return;
}
Log.w(TAG_UNPACK, "no available method found for class:" + className);
});
}
/**
* 直接将所有的dex组装到一个apk文件中,这样可以方便使用各种自动化的反编译工具打开
*
* @return 一个apk文件,该文件包括代码和资源,但是没有签名,无法直接运行
*/
public File constructUnpackedApk() {
try {
return constructUnpackedApkInternal();
} catch (IOException e) {
Log.w(TAG_UNPACK, "construct unpacked apk failed", e);
return null;
}
}
public static final Pattern classesIndexPattern = Pattern.compile("classes(\\d+)\\.dex");
private File constructUnpackedApkInternal() throws IOException {
File workDir = RatelToolKit.ratelUnpack.getWorkDir();
if (workDir == null || !workDir.exists()) {
return null;
}
List<byte[]> dumpedDex = RatelToolKit.ratelUnpack.findDumpedDex(null);
File apkFile = new File(RatelToolKit.sContext.getPackageCodePath());
int maxIndex = 0;
File target = new File(workDir, "unpacked.apk");
try (FileOutputStream fileOutputStream = new FileOutputStream(target)) {
try (ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream)) {
try (ZipFile zipFile = new ZipFile(apkFile)) {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
if (zipEntry.getName().startsWith("META-INF/")) {
// 去掉签名内容
continue;
}
zipOutputStream.putNextEntry(zipEntry);
IOUtils.copy(zipFile.getInputStream(zipEntry), zipOutputStream);
Matcher matcher = classesIndexPattern.matcher(zipEntry.getName());
if (matcher.matches()) {
int nowIndex = NumberUtils.toInt(matcher.group(1));
if (nowIndex > maxIndex) {
maxIndex = nowIndex;
}
}
}
// append dex
for (byte[] dexData : dumpedDex) {
zipOutputStream.putNextEntry(new ZipEntry("classes" + maxIndex + ".dex"));
zipOutputStream.write(dexData);
maxIndex++;
}
zipOutputStream.flush();
}
}
}
return target;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment