xposed art hook 浅析

xposed art hook 原理

只是自己一点浅薄的见解,最近在看art上面hook相关的东西,如果有写的不对的地方请指出。

java部分流程和dvm一样,现在只着重讲一下在art上面怎么hook的,所以从最关键的部分开始

xposed在java部分声明了各种native方法,而从这里进入navtive层,art和dvm的不同处理也是从这里开始

xposed整个重新编译了自己的libart.so ,也对art有一些改动,比如阻止内联等,详情可以看这个issue
https://github.com/rovo89/Xposed/issues/160

xposedbridge中有一个native方法 hookMethodNative ,从这里进入native层,因为看的是art的hook原理,所以从libxposed_art.cpp开始看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void XposedBridge_hookMethodNative(JNIEnv* env, jclass, jobject javaReflectedMethod,
jobject, jint, jobject javaAdditionalInfo) {
// Detect usage errors.
ScopedObjectAccess soa(env);
if (javaReflectedMethod == nullptr) {
#if PLATFORM_SDK_VERSION >= 23
ThrowIllegalArgumentException("method must not be null");
#else
ThrowIllegalArgumentException(nullptr, "method must not be null");
#endif
return;
}

// Get the ArtMethod of the method to be hooked.
ArtMethod* artMethod = ArtMethod::FromReflectedMethod(soa, javaReflectedMethod);

// Hook the method
artMethod->EnableXposedHook(soa, javaAdditionalInfo);
}

javaAdditionalInfo里面放的就是hook的部分

这里比较关键的就是后面两句,分别获取了要被hook的方法的artmethod以及对其执行EnableXposedHook
然后来看一下EnableXposedHook在做什么

基于自己的理解,只留下了部分关键代码,可能会忽略某些重要信息,如果有错误请指出

art_method.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void ArtMethod::EnableXposedHook(ScopedObjectAccess& soa, jobject additional_info) {
....
// Create a backup of the ArtMethod object
将这个需要被hook的method做了一个备份
ArtMethod* backup_method = cl->CreateRuntimeMethod(linear_alloc);
backup_method->CopyFrom(this, cl->GetImagePointerSize());
backup_method->SetAccessFlags(backup_method->GetAccessFlags() | kAccXposedOriginalMethod);
....
// Save extra information in a separate structure, stored instead of the native method
将hook信息也存起来
XposedHookInfo* hook_info = reinterpret_cast<XposedHookInfo*>(linear_alloc->Alloc(soa.Self(), sizeof(XposedHookInfo)));
hook_info->reflected_method = soa.Vm()->AddGlobalRef(soa.Self(), reflected_method);
hook_info->additional_info = soa.Env()->NewGlobalRef(additional_info);
hook_info->original_method = backup_method;
....
然后将hook信息存到entry_point_from_jni_这个指针,这里的hook信息也包括了原先那个被备份的方法
SetEntryPointFromJniPtrSize(reinterpret_cast<uint8_t*>(hook_info), sizeof(void*));
替换函数入口点entry_point_from_quick_compiled_code_为自己的GetQuickProxyInvokeHandler
SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());
....
}

至此xposed的hook处理就大致结束,然后来跟一下方法调用的流程,还是直接从native部分开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
const char* shorty) {
....
if (UNLIKELY(!runtime->IsStarted() || Dbg::IsForcedInterpreterNeededForCalling(self, this))) {
....
} else {
DCHECK_EQ(runtime->GetClassLinker()->GetImagePointerSize(), sizeof(void*));

constexpr bool kLogInvocationStartAndReturn = false;
bool have_quick_code = GetEntryPointFromQuickCompiledCode() != nullptr;
if (LIKELY(have_quick_code)) {
....
if (!IsStatic()) {
(*art_quick_invoke_stub)(this, args, args_size, self, result, shorty);
} else {
(*art_quick_invoke_static_stub)(this, args, args_size, self, result, shorty);
}
} else {
....
}
}
....
}

还是只看关键的一小部分
跟进一下art_quick_invoke_stub,这里就要到汇编部分了,只看arm64架构下的代码qucik_entrypoints_arm64.S

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
ENTRY art_quick_invoke_stub
// Spill registers as per AACPS64 calling convention.
INVOKE_STUB_CREATE_FRAME

// Fill registers x/w1 to x/w7 and s/d0 to s/d7 with parameters.
// Parse the passed shorty to determine which register to load.
// Load addresses for routines that load WXSD registers.
adr x11, .LstoreW2
adr x12, .LstoreX2
adr x13, .LstoreS0
adr x14, .LstoreD0

// Initialize routine offsets to 0 for integers and floats.
// x8 for integers, x15 for floating point.
mov x8, #0
mov x15, #0

add x10, x5, #1 // Load shorty address, plus one to skip return value.
ldr w1, [x9],#4 // Load "this" parameter, and increment arg pointer.

// Loop to fill registers.
.LfillRegisters:
ldrb w17, [x10], #1 // Load next character in signature, and increment.
cbz w17, .LcallFunction // Exit at end of signature. Shorty 0 terminated.

cmp w17, #'F' // is this a float?
bne .LisDouble

cmp x15, # 8*12 // Skip this load if all registers full.
beq .Ladvance4

add x17, x13, x15 // Calculate subroutine to jump to.
br x17

.LisDouble:
cmp w17, #'D' // is this a double?
bne .LisLong

cmp x15, # 8*12 // Skip this load if all registers full.
beq .Ladvance8

add x17, x14, x15 // Calculate subroutine to jump to.
br x17

.LisLong:
cmp w17, #'J' // is this a long?
bne .LisOther

cmp x8, # 6*12 // Skip this load if all registers full.
beq .Ladvance8

add x17, x12, x8 // Calculate subroutine to jump to.
br x17

.LisOther: // Everything else takes one vReg.
cmp x8, # 6*12 // Skip this load if all registers full.
beq .Ladvance4

add x17, x11, x8 // Calculate subroutine to jump to.
br x17

.Ladvance4:
add x9, x9, #4
b .LfillRegisters

.Ladvance8:
add x9, x9, #8
b .LfillRegisters

// Macro for loading a parameter into a register.
// counter - the register with offset into these tables
// size - the size of the register - 4 or 8 bytes.
// register - the name of the register to be loaded.
.macro LOADREG counter size register return
ldr \register , [x9], #\size
add \counter, \counter, 12
b \return
.endm

// Store ints.
.LstoreW2:
LOADREG x8 4 w2 .LfillRegisters
LOADREG x8 4 w3 .LfillRegisters
LOADREG x8 4 w4 .LfillRegisters
LOADREG x8 4 w5 .LfillRegisters
LOADREG x8 4 w6 .LfillRegisters
LOADREG x8 4 w7 .LfillRegisters

// Store longs.
.LstoreX2:
LOADREG x8 8 x2 .LfillRegisters
LOADREG x8 8 x3 .LfillRegisters
LOADREG x8 8 x4 .LfillRegisters
LOADREG x8 8 x5 .LfillRegisters
LOADREG x8 8 x6 .LfillRegisters
LOADREG x8 8 x7 .LfillRegisters

// Store singles.
.LstoreS0:
LOADREG x15 4 s0 .LfillRegisters
LOADREG x15 4 s1 .LfillRegisters
LOADREG x15 4 s2 .LfillRegisters
LOADREG x15 4 s3 .LfillRegisters
LOADREG x15 4 s4 .LfillRegisters
LOADREG x15 4 s5 .LfillRegisters
LOADREG x15 4 s6 .LfillRegisters
LOADREG x15 4 s7 .LfillRegisters

// Store doubles.
.LstoreD0:
LOADREG x15 8 d0 .LfillRegisters
LOADREG x15 8 d1 .LfillRegisters
LOADREG x15 8 d2 .LfillRegisters
LOADREG x15 8 d3 .LfillRegisters
LOADREG x15 8 d4 .LfillRegisters
LOADREG x15 8 d5 .LfillRegisters
LOADREG x15 8 d6 .LfillRegisters
LOADREG x15 8 d7 .LfillRegisters


.LcallFunction:

INVOKE_STUB_CALL_AND_RETURN

END art_quick_invoke_stub

看不懂没关系,我也看不懂233333
只跟进一下最后面的这个function INVOKE_STUB_CALL_AND_RETURN

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
.macro INVOKE_STUB_CALL_AND_RETURN
下面这两条汇编比较简单,也涉及到了我们需要的流程,所以还是说一下
// load method-> METHOD_QUICK_CODE_OFFSET
将存储函数ART_METHOD_QUICK_CODE_OFFSET_64的地址放到x9寄存器里面
ldr x9, [x0, #ART_METHOD_QUICK_CODE_OFFSET_64]
// Branch to method.
跳转到x9寄存器处,即执行ART_METHOD_QUICK_CODE_OFFSET_64函数
blr x9

// Restore return value address and shorty address.
ldp x4,x5, [xFP, #16]
.cfi_restore x4
.cfi_restore x5

ldr x28, [xFP, #112]
.cfi_restore x28

ldp x26, x27, [xFP, #96]
.cfi_restore x26
.cfi_restore x27

ldp x24, x25, [xFP, #80]
.cfi_restore x24
.cfi_restore x25

ldp x22, x23, [xFP, #64]
.cfi_restore x22
.cfi_restore x23

ldp x20, x21, [xFP, #48]
.cfi_restore x20
.cfi_restore x21

// Store result (w0/x0/s0/d0) appropriately, depending on resultType.
ldrb w10, [x5]

// Check the return type and store the correct register into the jvalue in memory.
// Use numeric label as this is a macro and Clang's assembler does not have unique-id variables.

// Don't set anything for a void type.
cmp w10, #'V'
beq 3f

// Is it a double?
cmp w10, #'D'
bne 1f
str d0, [x4]
b 3f

1: // Is it a float?
cmp w10, #'F'
bne 2f
str s0, [x4]
b 3f

2: // Just store x0. Doesn't matter if it is 64 or 32 bits.
str x0, [x4]

3: // Finish up.
ldp x2, x19, [xFP, #32] // Restore stack pointer and x19.
.cfi_restore x19
mov sp, x2
.cfi_restore sp

ldp xFP, xLR, [xFP] // Restore old frame pointer and link register.
.cfi_restore x29
.cfi_restore x30

ret

.endm

然后来看一下ART_METHOD_QUICK_CODE_OFFSET_64函数

1
2
3
#define ART_METHOD_QUICK_CODE_OFFSET_64 48
ADD_TEST_EQ(ART_METHOD_QUICK_CODE_OFFSET_64,
art::ArtMethod::EntryPointFromQuickCompiledCodeOffset(8).Int32Value())

先不管这里怎么跳过去的,这里就进入了EntryPointFromQuickCompiledCodeOffset

1
2
3
4
static MemberOffset EntryPointFromQuickCompiledCodeOffset(size_t pointer_size) {
return MemberOffset(PtrSizedFieldsOffset(pointer_size) + OFFSETOF_MEMBER(
PtrSizedFields, entry_point_from_quick_compiled_code_) / sizeof(void*) * pointer_size);
}

这里就进入了entry_point_from_quick_compiled_code_

而在EnableXposedHook中,执行了SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler());
即将这个函数入口设置为GetQuickProxyInvokeHandler

下面看一下GetQuickProxyInvokeHandler的实现

1
2
3
4
5
// Return the address of quick stub code for handling transitions into the proxy invoke handler.
extern "C" void art_quick_proxy_invoke_handler();
static inline const void* GetQuickProxyInvokeHandler() {
return reinterpret_cast<const void*>(art_quick_proxy_invoke_handler);
}

art_quick_proxy_invoke_handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* Called by managed code that is attempting to call a method on a proxy class. On entry
* x0 holds the proxy method and x1 holds the receiver; The frame size of the invoked proxy
* method agrees with a ref and args callee save frame.
*/
.extern artQuickProxyInvokeHandler
ENTRY art_quick_proxy_invoke_handler
SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME_WITH_METHOD_IN_X0
mov x2, xSELF // pass Thread::Current
mov x3, sp // pass SP
bl artQuickProxyInvokeHandler // (Method* proxy method, receiver, Thread*, SP)
ldr x2, [xSELF, THREAD_EXCEPTION_OFFSET]
cbnz x2, .Lexception_in_proxy // success if no exception is pending
RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME // Restore frame
fmov d0, x0 // Store result in d0 in case it was float or double
ret // return on success
.Lexception_in_proxy:
RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME
DELIVER_PENDING_EXCEPTION
END art_quick_proxy_invoke_handler

artQuickProxyInvokeHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Handler for invocation on proxy methods. On entry a frame will exist for the proxy object method
// which is responsible for recording callee save registers. We explicitly place into jobjects the
// incoming reference arguments (so they survive GC). We invoke the invocation handler, which is a
// field within the proxy object, which will box the primitive arguments and deal with error cases.
extern "C" uint64_t artQuickProxyInvokeHandler(
ArtMethod* proxy_method, mirror::Object* receiver, Thread* self, ArtMethod** sp)
SHARED_REQUIRES(Locks::mutator_lock_) {
....
if (is_xposed) {
jmethodID proxy_methodid = soa.EncodeMethod(proxy_method);
self->EndAssertNoThreadSuspension(old_cause);
JValue result = InvokeXposedHandleHookedMethod(soa, shorty, rcvr_jobj, proxy_methodid, args);
local_ref_visitor.FixupReferences();
return result.GetJ();
}
....
}

InvokeXposedHandleHookedMethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
JValue InvokeXposedHandleHookedMethod(ScopedObjectAccessAlreadyRunnable& soa, const char* shorty,
jobject rcvr_jobj, jmethodID method,
std::vector<jvalue>& args) {
....
const XposedHookInfo* hook_info = soa.DecodeMethod(method)->GetXposedHookInfo();

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);
....
}
}

这个地方取出了hookinfo并且执行了CallStaticObjectMethodA函数,先看看GetXposedHookInfo()是怎么实现的

1
2
3
4
const XposedHookInfo* GetXposedHookInfo() {
DCHECK(IsXposedHookedMethod());
return reinterpret_cast<const XposedHookInfo*>(GetEntryPointFromJniPtrSize(sizeof(void*)));
}

这里就是取出EntryPointFromJniPtrSize的信息,而这个信息,就是在enablexposedhook中备份进去的
然后再看一下xposed_callback_class和xposed_callback_method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/** Called very early during VM startup. */
bool onVmCreated(JNIEnv*) {
// TODO: Handle CLASS_MIUI_RESOURCES?
ArtMethod::xposed_callback_class = classXposedBridge;
ArtMethod::xposed_callback_method = methodXposedBridgeHandleHookedMethod;
return true;
}

bool initXposedBridge(JNIEnv* env) {
classXposedBridge = env->FindClass(CLASS_XPOSED_BRIDGE);
if (classXposedBridge == NULL) {
ALOGE("Error while loading Xposed class '%s':", CLASS_XPOSED_BRIDGE);
logExceptionStackTrace();
env->ExceptionClear();
return false;
}
classXposedBridge = reinterpret_cast<jclass>(env->NewGlobalRef(classXposedBridge));
....

methodXposedBridgeHandleHookedMethod = env->GetStaticMethodID(classXposedBridge, "handleHookedMethod",
"(Ljava/lang/reflect/Member;ILjava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
....
return true;
}

这里就很明显了,对应de/robv/android/xposed/XposedBridge和handleHookedMethod这两个东西
至此,就返回到java层了,后面的步骤就和dvm上hook的情况相同,这里就不再写了。

关于一些其他部分的理解

延迟链接的处理

方法在不执行的时候,可能实际并没有加载。xposed hook了方法的入口点,即延迟加载以后,仍然需要进入这个入口点,处理了这个问题。