- Epic 使用
- gradle 中引入依赖: com.github.tiann:epic:0.12
- 使用方法
class ThreadMethodHook extends XC_MethodHook{
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Thread t = (Thread) param.thisObject;
Log.i(TAG, "thread:" + t + ", started..");
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Thread t = (Thread) param.thisObject;
Log.i(TAG, "thread:" + t + ", exit..");
}
}
DexposedBridge.hookAllConstructors(Thread.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Thread thread = (Thread) param.thisObject;
Class<?> clazz = thread.getClass();
if (clazz != Thread.class) {
Log.d(TAG, "found class extend Thread:" + clazz);
DexposedBridge.findAndHookMethod(clazz, "run", new ThreadMethodHook());
}
Log.d(TAG, "Thread: " + thread.getName() + " class:" + thread.getClass() + " is created.");
}
});
DexposedBridge.hookAllMethod(Thread.class, "run", new ThreadMethodHook());
3. 注意事项
不支持armeabi架构,如果项目仅支持armeabi架构,需要从armeabi-v7进行so库的复制。
- 简介
基于dynamic dispatch的dynamic callee-side rewriting来实现hook。
其实就是修改entrypoint 所指向的代码.
1)保证要Hook的method完成compile,也就是运行时要执行其compiled_code。
2)根据要Hook的method对应的art::mirror::ArtMethod找到compiled_code入口点。
3)在compiled_code的开始位置放置一段很短的跳转代码,称为“一段跳板”,作用是跳转到二段跳板。之所以弄一个一段跳板,是怕二段跳板太长,原方法的compiled_code区域放不下。
4)二段跳板会将一些必要的参数打包,调用Java-Bridge方法,并将打包在一起的参数,通过r3传递给Java-Bridge。
5)Java-Bridge方法取出传递进来的参数,然后根据r1、r2、r3以及sp(以Thumb2为例,除了r0~r3,剩余的参数会通过sp传递),构造出原方法的参数,最后调用DexposedBridge.handleHookedArtMethod。
6)由DexposedBridge.handleHookedArtMethod调用beforeHookedMethod、原方法和afterHookedMethod。
DexposedBridge.hookAllMethods入口跟踪:
public static Set<XC_MethodHook.Unhook> hookAllMethods(Class<?> hookClass, String methodName, XC_MethodHook callback) {
Set<XC_MethodHook.Unhook> unhooks = new HashSet<XC_MethodHook.Unhook>();
for (Member method : hookClass.getDeclaredMethods())
if (method.getName().equals(methodName))
unhooks.add(hookMethod(method, callback));
return unhooks;
}
对传入的类进行遍历,然后匹配和传入的方法相同的方法mothod,取callbackhe和mothed调用hookMethod进行hook的操作.
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
if (!(hookMethod instanceof Method) && !(hookMethod instanceof Constructor<?>)) {
throw new IllegalArgumentException("only methods and constructors can be hooked");
}
boolean newMethod = false;
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
synchronized (hookedMethodCallbacks) {
callbacks = hookedMethodCallbacks.get(hookMethod);
if (callbacks == null) {
callbacks = new CopyOnWriteSortedSet<XC_MethodHook>();
hookedMethodCallbacks.put(hookMethod, callbacks);
newMethod = true;
}
}
Logger.w(TAG, "hook: " + hookMethod + ", newMethod ? " + newMethod);
callbacks.add(callback);
if (newMethod) {
if (Runtime.isArt()) {
if (hookMethod instanceof Method) {
Epic.hookMethod(((Method) hookMethod));
} else {
Epic.hookMethod(((Constructor) hookMethod));
}
} else {
...
}
}
return callback.new Unhook(hookMethod);
}
已经hook过得方法和回调会被存储在hookedMethodCallbacks中,如果方法已经被hook过的,会直接从map中取出来.Epic.hookMethod就是hook方法的入口.
public static boolean hookMethod(Method origin) { ArtMethod artOrigin = ArtMethod.of(origin); return hookMethod(artOrigin); } private static boolean hookMethod(ArtMethod artOrigin) { MethodInfo methodInfo = new MethodInfo(); methodInfo.isStatic = Modifier.isStatic(artOrigin.getModifiers()); final Class<?>[] parameterTypes = artOrigin.getParameterTypes(); if (parameterTypes != null) { methodInfo.paramNumber = parameterTypes.length; methodInfo.paramTypes = parameterTypes; } else { methodInfo.paramNumber = 0; methodInfo.paramTypes = new Class<?>[0]; } methodInfo.returnType = artOrigin.getReturnType(); methodInfo.method = artOrigin; // 存储方法的地址和新的methodInfo originSigs.put(artOrigin.getAddress(), methodInfo); // 取消权限检查 if (!artOrigin.isAccessible()) { artOrigin.setAccessible(true); } // 静态方法主动调用 artOrigin.ensureResolved(); long originEntry = artOrigin.getEntryPointFromQuickCompiledCode(); if (originEntry == ArtMethod.getQuickToInterpreterBridge()) { Logger.i(TAG, "this method is not compiled, compile it now. current entry: 0x" + Long.toHexString(originEntry)); // 未编译主动调用进行编译 boolean ret = artOrigin.compile(); if (ret) { originEntry = artOrigin.getEntryPointFromQuickCompiledCode(); Logger.i(TAG, "compile method success, new entry: 0x" + Long.toHexString(originEntry)); } else { Logger.e(TAG, "compile method failed..."); return false; // return hookInterpreterBridge(artOrigin); } } // 备份 ArtMethod backupMethod = artOrigin.backup(); Logger.i(TAG, "backup method address:" + Debug.addrHex(backupMethod.getAddress())); Logger.i(TAG, "backup method entry :" + Debug.addrHex(backupMethod.getEntryPointFromQuickCompiledCode())); ArtMethod backupList = getBackMethod(artOrigin); if (backupList == null) { setBackMethod(artOrigin, backupMethod); } final long key = originEntry; final EntryLock lock = EntryLock.obtain(originEntry); //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (lock) { if (!scripts.containsKey(key)) { scripts.put(key, new Trampoline(ShellCode, originEntry)); } // dynamic callee-side rewriting 实现hook Trampoline trampoline = scripts.get(key); boolean ret = trampoline.install(artOrigin); // Logger.d(TAG, "hook Method result:" + ret); return ret; } }
安装跳板,实现hook
public boolean install(ArtMethod originMethod){ boolean modified = segments.add(originMethod); if (!modified) { // Already hooked, ignore Logger.d(TAG, originMethod + " is already hooked, return."); return true; } // 创建 Trampoline byte[] page = create(); // Trampoline 写到一块新的内存中 EpicNative.put(page, getTrampolineAddress()); int quickCompiledCodeSize = Epic.getQuickCompiledCodeSize(originMethod); int sizeOfDirectJump = shellCode.sizeOfDirectJump(); if (quickCompiledCodeSize < sizeOfDirectJump) { // complied_code 太短,无法安装一段跳板,complied_code 入口改为二段跳板 Logger.w(TAG, originMethod.toGenericString() + " quickCompiledCodeSize: " + quickCompiledCodeSize); originMethod.setEntryPointFromQuickCompiledCode(getTrampolinePc()); return true; } // 这里是绝对不能改EntryPoint的,碰到GC就挂(GC暂停线程的时候,遍历所有线程堆栈,如果被hook的方法在堆栈上,那就GG) // 安装一段跳板 return activate(); }
private boolean activate() {
long pc = getTrampolinePc();
Logger.d(TAG, "Writing direct jump entry " + Debug.addrHex(pc) + " to origin entry: 0x" + Debug.addrHex(jumpToAddress));
synchronized (Trampoline.class) {
return EpicNative.activateNative(jumpToAddress, pc, shellCode.sizeOfDirectJump(),
shellCode.sizeOfBridgeJump(), shellCode.createDirectJump(pc));
}
}
1)创建Trampoline(包括“二段跳板”BridgeJump,和CallOrigin)
2)创建和安装“一段跳板”,完成Hook。