- 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。