Hooking virtual methods for fun and profit
What do you need for hooking Java virtual methods? Suppose you want to hook the method A within class Z and divert the control flow to the new method B ( the “patch” method ) within class X. At least you have to:
- load X into application’s memory
- retrive A reference from memory and its position inside the Z vtable
- modify the A entry within Z vtable with B address
Using the GetMethodID function of Java Native Interface (JNI) we can obtain the A memory reference (suppose A is already loaded in memory,true for Android APIs). The GetMethodID function returns an jobject type, which is just an alias to ArtMethod data structure.
Using relative offset, we can access to elements inside the ArtMethod object returned by GetMethodID for retriving the information needed to retrive A index value inside Z vtable. Relative offset approach is stable and reliable because ART internals are very unlikely to be subject of OEM modifications.
To achieve point 1 we use the DexClassLoader API, the “patch” method B is defined by the user and loaded from a DEX file.
Once we have got the information scanning the memory, we can achieve point 3 using just simple memory operations by native code.
Using the A memory reference returned by GetMethodID , we can parse the ArtMethod structure and access to its following elements:
- declaring_class_
- method_index_
The former is a reference to Z (a Class object). This class contains the method A. The latter is the A’s index value inside Z vtable.
Following the declaring_class_ pointer, we can parse the Class object from memory to access its following elements:
- vtable_
- virtual_methods_
How does the runtime is looking up virtual methods?
First let’s try to follow the flow of a Java method called by JNI. As example we take in account the CallObjectMethod function, defined in jniinternals.c. This function is used for calling Java methods from native code, exactly only methods which returns an Object type.
(All following source code is taken from androidxref Android 6.0.1_r10 )
680 static jobject CallObjectMethod(JNIEnv* env, jobject obj, jmethodID mid, ...) {
681 va_list ap;
682 va_start(ap, mid);
683 CHECK_NON_NULL_ARGUMENT(obj);
684 CHECK_NON_NULL_ARGUMENT(mid);
685 ScopedObjectAccess soa(env);
686 JValue result(InvokeVirtualOrInterfaceWithVarArgs(soa, obj, mid, ap));
687 va_end(ap);
688 return soa.AddLocalReference<jobject>(result.GetL());
689 }
The called function InvokeVirtualOrInterfaceWithVarArgs (line 686) is defined in reflection.cc:
529JValue InvokeVirtualOrInterfaceWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa,
530 jobject obj, jmethodID mid, va_list args) {
[...]
539 mirror::Object* receiver = soa.Decode<mirror::Object*>(obj);
540 ArtMethod* method = FindVirtualMethod(receiver, soa.DecodeMethod(mid));
541 bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor();
[...]
547 uint32_t shorty_len = 0;
548 const char* shorty = method->GetInterfaceMethodIfProxy(sizeof(void*))->GetShorty(&shorty_len);
549 JValue result;
550 ArgArray arg_array(shorty, shorty_len);
551 arg_array.BuildArgArrayFromVarArgs(soa, receiver, args);
552 InvokeWithArgArray(soa, method, &arg_array, &result, shorty);
[...]
557 return result;
558}
At line 540 is retrived the ArtMethod identified by the jmethodID passed as third argument, the FindVirtualMethod function is defined in reflection.cc The real method invocation is the call to InvokeWithArgArray at line 552.
The FindVirtualMethod function is used to look up a virtual method inside its receiver object, following box shows the code.
420static ArtMethod* FindVirtualMethod(mirror::Object* receiver, ArtMethod* method)
421 SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
422 return receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(method, sizeof(void*));
423}
The above code calls function FindVirtualMethodForVirtualOrInterface (line 422) defined in class-inl.h to look up the target method. The following box show FindVirtualMethodForVirtualOrInterface code
399 inline ArtMethod* Class::FindVirtualMethodForVirtualOrInterface(ArtMethod* method,
400 size_t pointer_size) {
401 if (method->IsDirect()) {
402 return method;
403 }
404 if (method->GetDeclaringClass()->IsInterface() && !method->IsMiranda()) {
405 return FindVirtualMethodForInterface(method, pointer_size);
406 }
407 return FindVirtualMethodForVirtual(method, pointer_size);
408 }
FindVirtualMethodForVirtual, defined in class-inl.h, returns the method scanning the vtable_ array.
387 inline ArtMethod* Class::FindVirtualMethodForVirtual(ArtMethod* method, size_t pointer_size) {
388 DCHECK(!method->GetDeclaringClass()->IsInterface() || method->IsMiranda());
389 // The argument method may from a super class.
390 // Use the index to a potentially overridden one for this instance's class.
391 return GetVTableEntry(method->GetMethodIndex(), pointer_size);
392 }
Analyzing the GetVtableEntry code, still defined in class-inl.h, we can see a call to function ShouldHaveEmbeddedImtAndVTable. If the returned result is True the vtable is embedded within the class, otherwise it is retrived calling the function GetVTable() (line 184)
180inline ArtMethod* Class::GetVTableEntry(uint32_t i, size_t pointer_size) {
181 if (ShouldHaveEmbeddedImtAndVTable()) {
182 return GetEmbeddedVTableEntry(i, pointer_size);
183 }
184 auto* vtable = GetVTable();
185 DCHECK(vtable != nullptr);
186 return vtable->GetElementPtrSize<ArtMethod*>(i, pointer_size);
187}
So, what do the function ShouldHaveEmbeddedImtAndVTable do?
754 bool ShouldHaveEmbeddedImtAndVTable() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
755 return IsInstantiable();
756 }
454 bool IsInstantiable() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
455 return (!IsPrimitive() && !IsInterface() && !IsAbstract()) ||
456 (IsAbstract() && IsArrayClass());
457 }
The GetVTable code is showed in following box:
138 inline PointerArray* Class::GetVTable() {
139 DCHECK(IsResolved() || IsErroneous());
140 return GetFieldObject<PointerArray>(OFFSET_OF_OBJECT_MEMBER(Class, vtable_));
141 }
Ok now we know that virtual methods called by JNI are looked up using the vtable_ array (embedded or not).
Let’s me try to figure out how the runtime invoke methods called by reflection. The function involved in reflection calls is InvokeMethod defined in reflection.cc. The target method to invoke is looked up using the following code (line 600):
586 if (!m->IsStatic()) {
587 // Replace calls to String.<init> with equivalent StringFactory call.
588 if (declaring_class->IsStringClass() && m->IsConstructor()) {
589 jmethodID mid = soa.EncodeMethod(m);
590 m = soa.DecodeMethod(WellKnownClasses::StringInitToStringFactoryMethodID(mid));
591 CHECK(javaReceiver == nullptr);
592 } else {
593 // Check that the receiver is non-null and an instance of the field's declaring class.
594 receiver = soa.Decode<mirror::Object*>(javaReceiver);
595 if (!VerifyObjectIsClass(receiver, declaring_class)) {
596 return nullptr;
597 }
598
599 // Find the actual implementation of the virtual method.
600 m = receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(m, sizeof(void*));
601 }
602 }
Finally, the looked up method is invoked at line 640:
630 // Invoke the method.
631 JValue result;
632 uint32_t shorty_len = 0;
633 const char* shorty = np_method->GetShorty(&shorty_len);
634 ArgArray arg_array(shorty, shorty_len);
635 if (!arg_array.BuildArgArrayFromObjectArray(receiver, objects, np_method)) {
636 CHECK(soa.Self()->IsExceptionPending());
637 return nullptr;
638 }
639
640 InvokeWithArgArray(soa, m, &arg_array, &result, shorty);
Once we know how the runtime is lookuping virtual methods, we can exploit this mechanism to achieve Java virtual methods hooking.
Next post introduces ARTDroid, an easy-to-use library for hooking virtual methods calls under ART runtime.