Commit 4852ccf9 authored by swift_gan's avatar swift_gan

done new hooker maker

parent 0d617246
...@@ -102,6 +102,8 @@ public class SandHook { ...@@ -102,6 +102,8 @@ public class SandHook {
mode = hookModeCallBack.hookMode(target); mode = hookModeCallBack.hookMode(target);
} }
globalHookEntityMap.put(entity.target, entity);
int res; int res;
if (mode != HookMode.AUTO) { if (mode != HookMode.AUTO) {
res = hookMethod(target, hook, backup, mode); res = hookMethod(target, hook, backup, mode);
...@@ -121,11 +123,10 @@ public class SandHook { ...@@ -121,11 +123,10 @@ public class SandHook {
} }
if (res < 0) { if (res < 0) {
globalHookEntityMap.remove(entity.target);
throw new HookErrorException("hook method <" + entity.target.toString() + "> error in native!"); throw new HookErrorException("hook method <" + entity.target.toString() + "> error in native!");
} }
globalHookEntityMap.put(entity.target, entity);
if (entity.backup != null) { if (entity.backup != null) {
globalBackupMap.put(entity.backup, entity); globalBackupMap.put(entity.backup, entity);
} }
......
...@@ -27,6 +27,7 @@ public class XposedCompat { ...@@ -27,6 +27,7 @@ public class XposedCompat {
//try to use internal stub hooker & backup method to speed up hook //try to use internal stub hooker & backup method to speed up hook
public static volatile boolean useInternalStub = true; public static volatile boolean useInternalStub = true;
public static volatile boolean useNewDexMaker = true;
public static volatile boolean retryWhenCallOriginError = false; public static volatile boolean retryWhenCallOriginError = false;
private static ClassLoader sandHookXposedClassLoader; private static ClassLoader sandHookXposedClassLoader;
......
...@@ -322,6 +322,77 @@ public class HookStubManager { ...@@ -322,6 +322,77 @@ public class HookStubManager {
} }
} }
public static Object hookBridge(Member origin, Object thiz, Object... args) throws Throwable {
if (XposedBridge.disableHooks) {
return SandHook.callOriginMethod(origin, thiz, args);
}
DexLog.printMethodHookIn(origin);
Object[] snapshot = hookCallbacks.get(origin).getSnapshot();
if (snapshot == null || snapshot.length == 0) {
return SandHook.callOriginMethod(origin, thiz, args);
}
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
param.method = origin;
param.thisObject = thiz;
param.args = args;
int beforeIdx = 0;
do {
try {
((XC_MethodHook) snapshot[beforeIdx]).callBeforeHookedMethod(param);
} catch (Throwable t) {
// reset result (ignoring what the unexpectedly exiting callback did)
param.setResult(null);
param.returnEarly = false;
continue;
}
if (param.returnEarly) {
// skip remaining "before" callbacks and corresponding "after" callbacks
beforeIdx++;
break;
}
} while (++beforeIdx < snapshot.length);
// call original method if not requested otherwise
if (!param.returnEarly) {
try {
param.setResult(SandHook.callOriginMethod(origin, thiz, param.args));
} catch (Throwable e) {
XposedBridge.log(e);
param.setThrowable(e);
}
}
// call "after method" callbacks
int afterIdx = beforeIdx - 1;
do {
Object lastResult = param.getResult();
Throwable lastThrowable = param.getThrowable();
try {
((XC_MethodHook) snapshot[afterIdx]).callAfterHookedMethod(param);
} catch (Throwable t) {
XposedBridge.log(t);
if (lastThrowable == null)
param.setResult(lastResult);
else
param.setThrowable(lastThrowable);
}
} while (--afterIdx >= 0);
if (!param.hasThrowable()) {
return param.getResult();
} else {
throw param.getThrowable();
}
}
public static long callOrigin(HookMethodEntity entity, Member origin, Object thiz, Object[] args) throws Throwable { public static long callOrigin(HookMethodEntity entity, Member origin, Object thiz, Object[] args) throws Throwable {
Object res = SandHook.callOriginMethod(origin, thiz, args); Object res = SandHook.callOriginMethod(origin, thiz, args);
return entity.getResultAddress(res); return entity.getResultAddress(res);
......
...@@ -24,7 +24,7 @@ import de.robv.android.xposed.XposedBridge; ...@@ -24,7 +24,7 @@ import de.robv.android.xposed.XposedBridge;
public final class DynamicBridge { public final class DynamicBridge {
private static final HashMap<Member, Method> hookedInfo = new HashMap<>(); private static final HashMap<Member, Method> hookedInfo = new HashMap<>();
private static final HookerDexMaker dexMaker = new HookerDexMaker(); private static HookMaker hookMaker = XposedCompat.useNewDexMaker ? new HookerDexMakerNew() : new HookerDexMaker();
private static final AtomicBoolean dexPathInited = new AtomicBoolean(false); private static final AtomicBoolean dexPathInited = new AtomicBoolean(false);
private static File dexDir; private static File dexDir;
...@@ -66,9 +66,9 @@ public final class DynamicBridge { ...@@ -66,9 +66,9 @@ public final class DynamicBridge {
SandHook.hook(new HookWrapper.HookEntity(hookMethod, stub.hook, stub.backup, false)); SandHook.hook(new HookWrapper.HookEntity(hookMethod, stub.hook, stub.backup, false));
entityMap.put(hookMethod, stub); entityMap.put(hookMethod, stub);
} else { } else {
dexMaker.start(hookMethod, additionalHookInfo, hookMaker.start(hookMethod, additionalHookInfo,
XposedCompat.classLoader, dexDir == null ? null : dexDir.getAbsolutePath()); XposedCompat.classLoader, dexDir == null ? null : dexDir.getAbsolutePath());
hookedInfo.put(hookMethod, dexMaker.getCallBackupMethod()); hookedInfo.put(hookMethod, hookMaker.getCallBackupMethod());
} }
DexLog.d("hook method <" + hookMethod.toString() + "> cost " + (System.currentTimeMillis() - timeStart) + " ms, by " + (stub != null ? "internal stub." : "dex maker")); DexLog.d("hook method <" + hookMethod.toString() + "> cost " + (System.currentTimeMillis() - timeStart) + " ms, by " + (stub != null ? "internal stub." : "dex maker"));
Trace.endSection(); Trace.endSection();
......
package com.swift.sandhook.xposedcompat.methodgen;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import de.robv.android.xposed.XposedBridge;
public interface HookMaker {
void start(Member member, XposedBridge.AdditionalHookInfo hookInfo,
ClassLoader appClassLoader, String dexDirPath) throws Exception;
Method getHookMethod();
Method getBackupMethod();
Method getCallBackupMethod();
}
...@@ -34,7 +34,7 @@ import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.autoUnboxIfNec ...@@ -34,7 +34,7 @@ import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.autoUnboxIfNec
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.createResultLocals; import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.createResultLocals;
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.getObjTypeIdIfPrimitive; import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.getObjTypeIdIfPrimitive;
public class HookerDexMaker { public class HookerDexMaker implements HookMaker {
public static final String METHOD_NAME_BACKUP = "backup"; public static final String METHOD_NAME_BACKUP = "backup";
public static final String METHOD_NAME_HOOK = "hook"; public static final String METHOD_NAME_HOOK = "hook";
......
package com.swift.sandhook.xposedcompat.methodgen;
import android.text.TextUtils;
import com.android.dx.Code;
import com.android.dx.DexMaker;
import com.android.dx.FieldId;
import com.android.dx.Label;
import com.android.dx.Local;
import com.android.dx.MethodId;
import com.android.dx.TypeId;
import com.swift.sandhook.SandHook;
import com.swift.sandhook.wrapper.HookWrapper;
import com.swift.sandhook.xposedcompat.hookstub.HookStubManager;
import com.swift.sandhook.xposedcompat.utils.DexLog;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.MD5;
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.autoBoxIfNecessary;
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.autoUnboxIfNecessary;
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.createResultLocals;
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.getObjTypeIdIfPrimitive;
public class HookerDexMakerNew implements HookMaker {
public static final String METHOD_NAME_BACKUP = "backup";
public static final String METHOD_NAME_HOOK = "hook";
public static final TypeId<Object[]> objArrayTypeId = TypeId.get(Object[].class);
private static final String CLASS_DESC_PREFIX = "L";
private static final String CLASS_NAME_PREFIX = "SandHookerN";
private static final String FIELD_NAME_HOOK_INFO = "additionalHookInfo";
private static final String FIELD_NAME_METHOD = "method";
private static final String FIELD_NAME_BACKUP_METHOD = "backupMethod";
private static final TypeId<Throwable> throwableTypeId = TypeId.get(Throwable.class);
private static final TypeId<Member> memberTypeId = TypeId.get(Member.class);
private static final TypeId<Method> methodTypeId = TypeId.get(Method.class);
private static final TypeId<XposedBridge.AdditionalHookInfo> hookInfoTypeId
= TypeId.get(XposedBridge.AdditionalHookInfo.class);
private FieldId<?, XposedBridge.AdditionalHookInfo> mHookInfoFieldId;
private FieldId<?, Member> mMethodFieldId;
private FieldId<?, Method> mBackupMethodFieldId;
private MethodId<?, ?> mHookMethodId;
private MethodId<?, ?> mBackupMethodId;
private MethodId<?, ?> mSandHookBridgeMethodId;
private TypeId<?> mHookerTypeId;
private TypeId<?>[] mParameterTypeIds;
private Class<?>[] mActualParameterTypes;
private Class<?> mReturnType;
private TypeId<?> mReturnTypeId;
private boolean mIsStatic;
// TODO use this to generate methods
private boolean mHasThrowable;
private DexMaker mDexMaker;
private Member mMember;
private XposedBridge.AdditionalHookInfo mHookInfo;
private ClassLoader mAppClassLoader;
private Class<?> mHookClass;
private Method mHookMethod;
private Method mBackupMethod;
private String mDexDirPath;
private static TypeId<?>[] getParameterTypeIds(Class<?>[] parameterTypes, boolean isStatic) {
int parameterSize = parameterTypes.length;
int targetParameterSize = isStatic ? parameterSize : parameterSize + 1;
TypeId<?>[] parameterTypeIds = new TypeId<?>[targetParameterSize];
int offset = 0;
if (!isStatic) {
parameterTypeIds[0] = TypeId.OBJECT;
offset = 1;
}
for (int i = 0; i < parameterTypes.length; i++) {
parameterTypeIds[i + offset] = TypeId.get(parameterTypes[i]);
}
return parameterTypeIds;
}
private static Class<?>[] getParameterTypes(Class<?>[] parameterTypes, boolean isStatic) {
if (isStatic) {
return parameterTypes;
}
int parameterSize = parameterTypes.length;
int targetParameterSize = parameterSize + 1;
Class<?>[] newParameterTypes = new Class<?>[targetParameterSize];
int offset = 1;
newParameterTypes[0] = Object.class;
System.arraycopy(parameterTypes, 0, newParameterTypes, offset, parameterTypes.length);
return newParameterTypes;
}
public void start(Member member, XposedBridge.AdditionalHookInfo hookInfo,
ClassLoader appClassLoader, String dexDirPath) throws Exception {
if (member instanceof Method) {
Method method = (Method) member;
mIsStatic = Modifier.isStatic(method.getModifiers());
mReturnType = method.getReturnType();
if (mReturnType.equals(Void.class) || mReturnType.equals(void.class)
|| mReturnType.isPrimitive()) {
mReturnTypeId = TypeId.get(mReturnType);
} else {
// all others fallback to plain Object for convenience
mReturnType = Object.class;
mReturnTypeId = TypeId.OBJECT;
}
mParameterTypeIds = getParameterTypeIds(method.getParameterTypes(), mIsStatic);
mActualParameterTypes = getParameterTypes(method.getParameterTypes(), mIsStatic);
mHasThrowable = method.getExceptionTypes().length > 0;
} else if (member instanceof Constructor) {
Constructor constructor = (Constructor) member;
mIsStatic = false;
mReturnType = void.class;
mReturnTypeId = TypeId.VOID;
mParameterTypeIds = getParameterTypeIds(constructor.getParameterTypes(), mIsStatic);
mActualParameterTypes = getParameterTypes(constructor.getParameterTypes(), mIsStatic);
mHasThrowable = constructor.getExceptionTypes().length > 0;
} else if (member.getDeclaringClass().isInterface()) {
throw new IllegalArgumentException("Cannot hook interfaces: " + member.toString());
} else if (Modifier.isAbstract(member.getModifiers())) {
throw new IllegalArgumentException("Cannot hook abstract methods: " + member.toString());
} else {
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + member.toString());
}
mMember = member;
mHookInfo = hookInfo;
mDexDirPath = dexDirPath;
if (appClassLoader == null
|| appClassLoader.getClass().getName().equals("java.lang.BootClassLoader")) {
mAppClassLoader = this.getClass().getClassLoader();
} else {
mAppClassLoader = appClassLoader;
}
mDexMaker = new DexMaker();
// Generate a Hooker class.
String className = getClassName(mMember);
String dexName = className + ".jar";
HookWrapper.HookEntity hookEntity = null;
//try load cache first
try {
ClassLoader loader = mDexMaker.loadClassDirect(mAppClassLoader, new File(mDexDirPath), dexName);
if (loader != null) {
hookEntity = loadHookerClass(loader, className);
}
} catch (Throwable throwable) {}
//do generate
if (hookEntity == null) {
hookEntity = doMake(className, dexName);
}
SandHook.hook(hookEntity);
}
private HookWrapper.HookEntity doMake(String className, String dexName) throws Exception {
mHookerTypeId = TypeId.get(CLASS_DESC_PREFIX + className + ";");
mDexMaker.declare(mHookerTypeId, className + ".generated", Modifier.PUBLIC, TypeId.OBJECT);
generateFields();
generateHookMethod();
generateBackupMethod();
ClassLoader loader;
if (TextUtils.isEmpty(mDexDirPath)) {
throw new IllegalArgumentException("dexDirPath should not be empty!!!");
}
// Create the dex file and load it.
loader = mDexMaker.generateAndLoad(mAppClassLoader, new File(mDexDirPath), dexName);
return loadHookerClass(loader, className);
}
private HookWrapper.HookEntity loadHookerClass(ClassLoader loader, String className) throws Exception {
mHookClass = loader.loadClass(className);
// Execute our newly-generated code in-process.
mHookMethod = mHookClass.getMethod(METHOD_NAME_HOOK, mActualParameterTypes);
mBackupMethod = mHookClass.getMethod(METHOD_NAME_BACKUP, mActualParameterTypes);
setup(mHookClass);
return new HookWrapper.HookEntity(mMember, mHookMethod, mBackupMethod, false);
}
private void setup(Class mHookClass) {
XposedHelpers.setStaticObjectField(mHookClass, FIELD_NAME_METHOD, mMember);
XposedHelpers.setStaticObjectField(mHookClass, FIELD_NAME_BACKUP_METHOD, mBackupMethod);
XposedHelpers.setStaticObjectField(mHookClass, FIELD_NAME_HOOK_INFO, mHookInfo);
}
private String getClassName(Member originMethod) {
return CLASS_NAME_PREFIX + "_" + MD5(originMethod.toString());
}
public Method getHookMethod() {
return mHookMethod;
}
public Method getBackupMethod() {
return mBackupMethod;
}
public Method getCallBackupMethod() {
return mBackupMethod;
}
public Class getHookClass() {
return mHookClass;
}
private void generateFields() {
mHookInfoFieldId = mHookerTypeId.getField(hookInfoTypeId, FIELD_NAME_HOOK_INFO);
mMethodFieldId = mHookerTypeId.getField(memberTypeId, FIELD_NAME_METHOD);
mBackupMethodFieldId = mHookerTypeId.getField(methodTypeId, FIELD_NAME_BACKUP_METHOD);
mDexMaker.declare(mHookInfoFieldId, Modifier.STATIC, null);
mDexMaker.declare(mMethodFieldId, Modifier.STATIC, null);
mDexMaker.declare(mBackupMethodFieldId, Modifier.STATIC, null);
}
private void generateBackupMethod() {
mBackupMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_BACKUP, mParameterTypeIds);
Code code = mDexMaker.declare(mBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
Local<Member> method = code.newLocal(memberTypeId);
Map<TypeId, Local> resultLocals = createResultLocals(code);
MethodId<?, ?> errLogMethod = TypeId.get(DexLog.class).getMethod(TypeId.get(Void.TYPE), "printCallOriginError", memberTypeId);
//very very important!!!!!!!!!!!
//add a try cache block avoid inline
Label tryCatchBlock = new Label();
code.addCatchClause(throwableTypeId, tryCatchBlock);
code.sget(mMethodFieldId, method);
code.invokeStatic(errLogMethod, null, method);
// start of try
code.mark(tryCatchBlock);
// do nothing
if (mReturnTypeId.equals(TypeId.VOID)) {
code.returnVoid();
} else {
// we have limited the returnType to primitives or Object, so this should be safe
code.returnValue(resultLocals.get(mReturnTypeId));
}
}
private void generateHookMethod() {
mHookMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_HOOK, mParameterTypeIds);
mSandHookBridgeMethodId = TypeId.get(HookStubManager.class).getMethod(TypeId.get(Object.class), "hookBridge", memberTypeId, TypeId.get(Object.class), TypeId.get(Object[].class));
Code code = mDexMaker.declare(mHookMethodId, Modifier.PUBLIC | Modifier.STATIC);
Local<Member> method = code.newLocal(memberTypeId);
// Local<Method> backupMethod = code.newLocal(methodTypeId);
Local<Object> thisObject = code.newLocal(TypeId.OBJECT);
Local<Object[]> args = code.newLocal(objArrayTypeId);
Local<Integer> actualParamSize = code.newLocal(TypeId.INT);
Local<Integer> argIndex = code.newLocal(TypeId.INT);
Local<Object> resultObj = code.newLocal(TypeId.OBJECT);
Local[] allArgsLocals = createParameterLocals(code);
Map<TypeId, Local> resultLocals = createResultLocals(code);
code.sget(mMethodFieldId, method);
code.loadConstant(args, null);
code.loadConstant(argIndex, 0);
// code.sget(mBackupMethodFieldId, backupMethod);
int paramsSize = mParameterTypeIds.length;
int offset = 0;
// thisObject
if (mIsStatic) {
// thisObject = null
code.loadConstant(thisObject, null);
} else {
// thisObject = args[0]
offset = 1;
code.move(thisObject, allArgsLocals[0]);
}
// actual args (exclude thisObject if this is not a static method)
code.loadConstant(actualParamSize, paramsSize - offset);
code.newArray(args, actualParamSize);
for (int i = offset; i < paramsSize; i++) {
Local parameter = allArgsLocals[i];
// save parameter to resultObj as Object
autoBoxIfNecessary(code, resultObj, parameter);
code.loadConstant(argIndex, i - offset);
// save Object to args
code.aput(args, argIndex, resultObj);
}
if (mReturnTypeId.equals(TypeId.VOID)) {
code.invokeStatic(mSandHookBridgeMethodId, null, method, thisObject, args);
code.returnVoid();
} else {
code.invokeStatic(mSandHookBridgeMethodId, resultObj, method, thisObject, args);
TypeId objTypeId = getObjTypeIdIfPrimitive(mReturnTypeId);
Local matchObjLocal = resultLocals.get(objTypeId);
code.cast(matchObjLocal, resultObj);
// have to use matching typed Object(Integer, Double ...) to do unboxing
Local toReturn = resultLocals.get(mReturnTypeId);
autoUnboxIfNecessary(code, toReturn, matchObjLocal, resultLocals, true);
code.returnValue(toReturn);
}
}
private Local[] createParameterLocals(Code code) {
Local[] paramLocals = new Local[mParameterTypeIds.length];
for (int i = 0; i < mParameterTypeIds.length; i++) {
paramLocals[i] = code.getParameter(i, mParameterTypeIds[i]);
}
return paramLocals;
}
}
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