package com.lody.legend;
import com.lody.legend.art.ArtMethod;
import com.lody.legend.art.ArtMethodStructV19;
import com.lody.legend.dalvik.DalvikMethodStruct;
import com.lody.legend.io.SizeOf;
import com.lody.legend.utility.Logger;
import com.lody.legend.utility.Memory;
import com.lody.legend.utility.Runtime;
import com.lody.legend.utility.Struct;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Lody
* @version 1.0
*/
public class HookManager {
/**
* HookInfo struct size in Dalvik.
*/
private static final int DVM_HOOK_INFO_SIZE =
SizeOf.INT
+ SizeOf.INT
+ Struct.UINT_16
+ Struct.UINT_16
+ Struct.UINT_32
+ SizeOf.INT;
/**
* HookInfo struct size in Art.
* 8 bytes * 9 * 2
*/
private static final int ART_HOOK_INFO_SIZE = 72 * 2;
/**
* Singleton
*/
private static final HookManager sDefault = new HookManager();
/**
* [Key] = MethodName
* [Value] = BackupMethods
*/
private final Map<String, Map<String, List<Method>>> classToBackupMethodsMapping = new ConcurrentHashMap<String, Map<String, List<Method>>>();
public static HookManager getDefault() {
return sDefault;
}
//
// public void applyHooks(Class<?> holdClass) {
// for (Method hookMethod : holdClass.getDeclaredMethods()) {
// Hook hook = hookMethod.getAnnotation(Hook.class);
// if (hook != null) {
// String statement = hook.value();
// String[] splitValues = statement.split("::");
// if (splitValues.length == 2) {
// String className = splitValues[0];
// String[] methodNameWithSignature = splitValues[1].split("@");
// if (methodNameWithSignature.length <= 2) {
// String methodName = methodNameWithSignature[0];
// String signature = methodNameWithSignature.length == 2 ? methodNameWithSignature[1] : "";
// String[] paramList = signature.split("#");
// if (paramList[0].equals("")) {
// paramList = new String[0];
// }
// try {
// Class<?> clazz = Class.forName(className);
// boolean isResolve = false;
// for (Method method : clazz.getDeclaredMethods()) {
// if (method.getName().equals(methodName)) {
// Class<?>[] types = method.getParameterTypes();
// if (paramList.length == types.length) {
// boolean isMatch = true;
// for (int N = 0; N < types.length; N++) {
// if (!types[N].getName().equals(paramList[N])) {
// isMatch = false;
// break;
// }
// }
// if (isMatch) {
// hookMethod(method, hookMethod);
// isResolve = true;
// Logger.d("[+++] %s have hooked.", method.getName());
// }
// }
// }
// if (isResolve) {
// break;
// }
// }
// if (!isResolve) {
// Logger.e("[---] Cannot resolve Method : %s.", Arrays.toString(methodNameWithSignature));
// }
// } catch (Throwable e) {
// Logger.e("[---] Error to Load Hook Method From : %s." , hookMethod.getName());
// e.printStackTrace();
// }
//
// }else {
// Logger.e("[---] Can't split method and signature : %s.", Arrays.toString(methodNameWithSignature));
// }
// }else {
// Logger.e("[---] Can't understand your statement : [%s].", statement);
// }
// }
// }
// }
public void hookMethod(Method origin, Method hook) {
if (origin == null) {
throw new IllegalArgumentException("Origin method cannot be null");
}
if (hook == null) {
throw new IllegalArgumentException("Hook method cannot be null");
}
// if (!Modifier.isStatic(hook.getModifiers())) {
// throw new IllegalStateException("Hook method must be a static method.");
// }
origin.setAccessible(true);
hook.setAccessible(true);
String methodName = Runtime.isArt() ? hook.getName() : origin.getName();
Method backupMethod;
if (Runtime.isArt()) {
backupMethod = hookMethodArt(origin, hook);
} else {
backupMethod = hookMethodDalvik(origin, hook);
}
String className = hook.getDeclaringClass().getName();
Map<String, List<Method>> methodNameToBackupMethodsMap = classToBackupMethodsMapping.get(className);
if (methodNameToBackupMethodsMap == null) {
methodNameToBackupMethodsMap = new ConcurrentHashMap<String, List<Method>>();
classToBackupMethodsMapping.put(className, methodNameToBackupMethodsMap);
}
List<Method> backupList = methodNameToBackupMethodsMap.get(methodName);
if (backupList == null) {
backupList = new LinkedList<Method>();
methodNameToBackupMethodsMap.put(methodName, backupList);
}
backupMethod.setAccessible(true);
backupList.add(backupMethod);
}
private static Method hookMethodDalvik(Method origin, Method hook) {
DalvikMethodStruct dvmOriginMethod = DalvikMethodStruct.of(origin);
DalvikMethodStruct dvmHookMethod = DalvikMethodStruct.of(hook);
byte[] originClassData = dvmOriginMethod.clazz.read();
byte[] originInsnsData = dvmOriginMethod.insns.read();
byte[] originInsSizeData = dvmOriginMethod.insSize.read();
byte[] originRegisterSizeData = dvmOriginMethod.registersSize.read();
byte[] originAccessFlags = dvmOriginMethod.accessFlags.read();
byte[] originNativeFunc = dvmOriginMethod.nativeFunc.read();
byte[] hookClassData = dvmHookMethod.clazz.read();
byte[] hookInsnsData = dvmHookMethod.insns.read();
byte[] hookInsSizeData = dvmHookMethod.insSize.read();
byte[] hookRegisterSizeData = dvmHookMethod.registersSize.read();
byte[] hookAccessFlags = dvmHookMethod.accessFlags.read();
byte[] hookNativeFunc = dvmHookMethod.nativeFunc.read();
dvmOriginMethod.clazz.write(hookClassData);
dvmOriginMethod.insns.write(hookInsnsData);
dvmOriginMethod.insSize.write(hookInsSizeData);
dvmOriginMethod.registersSize.write(hookRegisterSizeData);
dvmOriginMethod.accessFlags.write(hookAccessFlags);
ByteBuffer byteBuffer = ByteBuffer.allocate(DVM_HOOK_INFO_SIZE);
byteBuffer.put(originClassData);
byteBuffer.put(originInsnsData);
byteBuffer.put(originInsSizeData);
byteBuffer.put(originRegisterSizeData);
byteBuffer.put(originAccessFlags);
byteBuffer.put(originNativeFunc);
//May leak
long memoryAddress = Memory.alloc(DVM_HOOK_INFO_SIZE);
Memory.write(memoryAddress, byteBuffer.array());
dvmOriginMethod.nativeFunc.write(memoryAddress);
return origin;
}
private static Method hookMethodArt(Method origin, Method hook) {
ArtMethod artOrigin = ArtMethod.of(origin);
ArtMethod artHook = ArtMethod.of(hook);
Method backup = artOrigin.backup().getMethod();
backup.setAccessible(true);
long originPointFromQuickCompiledCode = artOrigin.getEntryPointFromQuickCompiledCode();
long originEntryPointFromJni = artOrigin.getEntryPointFromJni();
long originEntryPointFromInterpreter = artOrigin.getEntryPointFromInterpreter();
long originDeclaringClass = artOrigin.getDeclaringClass();
long originAccessFlags = artOrigin.getAccessFlags();
long originDexCacheResolvedMethods = artOrigin.getDexCacheResolvedMethods();
long originDexCacheResolvedTypes = artOrigin.getDexCacheResolvedTypes();
long originDexCodeItemOffset = artOrigin.getDexCodeItemOffset();
long originDexMethodIndex = artOrigin.getDexMethodIndex();
long hookPointFromQuickCompiledCode = artHook.getEntryPointFromQuickCompiledCode();
long hookEntryPointFromJni = artHook.getEntryPointFromJni();
long hookEntryPointFromInterpreter = artHook.getEntryPointFromInterpreter();
long hookDeclaringClass = artHook.getDeclaringClass();
long hookAccessFlags = artHook.getAccessFlags();
long hookDexCacheResolvedMethods = artHook.getDexCacheResolvedMethods();
long hookDexCacheResolvedTypes = artHook.getDexCacheResolvedTypes();
long hookDexCodeItemOffset = artHook.getDexCodeItemOffset();
long hookDexMethodIndex = artHook.getDexMethodIndex();
ByteBuffer hookInfo = ByteBuffer.allocate(ART_HOOK_INFO_SIZE);
hookInfo.putLong(originPointFromQuickCompiledCode);
hookInfo.putLong(originEntryPointFromJni);
hookInfo.putLong(originEntryPointFromInterpreter);
hookInfo.putLong(originDeclaringClass);
hookInfo.putLong(originAccessFlags);
hookInfo.putLong(originDexCacheResolvedMethods);
hookInfo.putLong(originDexCacheResolvedTypes);
hookInfo.putLong(originDexCodeItemOffset);
hookInfo.putLong(originDexMethodIndex);
hookInfo.putLong(hookPointFromQuickCompiledCode);
hookInfo.putLong(hookEntryPointFromJni);
hookInfo.putLong(hookEntryPointFromInterpreter);
hookInfo.putLong(hookDeclaringClass);
hookInfo.putLong(hookAccessFlags);
hookInfo.putLong(hookDexCacheResolvedMethods);
hookInfo.putLong(hookDexCacheResolvedTypes);
hookInfo.putLong(hookDexCodeItemOffset);
hookInfo.putLong(hookDexMethodIndex);
artOrigin.setEntryPointFromQuickCompiledCode(hookPointFromQuickCompiledCode);
artOrigin.setEntryPointFromInterpreter(hookEntryPointFromInterpreter);
artOrigin.setDeclaringClass(hookDeclaringClass);
artOrigin.setDexCacheResolvedMethods(hookDexCacheResolvedMethods);
artOrigin.setDexCacheResolvedTypes(hookDexCacheResolvedTypes);
artOrigin.setDexCodeItemOffset((int) hookDexCodeItemOffset);
artOrigin.setDexMethodIndex((int) hookDexMethodIndex);
int accessFlags = origin.getModifiers();
if (Modifier.isNative(accessFlags)) {
accessFlags &= ~Modifier.NATIVE;
artOrigin.setAccessFlags(accessFlags);
}
long memoryAddress = Memory.alloc(ART_HOOK_INFO_SIZE);
Memory.write(memoryAddress, hookInfo.array());
artOrigin.setEntryPointFromJni(memoryAddress);
return backup;
}
public <T> T callSuper(Object who, Object... args) {
StackTraceElement[] traceElements = Thread.currentThread().getStackTrace();
StackTraceElement currentInvoking = traceElements[3];
String invokingClassName = currentInvoking.getClassName();
String invokingMethodName = currentInvoking.getMethodName();
Map<String, List<Method>> methodNameToBackupMethodsMap = classToBackupMethodsMapping.get(invokingClassName);
if (methodNameToBackupMethodsMap != null) {
List<Method> methodList = methodNameToBackupMethodsMap.get(invokingMethodName);
if (methodList != null) {
Method method = matchSimilarMethod(methodList, args);
if (method != null) {
try {
if (Runtime.isArt()) {
return callSuperArt(method, who, args);
} else {
return callSuperDalvik(method, who, args);
}
} catch (Throwable e) {
Logger.e("[---] Call super method with error : %s, detail message please see the [Logcat :system.err].", e.getMessage());
e.printStackTrace();
}
} else {
Logger.e("[---] Super method cannot found in backup map.");
}
}
}
return null;
}
private <T> T callSuperArt(Method method, Object who, Object... args) throws Throwable {
//noinspection unchecked
return (T) method.invoke(who, args);
}
private <T> T callSuperDalvik(Method method, Object who, Object... args) throws Throwable {
DalvikMethodStruct dvmMethod = DalvikMethodStruct.of(method);
long memoryAddress = dvmMethod.nativeFunc.readLong();
byte[] hookInfo = Memory.read(memoryAddress, DVM_HOOK_INFO_SIZE);
ByteBuffer hookBuffer = ByteBuffer.wrap(hookInfo);
byte[] originClassData = new byte[SizeOf.INT];
byte[] originInsnsData = new byte[SizeOf.INT];
byte[] originInsSizeData = new byte[Struct.UINT_16];
byte[] originRegisterSizeData = new byte[Struct.UINT_16];
byte[] originAccessFlags = new byte[Struct.UINT_32];
byte[] originNativeFunc = new byte[SizeOf.INT];
hookBuffer.get(originClassData);
hookBuffer.get(originInsnsData);
hookBuffer.get(originInsSizeData);
hookBuffer.get(originRegisterSizeData);
hookBuffer.get(originAccessFlags);
hookBuffer.get(originNativeFunc);
byte[] hookClassData = dvmMethod.clazz.read();
byte[] hookInsnsData = dvmMethod.insns.read();
byte[] hookInsSizeData = dvmMethod.insSize.read();
byte[] hookRegisterSizeData = dvmMethod.registersSize.read();
byte[] hookAccessFlags = dvmMethod.accessFlags.read();
byte[] hookNativeFunc = dvmMethod.nativeFunc.read();
dvmMethod.clazz.write(originClassData);
dvmMethod.insns.write(originInsnsData);
dvmMethod.insSize.write(originInsSizeData);
dvmMethod.registersSize.write(originRegisterSizeData);
dvmMethod.accessFlags.write(originAccessFlags);
dvmMethod.nativeFunc.write(originNativeFunc);
//noinspection unchecked
T result = (T) method.invoke(who, args);
dvmMethod.clazz.write(hookClassData);
dvmMethod.insns.write(hookInsnsData);
dvmMethod.insSize.write(hookInsSizeData);
dvmMethod.registersSize.write(hookRegisterSizeData);
dvmMethod.accessFlags.write(hookAccessFlags);
dvmMethod.nativeFunc.write(hookNativeFunc);
return result;
}
private Method matchSimilarMethod(List<Method> methodList, Object... args) {
if (methodList.size() == 1) {
//Only hold one method
return methodList.get(0);
} else {
//Hold more than one methods
Class<?>[] types = types(args);
for (Method method : methodList) {
if (isSimilarSignature(method.getParameterTypes(), types)) {
return method;
}
}
return null;
}
}
private static boolean isSimilarSignature(Class<?>[] declaredTypes, Class<?>[] actualTypes) {
if (declaredTypes.length == actualTypes.length) {
for (int i = 0; i < actualTypes.length; i++) {
if (actualTypes[i] == NULL.class)
continue;
if (wrap(declaredTypes[i]).isAssignableFrom(wrap(actualTypes[i])))
continue;
return false;
}
return true;
} else {
return false;
}
}
private static Class<?> wrap(Class<?> type) {
if (type == null) {
return null;
} else if (type.isPrimitive()) {
if (boolean.class == type) {
return Boolean.class;
} else if (int.class == type) {
return Integer.class;
} else if (long.class == type) {
return Long.class;
} else if (short.class == type) {
return Short.class;
} else if (byte.class == type) {
return Byte.class;
} else if (double.class == type) {
return Double.class;
} else if (float.class == type) {
return Float.class;
} else if (char.class == type) {
return Character.class;
} else if (void.class == type) {
return Void.class;
}
}
return type;
}
private static Class<?>[] types(Object... values) {
if (values == null) {
return new Class[0];
}
Class<?>[] result = new Class[values.length];
for (int i = 0; i < values.length; i++) {
Object value = values[i];
result[i] = value == null ? NULL.class : value.getClass();
}
return result;
}
private static final class NULL {
}
}