逆向-hook-java

搬砖

ArtMethod hook

1. xposed

步骤一:hookMethod->hookMethodNative->XposedBridge_hookMethodNative->EnableXposedHook
  
1
2
3
// 核心:传递参数,替换artmethod entrypoint
SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info), sizeof(void*));
SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());
步骤二:触发函数调用进入 artQuickProxyInvokeHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
// 通过反射调用“handleHookedMethod”
InvokeXposedHandleHookedMethod(soa, shorty, rcvr_jobj, proxy_methodid, args);
// Call XposedBridge.handleHookedMethod
jvalue invocation_args[5];
invocation_args[0].l = hook_info->reflected_method;
invocation_args[1].i = 1;
invocation_args[2].l = hook_info->additional_info;
invocation_args[3].l = rcvr_jobj;
invocation_args[4].l = args_jobj;
jobject result =
soa.Env()->CallStaticObjectMethodA(ArtMethod::xposed_callback_class,
ArtMethod::xposed_callback_method,
invocation_args);
步骤三:在XposedBridge的handleHookedMethod进行aop,完成hook
1
2
3
4
before: ((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
origin: param.setResult(invokeOriginalMethodNative(method, originalMethodId,
additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args));
after: ((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);

2. lsplant

步骤一: 将需要hook对象-target目标,和中间对象NativeHooker绑定到一起
  
1
2
3
4
5
6
7
8
// new LSPosedBridge.NativeHooker.class
auto init = env->GetMethodID(hooker, "<init>", "(Ljava/lang/reflect/Executable;)V");
// 中间方法
auto callback_method = env->ToReflectedMethod(hooker, env->GetMethodID(hooker, "callback",
"([Ljava/lang/Object;)Ljava/lang/Object;"),
false);
auto hooker_object = env->NewObject(hooker, init, hookMethod);
hook_item->SetBackup(lsplant::Hook(env, hookMethod, hooker_object, callback_method));
步骤二:通过BuildDex 生成一个与taget目标参数一样的hook bridge方法,然后多出第一个参数来存放this 指针,函数主要是调用NativeHooker 的callback方法 步骤三:生成跳板,将原始函数的artmethod替换为动态生成的hook bridge的artmethod 核心点: 1. 生成跳板并修改entry_point 完成hook 2. 在 Java 中,非静态方法(实例方法)的调用隐式地传递了一个 this 指针作为第一个参数。所以如果你要用一个静态方法去替换原来的实例方法,你需要显式地把这个 this 指针作为静态方法的第一个参数 实际案例:
1
2
3
4
    LSPlant : lsplant.cc:676#bool lsplant::(anonymous namespace)::DoHook(ArtMethod *, ArtMethod *, ArtMethod *): Hooking: target = java.lang.String org.lsposed.lsplant.LSPTest.manyParametersMethod(java.lang.String, boolean, byte, short, int, long, float, double, java.lang.Integer, java.lang.Long)(0x795d3ef120), hook = java.lang.Object LSPHooker_.manyParametersMethod(java.lang.Object, java.lang.Object, boolean, byte, short, int, long, float, double, java.lang.Object, java.lang.Object)(0x795d323050), backup = java.lang.Object LSPHooker_.backup(java.lang.Object, java.lang.Object, boolean, byte, short, int, long, float, double, java.lang.Object, java.lang.Object)(0x795d323030)
LSPlant : lsplant.cc:657#void *lsplant::(anonymous namespace)::GenerateTrampolineFor(art::ArtMethod *): trampoline: count = 0, address = 7cafdbc000, target = 7cafdbc000
LSPlant : lsplant.cc:683#bool lsplant::(anonymous namespace)::DoHook(ArtMethod *, ArtMethod *, ArtMethod *): Generated trampoline 0x7cafdbc000
LSPlant : lsplant.cc:699#bool lsplant::(anonymous namespace)::DoHook(ArtMethod *, ArtMethod *, ArtMethod *): Done hook: target(0x795d3ef120:0x1a000000) -> 0x7cafdbc000; backup(0x795d323030:0x1a000002) -> 0x797e222320; hook(0x795d323050:0x12080009) -> 0x797e222070
1
2
3
4
5
6
7
8
9
10
11
12
13
14
在 Java 中,任何非静态方法隐式地接收 this 作为第一个参数。在 Android ART 环境里进行方法替换时,如果原始的非静态方法(org.lsposed.lsplant.LSPTest.manyParametersMethod)被替换为一个静态方法(LSPHooker_.manyParametersMethod),那么这个静态方法需要显式地声明一个额外的参数来接收原本隐式传递的 this 指针。

因此,LSPHooker_.manyParametersMethod 的第一个参数类型是 java.lang.Object,它是为了通用性设计的,可以接收任意类型的 Java 对象。这个参数在运行时将接收原始方法的 this 指针,即实例对应的 org.lsposed.lsplant.LSPTest 对象实例。

这样,在运行时,调用 LSPHooker_.manyParametersMethod 时,会将原始 target 方法被调用时的 this 指针和所有其他参数正确地传递给 hook 方法。因此,第一个参数总是用于 this 指针,而后续的参数将是 target 方法原来的参数,保持相同的顺序。

在 LSPHooker_.manyParametersMethod 方法内,你可以像处理其他参数一样处理 this 指针参数,如果你需要调用原始对象的其他非静态成员,可以将 java.lang.Object 类型的 this 指针参数转换回原始类的类型。

简单地说:

当原始方法(target)的 entry_point 被替换为 hook 方法的地址时,
任何对原始 target 方法的调用都会被重定向到你的 hook 方法,
由于 hook 方法是静态的,LSPHooker_.manyParametersMethod 需要一个额外的参数来接收 this 指针,
在 hook 方法的第一个参数位置,你将得到原始实例的 this 指针,你可以把它看作是任何传递给 target 方法的常规参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
我猜是生成一个大致这样的类
public class LSPHooker_ {
static C1 hooker;
public static Object backup(Object obj) {
return null;
}
public static Object manyParametersMethod(Object arg0, int arg1, ... /* 其他参数 */) {
Object[] hook_params_array = new Object[parameter_types.size()];
hook_params_array[0] = arg0; // 假设arg0已经是Object类型
hook_params_array[1] = Integer.valueOf(arg1); // 对于原始类型进行装箱操作
// 对于更多参数的处理...
return hooker.callback(hook_params_array);
}}

3. dexposed-on-art

source->原始函数,被hook函数
target->hook函数,是一个entry

步骤一:创建跳板
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private byte[] createTrampoline(ArtMethod source){
final Epic.MethodInfo methodInfo = Epic.getMethodInfo(source.getAddress());
final Class<?> returnType = methodInfo.returnType;

// 动态创建一个(Bridge|class对象中method|artmethod|target)
Method bridgeMethod = Runtime.is64Bit() ? Entry64.getBridgeMethod(returnType)
: Entry.getBridgeMethod(returnType);
final ArtMethod target = ArtMethod.of(bridgeMethod);
long targetAddress = target.getAddress();
long targetEntry = target.getEntryPointFromQuickCompiledCode();
long sourceAddress = source.getAddress();
long structAddress = EpicNative.malloc(4);

Logger.d(TAG, "targetAddress:"+ Debug.longHex(targetAddress));
Logger.d(TAG, "sourceAddress:"+ Debug.longHex(sourceAddress));
Logger.d(TAG, "targetEntry:"+ Debug.longHex(targetEntry));
Logger.d(TAG, "structAddress:"+ Debug.longHex(structAddress));
// 生成跳板
return shellCode.createBridgeJump(targetAddress, targetEntry, sourceAddress, structAddress);
}


//跳板逻辑
byte[] instructions = new byte[]{
0x1f, 0x20, 0x03, (byte) 0xd5, // nop
0x69, 0x02, 0x00, 0x58, // ldr x9, source_method
0x1f, 0x00, 0x09, (byte) 0xeb, // cmp x0, x9
(byte) 0xa1, 0x02, 0x00, 0x54, // bne 5f
(byte) 0x80, 0x01, 0x00, 0x58, // ldr x0, target_method

0x29, 0x02, 0x00, 0x58, // ldr x9, struct
(byte) 0xea, 0x03, 0x00, (byte) 0x91, // mov x10, sp

0x2a, 0x01, 0x00, (byte) 0xf9, // str x10, [x9, #0]
0x22, 0x05, 0x00, (byte) 0xf9, // str x2, [x9, #8]

0x23, 0x09, 0x00, (byte) 0xf9, // str x3, [x9, #16]
(byte) 0xe3, 0x03, 0x09, (byte) 0xaa, // mov x3, x9
0x22, 0x01, 0x00, 0x58, // ldr x2, source_method
0x22, 0x0d, 0x00, (byte) 0xf9, // str x2, [x9, #24]
(byte) 0xe2, 0x03, 0x13, (byte) 0xaa, // mov x2, x19
(byte) 0x89, 0x00, 0x00, 0x58, // ldr x9, target_method_entry
0x20, 0x01, 0x1f, (byte) 0xd6, // br x9

0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // target_method_address
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // target_method_entry
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // source_method
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // struct
};
步骤二:修改原函数artmethod的entrypoint,直接加载指向跳板 步骤三:由跳板得知,当触发函数调用的时候会直接进入target_method_entry,原函数的参数通过申请的struct结构体填充到相关栈帧的地址上并传递进入到(Bridge|class对象中method|artmethod|target)中。 步骤四:解析出原始artmethod,参数,对象。回调onHook,进入handleHookedArtMethod逻辑 步骤五:handleHookedArtMethod中通过aop方式(before->反射调用原函数->after)完成hook
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static Object handleHookedArtMethod(Object artMethodObject, Object thisObject, Object[] args) {
...
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
param.method = (Member) (artmethod).getExecutable();
param.thisObject = thisObject;
param.args = args;

...
((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
...

// call original method if not requested otherwise
if (!param.returnEarly) {
try {
ArtMethod method = Epic.getBackMethod(artmethod);
Object result = method.invoke(thisObject, args);
param.setResult(result);
} catch (Exception e) {
// log(e); origin throw exception is normal.
param.setThrowable(e);
}
}
...
((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
...
}

jvmti