0x1
¿ªÊ¼
AnddroidÉϵÄART´Ó5.0Ö®ºó±ä³ÉĬÈϵÄÑ¡Ôñ£¬¿É¼ûARTµÄÖØÒªÐÔ£¬Ä¿Ç°¹ØÓÚDalvik Hook·½ÃæÑо¿µÄÎÄÕºܶ࣬µ«ÎÒÔÚÍøÉÏÈ´ÕÒ²»µ½¹ØÓÚART
HookÏà¹ØµÄÎÄÕ£¬ÉõÖÁÁ¬¶¦¶¦´óÃûµÄXPosedºÍCydia Substrateµ½Ä¿Ç°ÎªÖ¹Ò²²»Ö§³ÖARTµÄHook¡£µ±È»ÎÒÏàÐÅ£¬¼¼Êõ·½°¸ËûÃǿ϶¨Êǵ쬹À¼Æ¿¨ÔÚ»úÐÍÊÊÅäÉϵÄÁË¡£
¼ÈÈ»ÍøÉÏÕÒ²»µ½Ïà¹ØµÄ×ÊÁÏ£¬ÓÚÊÇÎÒ¾ö¶¨×Ô¼º»¨Ð©Ê±¼äÈ¥Ñо¿Ò»Ï£¬ÖÕÓÚüSÌì²»¸ºÓÐÐÄÈË£¬ÎÒÕÒµ½ÁËÒ»¸öÇÐʵ¿ÉÐеķ½·¨£¬¼´±¾ÎÄËù½éÉܵķ½·¨¡£
Ó¦¸Ã˵Ã÷µÄÊDZ¾ÎÄËù½éÉܵķ½·¨¿Ï¶¨²»ÊÇ×îºÃµÄ£¬µ«´ó¼Ò¿´Íê±¾ÎÄÖ®ºó£¬Èç¹ûÄÜÆô·¢´ó¼ÒÕÒµ½¸üºÃµÄART Hook·½·¨£¬ÄÇÎÒÅ×שÒýÓñµÄÄ¿µÄ¾Í´ïµ½ÁË¡£·Ï»°²»¶à˵£¬ÎÒÃÇ¿ªÊ¼°É¡£
ÔËÐл·¾³: 4.4.2 ARTģʽµÄÄ£ÄâÆ÷
¿ª·¢»·¾³: Mac OS X 10.10.3
0x2 ARTÀà·½·¨¼ÓÔØ¼°Ö´ÐÐ
ÔÚARTÖÐÀà·½·¨µÄÖ´ÐÐÒª±ÈÔÚDalvikÖÐÒª¸´Ôӵö࣬DalvikÈç¹û³ýÈ¥JIT²¿·Ö£¬¿ÉÒÔÀí½âΪÊÇÒ»¸ö½âÎöÖ´ÐеÄÐéÄâ»ú£¬¶øARTÔòͬʱ°üº¬±¾µØÖ¸ÁîÖ´ÐкͽâÎöÖ´ÐÐÁ½ÖÖģʽ£¬Í¬Ê±ËùÉú³ÉµÄoatÎļþÒ²°üº¬Á½ÖÖÀàÐÍ£¬·Ö±ðÊÇportableºÍquick¡£portableºÍquickµÄÖ÷񻂿±ðÊǶÔÓÚ·½·¨µÄ¼ÓÔØ»úÖÆ²»Ïàͬ£¬quick´óÁ¿Ê¹ÓÃÁËLazy
Load»úÖÆ£¬Òò´ËÓ¦ÓÃµÄÆô¶¯Ëٶȸü¿ì£¬µ«¼ÓÔØÁ÷³Ì¸ü¸´ÔÓ¡£ÆäÖÐquickÊÇ×÷ΪĬÈÏÑ¡ÏÒò´Ë±¾ÎÄËùÉæ¼°µÄ¼¼Êõ·ÖÎö¶¼ÊÇ»ùÓÚquickÀàÐ͵ġ£
ÓÉÓÚART´æÔÚ±¾µØÖ¸ÁîÖ´ÐкͽâÎöÖ´ÐÐÁ½ÖÖģʽ£¬Òò´ËÀà·½·¨Ö®¼ä²¢²»ÊÇÄÜÖ±½ÓÌø×ªµÄ£¬¶øÊÇͨ¹ýһЩԤÏȶ¨ÒåµÄbridgeº¯Êý½øÐÐ״̬ºÍÉÏÏÂÎĵÄÇл»£¬ÕâÀïÒýÓÃÒ»ÏÂÀÏÂÞ²©¿ÍÖеÄʾÒâͼ£º
µ±Ö´ÐÐij¸ö·½·¨Ê±£¬Èç¹ûµ±Ç°ÊDZ¾µØÖ¸ÁîÖ´ÐÐģʽ£¬Ôò»áÖ´ÐÐArtMethod::GetEntryPointFromCompiledCode()Ö¸ÏòµÄº¯Êý£¬·ñÔòÔòÖ´ÐÐArtMethod::GetEntryPointFromInterpreter()Ö¸ÏòµÄº¯Êý¡£Òò´Ëÿ¸ö·½·¨£¬¶¼ÓÐÁ½¸öÈë¿Úµã£¬·Ö±ð±£´æÔÚArtMethod::entry_point_from_compiled_code_ºÍArtMethod::entry_point_from_interpreter_¡£Á˽âÕâÒ»µã·Ç³£ÖØÒª£¬ºóÃæÎÒÃÇÖ÷Òª¾ÍÊÇÔÚÕâÁ½¸öÈë¿Ú×öÎÄÕ¡£
ÔÚ½²ÊöÔÀí֮ǰ£¬ÐèÒªÏȰÑÒÔÏÂÁ½¸öÁ÷³ÌÁ˽âÇå³þ£¬ÕâÀïµÄÄÚÈÝÒªÕ¹¿ªÊǷdz£ÅÓ´óµÄ£¬ÎÒÕë¶ÔHookµÄ¹Ø¼üµã£¬¼òÃ÷¶óÒªµÄÃèÊöһϣ¬µ«»¹ÊÇÇ¿ÁÒ½¨Òé´ó¼ÒÈ¥ÀÏÂ޵IJ©¿ÍÀïϸ¶ÁÒ»ÏÂÆäÖйØÓÚARTµÄ¼¸ÆªÎÄÕ¡£
ArtMethod¼ÓÔØÁ÷³Ì
Õâ¸ö¹ý³Ì·¢ÉúÔÚoat±»×°ÔؽøÄÚ´æ²¢½øÐÐÀà·½·¨Á´½ÓµÄʱºò£¬Àà·½·¨Á´½ÓµÄ´úÂëÔÚart/runtime/class_linker.ccÖеÄLinkCode£¬ÈçÏÂËùʾ£º
static void LinkCode(SirtRef<mirror::ArtMethod>& method, const OatFile::OatClass* oat_class, uint32_t method_index) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
// Method shouldn't have already been linked.
DCHECK(method->GetEntryPointFromCompiledCode()
== NULL);
// Every kind of method should at least get an
invoke stub from the oat_method.
// non-abstract methods also get their code pointers.
const OatFile::OatMethod oat_method = oat_class->GetOatMethod(method_index);
// ÕâÀïĬÈÏ»á°Ñmethod::entry_point_from_compiled_code_ÉèÖÃoatmethodµÄcode
oat_method.LinkMethod(method.get());
// Install entry point from interpreter.
Runtime* runtime = Runtime::Current();
bool enter_interpreter = NeedsInterpreter(method.get(),
method->GetEntryPointFromCompiledCode());
//ÅжϷ½·¨ÊÇ·ñÐèÒª½âÎöÖ´ÐÐ
// ÉèÖýâÎöÖ´ÐеÄÈë¿Úµã
if (enter_interpreter) {
method->SetEntryPointFromInterpreter(interpreter::artInterpreterToInterpreterBridge);
} else {
method->SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge);
}
// ÏÂÃæÊÇÉèÖñ¾µØÖ¸ÁîÖ´ÐеÄÈë¿Úµã
if (method->IsAbstract()) {
method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
return;
}
// ÕâÀï±È½ÏÄÑÀí½â£¬Èç¹ûÊǾ²Ì¬·½·¨£¬µ«²»ÊÇclinit£¬µ«ÐèÒª°Ñentry_point_from_compiled_code_ÉèÖÃΪGetResolutionTrampolineµÄ·µ»ØÖµ
if (method->IsStatic() && !method->IsConstructor())
{
// For static methods excluding the class initializer,
install the trampoline.
// It will be replaced by the proper entry point
by ClassLinker::FixupStaticTrampolines
// after initializing class (see ClassLinker::InitializeClass
method).
method->SetEntryPointFromCompiledCode(GetResolutionTrampoline(runtime->GetClassLinker()));
} else if (enter_interpreter) {
// Set entry point from compiled code if there's
no code or in interpreter only mode.
method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
}
if (method->IsNative()) {
// Unregistering restores the dlsym lookup stub.
method->UnregisterNative(Thread::Current());
}
// Allow instrumentation its
chance to hijack code.
runtime->GetInstrumentation()->UpdateMethodsCode(method.get(),
method->GetEntryPointFromCompiledCode());
} |
ͨ¹ýÉÏÃæµÄ´úÂëÎÒÃÇ¿ÉÒԵõ½£¬Ò»¸öArtMethodµÄÈë¿ÚÖ÷ÒªÓÐÒÔϼ¸ÖÖ£º
Interpreter2Interpreter¶ÔÓ¦artInterpreterToInterpreterBridge(art/runtime/interpreter/interpreter.cc); Interpreter2CompledCode¶ÔÓ¦
artInterpreterToCompiledCodeBridge(/art/runtime/entrypoints/interpreter/interpreter_entrypoints.cc); CompliedCode2Interpreter¶ÔÓ¦art_quick_to_interpreter_bridge(art/runtime/arch/arm/quick_entrypoints_arm.S); CompliedCode2ResolutionTrampoline¶ÔÓ¦art_quick_resolution_trampoline(art/runtime/arch/arm/quick_entrypoints_arm.S); CompliedCode2CompliedCodeÕâ¸öÈë¿ÚÊÇÖ±½ÓÖ¸ÏòoatÖеÄÖ¸ÁÏêϸ¿É¼ûOatMethod::LinkMethod; |
ÆäÖе÷ÓÃÔ¼¶¨Ö÷ÒªÓÐÁ½ÖÖ£¬·Ö±ðÊÇ£º
typedef void (EntryPointFromInterpreter)(Thread* self,
MethodHelper& mh, const DexFile::CodeItem* code_item,
ShadowFrame* shadow_frame, JValue* result), ÕâÖÖ¶ÔÓ¦ÉÏÊö1£¬3Á½ÖÖÈë¿Ú£»
ʣϵÄ2£¬4£¬5ÈýÖÖÈë¿Ú¶ÔÓ¦µÄÊÇCompledCodeµÄÈë¿Ú£¬´úÂëÖв¢Ã»ÓÐÖ±½Ó¸ø³ö£¬µ«ÎÒÃÇͨ¹ý·ÖÎöArtMethod::InvokeµÄ·½·¨µ÷Ó㬾ͿÉÒÔÖªµÀÆäµ÷ÓÃÔ¼¶¨ÁË¡£Invoke¹ý³ÌÖлáµ÷ÓÃart_quick_invoke_stub(/art/runtime/arch/arm/quick_entrypoints_arm.S)£¬´úÂëÈçÏÂËùʾ£º
/* * Quick invocation stub. * On entry: * r0 = method pointer * r1 = argument array or NULL for no argument methods * r2 = size of argument array in bytes * r3 = (managed) thread pointer * [sp] = JValue* result * [sp + 4] = result type char */ ENTRY art_quick_invoke_stub push {r0, r4, r5, r9, r11, lr} @ spill regs .save {r0, r4, r5, r9, r11, lr} .pad #24 .cfi_adjust_cfa_offset 24 .cfi_rel_offset r0, 0 .cfi_rel_offset r4, 4 .cfi_rel_offset r5, 8 .cfi_rel_offset r9, 12 .cfi_rel_offset r11, 16 .cfi_rel_offset lr, 20 mov r11, sp @ save the stack pointer .cfi_def_cfa_register r11 mov r9, r3 @ move managed thread pointer into r9 mov r4, #SUSPEND_CHECK_INTERVAL @ reset r4 to suspend check interval add r5, r2, #16 @ create space for method pointer in frame and r5, #0xFFFFFFF0 @ align frame size to 16 bytes sub sp, r5 @ reserve stack space for argument array add r0, sp, #4 @ pass stack pointer + method ptr as dest for memcpy bl memcpy @ memcpy (dest, src, bytes) ldr r0, [r11] @ restore method* ldr r1, [sp, #4] @ copy arg value for r1 ldr r2, [sp, #8] @ copy arg value for r2 ldr r3, [sp, #12] @ copy arg value for r3 mov ip, #0 @ set ip to 0 str ip, [sp] @ store NULL for method* at bottom of frame ldr ip, [r0, #METHOD_CODE_OFFSET] @ get pointer to the code blx ip @ call the method mov sp, r11 @ restore the stack pointer ldr ip, [sp, #24] @ load the result pointer strd r0, [ip] @ store r0/r1 into result pointer pop {r0, r4, r5, r9, r11, lr} @ restore spill regs .cfi_adjust_cfa_offset -24 bx lr END art_quick_invoke_stub |
¡°ldr ip, [r0, #METHOD_CODE_OFFSET]¡±Æäʵ¾ÍÊǰÑArtMethod::entry_point_from_compiled_code_¸³Öµ¸øip£¬È»ºóͨ¹ýblxÖ±½Óµ÷Óá£Í¨¹ýÕâ¶ÎССµÄ»ã±à´úÂ룬ÎÒÃǵóöÈç϶ÑÕ»µÄ²¼¾Ö£º
-(low) | caller(Method *) | <- sp | arg1 | <- r1 | arg2 | <- r2 | arg3 | <- r3 | ... | | argN | | callee(Method *) | <- r0 +(high) |
ÕâÖÖµ÷ÓÃÔ¼¶¨²¢²»ÊÇÆ½Ê±ÎÒÃÇËù¼ûµÄµ÷ÓÃÔ¼¶¨£¬Ö÷ÒªÌåÏÖÔÚ²ÎÊýµ±³¬¹ý4ʱ£¬²¢²»ÊÇ´Ósp¿ªÊ¼±£´æ£¬¶øÊÇ´Ósp
+ 20Õâ¸öλÖÿªÊ¼´æ´¢£¬ËùÒÔÕâ¾ÍÊÇΪʲôÔÚ´úÂëÀïentry_point_from_compiled_code_µÄÀàÐÍÊÇvoid
*µÄÔÒòÁË£¬ÒòΪÎÞ·¨ÓôúÂë±íʾ¡£
Àí½âºÃÕâ¸öµ÷ÓÃÔ¼¶¨¶ÔÎÒÃÇ·½°¸µÄʵÏÖÖÁ¹ØÖØÒª¡£
ArtMethodÖ´ÐÐÁ÷³Ì
ÉÏÃæÏêϸ½²ÊöÁËÀà·½·¨¼ÓÔØºÍÁ´½ÓµÄ¹ý³Ì£¬µ«ÔÚʵ¼ÊÖ´ÐеĹý³ÌÖУ¬Æäʵ»¹²»ÊÇÖ±½Óµ÷ÓÃArtMethodµÄentry_point(½âÎöÖ´Ðкͱ¾µØÖ¸ÁîÖ´ÐеÄÈë¿Ú)£¬ÎªÁ˼ӿìÖ´ÐÐËÙ¶È£¬ARTΪoatÎļþÖеÄÿ¸ödex´´½¨ÁËÒ»¸öDexCache£¨art/runtime/mirror/dex_cache.h£©½á¹¹£¬Õâ¸ö½á¹¹»á°´dexµÄ½á¹¹Éú³ÉһϵÁеÄÊý×飬ÕâÀïÎÒÃÇÖ»·ÖÎöËüÀïÃæµÄmethods×ֶΡ£
DexCache³õʼ»¯µÄ·½·¨ÊÇInit£¬ÊµÏÖÈçÏ£º
void DexCache::Init(const DexFile* dex_file, String* location, ObjectArray<String>* strings, ObjectArray<Class>* resolved_types, ObjectArray<ArtMethod>* resolved_methods, ObjectArray<ArtField>* resolved_fields, ObjectArray<StaticStorageBase>* initialized_static_storage) { //... //... Runtime* runtime = Runtime::Current(); if (runtime->HasResolutionMethod()) { // Initialize the resolve methods array to contain trampolines for resolution. ArtMethod* trampoline = runtime->GetResolutionMethod(); size_t length = resolved_methods->GetLength(); for (size_t i = 0; i < length; i++) { resolved_methods->SetWithoutChecks(i, trampoline); } } } |
¸ù¾Ýdex·½·¨µÄ¸öÊý£¬²úÉúÏàÓ¦³¤¶Èresolved_methodsÊý×飬Ȼºóÿһ¸ö¶¼ÓÃRuntime::GetResolutionMethod()·µ»ØµÄ½á¹û½øÐÐÌî³ä£¬Õâ¸ö·½·¨ÊÇÓÉRuntime::CreateResolutionMethod²úÉúµÄ£¬´úÂëÈçÏ£º
mirror::ArtMethod* Runtime::CreateResolutionMethod() { mirror::Class* method_class = mirror::ArtMethod::GetJavaLangReflectArtMethod(); Thread* self = Thread::Current(); SirtRef<mirror::ArtMethod> method(self, down_cast<mirror::ArtMethod*>(method_class->AllocObject(self))); method->SetDeclaringClass(method_class); // TODO: use a special method for resolution method saves method->SetDexMethodIndex(DexFile::kDexNoIndex); // When compiling, the code pointer will get set later when the image is loaded. Runtime* r = Runtime::Current(); ClassLinker* cl = r->GetClassLinker(); method->SetEntryPointFromCompiledCode(r->IsCompiler() ? NULL : GetResolutionTrampoline(cl)); return method.get(); } |
´Ómethod->SetDexMethodIndex(DexFile::kDexNoIndex)Õâ¾äµÃÖª£¬ËùÓеÄResolutionMethodµÄmethodIndexDexFile::kDexNoIndex¡£¶øResolutionMethodµÄentrypoint¾ÍÊÇÎÒÃÇÉÏÃæÈë¿Ú·ÖÎöÖеĵÚ4ÖÖÇé¿ö£¬GetResolutionTrampoline×îÖÕ·µ»ØµÄÈë¿ÚΪart_quick_resolution_trampoline(art/runtime/arch/arm/quick_entrypoints_arm.S)¡£ÎÒÃÇ¿´Ò»ÏÂÆäʵÏÖ´úÂ룺
.extern artQuickResolutionTrampoline ENTRY art_quick_resolution_trampoline SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME mov r2, r9 @ pass Thread::Current mov r3, sp @ pass SP blx artQuickResolutionTrampoline @ (Method* called, receiver, Thread*, SP) cbz r0, 1f @ is code pointer null? goto exception mov r12, r0 ldr r0, [sp, #0] @ load resolved method in r0 ldr r1, [sp, #8] @ restore non-callee save r1 ldrd r2, [sp, #12] @ restore non-callee saves r2-r3 ldr lr, [sp, #44] @ restore lr add sp, #48 @ rewind sp .cfi_adjust_cfa_offset -48 bx r12 @ tail-call into actual code 1: RESTORE_REF_AND_ARGS_CALLEE_SAVE_FRAME DELIVER_PENDING_EXCEPTION END art_quick_resolution_trampoline |
ÕûºÃ¼Ä´æÆ÷ºó£¬Ö±½ÓÌø×ªÖÁartQuickResolutionTrampoline(art/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc)£¬½ÓÏÂÀ´ÎÒÃÇ·ÖÎöÕâ¸ö·½·¨µÄʵÏÖ£¨´ó¼Ò²»ÒªÔÎÁË¡£¡£¡££¬ÎÒ»á°ÑÎ޹ؽôÒªµÄ´úÂëÈ¥µô£©£º
// Lazily resolve a method for quick. Called by stub code. extern "C" const void* artQuickResolutionTrampoline(mirror::ArtMethod* called, mirror::Object* receiver, Thread* thread, mirror::ArtMethod** sp) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { FinishCalleeSaveFrameSetup(thread, sp, Runtime::kRefsAndArgs); // Start new JNI local reference state JNIEnvExt* env = thread->GetJniEnv(); ScopedObjectAccessUnchecked soa(env); ScopedJniEnvLocalRefState env_state(env); const char* old_cause = thread->StartAssertNoThreadSuspension("Quick method resolution set up");
// Compute details about the called method (avoid
GCs)
ClassLinker* linker = Runtime::Current()->GetClassLinker();
mirror::ArtMethod* caller = QuickArgumentVisitor::GetCallingMethod(sp);
InvokeType invoke_type;
const DexFile* dex_file;
uint32_t dex_method_idx;
if (called->IsRuntimeMethod()) {
//...
//...
} else {
invoke_type = kStatic;
dex_file = &MethodHelper(called).GetDexFile();
dex_method_idx = called->GetDexMethodIndex();
}
//...
// Resolve method filling in dex cache.
if (called->IsRuntimeMethod()) {
called = linker->ResolveMethod(dex_method_idx,
caller, invoke_type);
}
const void* code = NULL;
if (LIKELY(!thread->IsExceptionPending()))
{
//...
linker->EnsureInitialized(called_class, true,
true);
//...
}
// ...
return code;
} |
inline bool ArtMethod::IsRuntimeMethod() const { return GetDexMethodIndex() == DexFile::kDexNoIndex; } |
called->IsRuntimeMethod()ÓÃÓÚÅжϵ±Ç°·½·¨ÊÇ·ñΪResolutionMethod¡£Èç¹ûÊÇ£¬ÄÇô¾Í×ßClassLinker::ResolveMethodÁ÷³ÌÈ¥»ñÈ¡ÕæÕýµÄ·½·¨£¬¼û´úÂ룺
mirror::ArtMethod* ClassLinker::ResolveMethod(const DexFile& dex_file, uint32_t method_idx, mirror::DexCache* dex_cache, mirror::ClassLoader* class_loader, const mirror::ArtMethod* referrer, InvokeType type) { DCHECK(dex_cache != NULL); // Check for hit in the dex cache. mirror::ArtMethod* resolved = dex_cache->GetResolvedMethod(method_idx); if (resolved != NULL) { return resolved; } // Fail, get the declaring class. const DexFile::MethodId& method_id = dex_file.GetMethodId(method_idx); mirror::Class* klass = ResolveType(dex_file, method_id.class_idx_, dex_cache, class_loader);
if (klass == NULL) {
DCHECK(Thread::Current()->IsExceptionPending());
return NULL;
}
// Scan using method_idx, this saves string
compares but will only hit for matching dex
// caches/files.
switch (type) {
case kDirect: // Fall-through.
case kStatic:
resolved = klass->FindDirectMethod(dex_cache,
method_idx);
break;
case kInterface:
resolved = klass->FindInterfaceMethod(dex_cache,
method_idx);
DCHECK(resolved == NULL || resolved->GetDeclaringClass()->IsInterface());
break;
case kSuper: // Fall-through.
case kVirtual:
resolved = klass->FindVirtualMethod(dex_cache,
method_idx);
break;
default:
LOG(FATAL) << "Unreachable - invocation
type: " << type;
}
if (resolved == NULL) {
// Search by name, which works across dex files.
const char* name = dex_file.StringDataByIdx(method_id.name_idx_);
std::string signature(dex_file.CreateMethodSignature(method_id.proto_idx_,
NULL));
switch (type) {
case kDirect: // Fall-through.
case kStatic:
resolved = klass->FindDirectMethod(name, signature);
break;
case kInterface:
resolved = klass->FindInterfaceMethod(name,
signature);
DCHECK(resolved == NULL || resolved->GetDeclaringClass()->IsInterface());
break;
case kSuper: // Fall-through.
case kVirtual:
resolved = klass->FindVirtualMethod(name, signature);
break;
}
}
if (resolved != NULL) {
// Be a good citizen and update the dex cache
to speed subsequent calls.
dex_cache->SetResolvedMethod(method_idx, resolved);
return resolved;
} else {
// ...
}
}
|
ÆäʵÕâÀï·¢ÉúÁË¡°Á¬Ëø·´Ó¦¡±£¬ClassLinker::ResolveType×ßµÄÁ÷³Ì£¬¸úResolveMethodÊǷdz£ÀàËÆµÄ£¬ÓÐÐËȤµÄÅóÓÑ¿ÉÒÔ¸úһϡ£
ÕÒµ½½âÎöºóµÄklass£¬ÔÙ¾¹ýÒ»ÂÖ·è¿ñµÄËÑË÷£¬°ÑÕÒµ½µÄresolvedͨ¹ýDexCache::SetResolvedMethod¸²¸Çµô֮ǰµÄ¡°ÌæÉí¡±¡£µ±ÔÙÏ´ÎÔÙͨ¹ýResolveMethod½âÎö·½·¨Ê±£¬¾Í¿ÉÒÔÖ±½Ó°Ñ¸Ã·½·¨·µ»Ø£¬²»ÐèÒªÔÙ½âÎöÁË¡£
ÎÒÃǻعýÍ·À´ÔÙÖØÐ¡°¸´ÏÖ¡±Ò»ÏÂÕâ¸ö¹ý³Ì£¬µ±ÎÒÃÇÊ״ε÷ÓÃij¸öÀà·½·¨£¬Æä¹ý³ÌÈçÏÂËùʾ£º
µ÷ÓÃResolutionMethodµÄentrypoint£¬½øÈëart_quick_resolution_trampoline£» art_quick_resolution_trampolineÌø×ªµ½artQuickResolutionTrampoline£» artQuickResolutionTrampolineµ÷ÓÃClassLinker::ResolveMethod½âÎöÀà·½·¨£» ClassLinker::ResolveMethodµ÷ÓÃClassLinkder::ResolveType½âÎöÀ࣬ÔÙ´Ó½âÎöºÃµÄÀàѰÕÒÕæÕýµÄ·½·¨£» µ÷ÓÃDexCache::SetResolvedMethod£¬ÓÃÕæÕýµÄ·½·¨¸²¸Çµô¡°ÌæÉí¡±·½·¨£» µ÷ÓÃÕæÕý·½·¨µÄentrypoint´úÂ룻 |
Ò²ÐíÄã»áÎÊ£¬ÎªÊ²Ã´Òª°Ñ¹ý³Ì¸ãµÃÕâÃ´ÈÆ£¿ Ò»Çж¼ÊÇΪÁËÑÓ³Ù¼ÓÔØ£¬Ìá¸ßÆô¶¯ËÙ¶È£¬Õâ¸ö¹ý³Ì¸úELF
LinkerµÄPLT/GOT·ûºÅÖØ¶¨ÏòµÄ¹ý³ÌÊÇºÎÆäÏàËÆ°¡£¬ËùÒÔ¼¼Êõ¶¼ÊÇÏëͨµÄ£¬Ò»Í¨°ÙÃ÷¡£
0x3 Hook ArtMethod
ͨ¹ýÉÏÊöArtMethod¼ÓÔØºÍÖ´ÐÐÁ½¸öÁ÷³ÌµÄ·ÖÎö£¬¶ÔÓÚÈçºÎHook ArtMethod£¬ÎÒÏëµ½ÁËÁ½¸ö·½°¸£¬·Ö±ð
ÐÞ¸ÄDexCachÀïµÄmethods£¬°ÑÀïÃæµÄentrypointÐÞ¸ÄΪ×Ô¼ºµÄ£¬×öÒ»¸öÖÐת´¦Àí£»
Ö±½ÓÐ޸ļÓÔØºóµÄArtMethodµÄentrypoint£¬Í¬Ñù×öÒ»¸öÖÐת´¦Àí£»
ÉÏÃæÁ½¸ö·½·¨¶¼ÊÇ¿ÉÐе쬵«ÓÉÓÚÎÒÏ£ÍûÕû¸öÏîÄ¿¿ÉÒÔÔÚNDK»·¾³(¶ø²»ÊÇÔÚÔ´ÂëÏÂ)ϱàÒ룬ÒòΪ¾Í²ÉÓÃÁË·½°¸2£¬ÒòΪͨ¹ýJNIµÄ½Ó¿Ú¾Í¿ÉÒÔÖ±½Ó»ñÈ¡½âÎöÖ®ºóµÄArtMethod£¬¿ÉÒÔ¼õÉٺܶàÎļþÒÀÀµ¡£
»Øµ½Ç°ÃæµÄµ÷ÓÃÔ¼¶¨£¬Ã¿¸öArtMethod¶¼ÓÐÁ½¸öÔ¼¶¨£¬°´µÀÀíÎÒÃÇÓ¦¸Ã×¼±¸Á½¸öÖÐתº¯ÊýµÄ£¬µ«ÕâÀïÎÒÃDz»¿¼ÂÇÇ¿ÖÆ½âÎöģʽִÐУ¬ËùÒÔÖ»Òª´¦ÀíºÃentry_point_from_compiled_codeµÄÖÐת¼´¿É¡£
Ê×ÏÈ£¬ÎÒÃÇÕÒµ½¶ÔÓ¦µÄ·½·¨£¬Ïȱ£´æÆäentrypoint£¬È»ºóÔÙ°ÑÎÒÃǵÄÖÐתº¯Êýart_quick_dispatcher¸²¸Ç£¬´úÂëÈçÏÂËùʾ£º
extern int __attribute__ ((visibility ("hidden"))) art_java_method_hook(JNIEnv* env, HookInfo *info) { const char* classDesc = info->classDesc; const char* methodName = info->methodName; const char* methodSig = info->methodSig; const bool isStaticMethod = info->isStaticMethod;
// TODO we can find class by special classloader
what do just like dvm
jclass claxx = env->FindClass(classDesc);
if(claxx == NULL){
LOGE("[-] %s class not found", classDesc);
return -1;
}
jmethodID methid = isStaticMethod ?
env->GetStaticMethodID(claxx, methodName, methodSig)
:
env->GetMethodID(claxx, methodName, methodSig);
if(methid == NULL){
LOGE("[-] %s->%s method not found",
classDesc, methodName);
return -1;
}
ArtMethod *artmeth = reinterpret_cast<ArtMethod
*>(methid);
if(art_quick_dispatcher != artmeth->GetEntryPointFromCompiledCode()){
uint64_t (*entrypoint)(ArtMethod* method, Object
*thiz, u4 *arg1, u4 *arg2);
entrypoint = (uint64_t (*)(ArtMethod*, Object
*, u4 *, u4 *))artmeth->GetEntryPointFromCompiledCode();
info->entrypoint = (const void *)entrypoint;
info->nativecode = artmeth->GetNativeMethod();
artmeth->SetEntryPointFromCompiledCode((const
void *)art_quick_dispatcher);
// save info to nativecode :)
artmeth->SetNativeMethod((const void *)info);
LOGI("[+] %s->%s was hooked\n",
classDesc, methodName);
}else{
LOGW("[*] %s->%s method had been hooked",
classDesc, methodName);
}
return 0;
} |
ÎÒÃǹؼüµÄÐÅϢͨ¹ýArtMethod::SetNativeMethod±£´æÆðÀ´ÁË¡£
¿¼Âǵ½ARTÌØÊâµÄµ÷ÓÃÔ¼¶¨£¬art_quick_dispatcherÖ»ÄÜÓûã±àʵÏÖÁË£¬°Ñ¼Ä´æÆ÷Êʵ±µÄµ÷Õûһϣ¬ÔÙÌø×ªµ½ÁíÒ»¸öº¯ÊýartQuickToDispatcher£¬ÕâÑù¾Í¿ÉÒԺܷ½±ãÓÃc/c++·ÃÎʲÎÊýÁË¡£
ÏÈ¿´Ò»ÏÂart_quick_dispatcherº¯ÊýµÄʵÏÖÈçÏ£º
/* * Art Quick Dispatcher. * On entry: * r0 = method pointer * r1 = arg1 * r2 = arg2 * r3 = arg3 * [sp] = method pointer * [sp + 4] = addr of thiz * [sp + 8] = addr of arg1 * [sp + 12] = addr of arg2 * [sp + 16] = addr of arg3 * and so on */ .extern artQuickToDispatcher ENTRY art_quick_dispatcher push {r4, r5, lr} @ sp - 12 mov r0, r0 @ pass r0 to method str r1, [sp, #(12 + 4)] str r2, [sp, #(12 + 8)] str r3, [sp, #(12 + 12)] mov r1, r9 @ pass r1 to thread add r2, sp, #(12 + 4) @ pass r2 to args array add r3, sp, #12 @ pass r3 to old SP blx artQuickToDispatcher @ (Method* method, Thread*, u4 **, u4 **) pop {r4, r5, pc} @ return on success, r0 and r1 hold the result END art_quick_dispatcher |
ÎÒ°Ñr2Ö¸Ïò²ÎÊýÊý×飬ÕâÑù¾ÍÎÒÃǾͿÉÒԷdz£·½±ãµÄ·ÃÎÊËùÓвÎÊýÁË¡£ÁíÍ⣬ÎÒÓÃr3±£´æÁ˾ɵÄspµØÖ·£¬ÕâÑùÊÇΪºóÃæµ÷ÓÃÔÀ´µÄentrypoint×ö×¼±¸µÄ¡£ÎÒÃÇÏÈ¿´¿´artQuickToDispatcherµÄʵÏÖ£º
extern "C" uint64_t artQuickToDispatcher(ArtMethod* method, Thread *self, u4 **args, u4 **old_sp){ HookInfo *info = (HookInfo *)method->GetNativeMethod(); LOGI("[+] entry ArtHandler %s->%s", info->classDesc, info->methodName);
// If it not is static method, then args[0]
was pointing to this
if(!info->isStaticMethod){
Object *thiz = reinterpret_cast<Object *>(args[0]);
if(thiz != NULL){
char *bytes = get_chars_from_utf16(thiz->GetClass()->GetName());
LOGI("[+] thiz class is %s", bytes);
delete bytes;
}
}
const void *entrypoint = info->entrypoint;
method->SetNativeMethod(info->nativecode);
//restore nativecode for JNI method
uint64_t res = art_quick_call_entrypoint(method,
self, args, old_sp, entrypoint);
JValue* result = (JValue* )&res;
if(result != NULL){
Object *obj = result->l;
char *raw_class_name = get_chars_from_utf16(obj->GetClass()->GetName());
if(strcmp(raw_class_name, "java.lang.String")
== 0){
char *raw_string_value = get_chars_from_utf16((String
*)obj);
LOGI("result-class %s, result-value \"%s\"",
raw_class_name, raw_string_value);
free(raw_string_value);
}else{
LOGI("result-class %s", raw_class_name);
}
free(raw_class_name);
}
// entrypoid may be replaced by trampoline,
only once.
// if(method->IsStatic() && !method->IsConstructor()){
entrypoint = method->GetEntryPointFromCompiledCode();
if(entrypoint != (const void *)art_quick_dispatcher){
LOGW("[*] entrypoint was replaced. %s->%s",
info->classDesc, info->methodName);
method->SetEntryPointFromCompiledCode((const
void *)art_quick_dispatcher);
info->entrypoint = entrypoint;
info->nativecode = method->GetNativeMethod();
}
method->SetNativeMethod((const void *)info);
// }
return res;
} |
ÕâÀï²ÎÊý½âÎö¾Í²»Ïêϸ˵ÁË£¬½ÓÏÂÀ´ÊÇ×ÊÖµÄÎÊÌ⡪¡ªÈçºÎÖØÐµ÷»ØÔÀ´µÄentrypoint¡£
ÕâÀïµÄ¹Ø¼üµãÊÇÒª»¹Ô֮ǰµÄ¶ÑÕ»²¼¾Ö£¬art_quick_call_entrypoint¾ÍÊǸºÔðÍê³ÉÕâ¸ö¹¤×÷µÄ£¬ÆäʵÏÖÈçÏÂËùʾ£º
/* * * Art Quick Call Entrypoint * On entry: * r0 = method pointer * r1 = thread pointer * r2 = args arrays pointer * r3 = old_sp * [sp] = entrypoint */ ENTRY art_quick_call_entrypoint push {r4, r5, lr} @ sp - 12 sub sp, #(40 + 20) @ sp - 40 - 20 str r0, [sp, #(40 + 0)] @ var_40_0 = method_pointer str r1, [sp, #(40 + 4)] @ var_40_4 = thread_pointer str r2, [sp, #(40 + 8)] @ var_40_8 = args_array str r3, [sp, #(40 + 12)] @ var_40_12 = old_sp mov r0, sp mov r1, r3 ldr r2, =40 blx memcpy @ memcpy(dest, src, size_of_byte) ldr r0, [sp, #(40 + 0)] @ restore method to r0 ldr r1, [sp, #(40 + 4)] mov r9, r1 @ restore thread to r9 ldr r5, [sp, #(40 + 8)] @ pass r5 to args_array ldr r1, [r5] @ restore arg1 ldr r2, [r5, #4] @ restore arg2 ldr r3, [r5, #8] @ restore arg3 ldr r5, [sp, #(40 + 20 + 12)] @ pass ip to entrypoint blx r5 add sp, #(40 + 20) pop {r4, r5, pc} @ return on success, r0 and r1 hold the result END art_quick_call_entrypoint |
ÕâÀïÎÒ͵ÀÁÁË£¬Ö±½ÓÉêÇëÁË10¸ö²ÎÊýµÄ¿Õ¼ä£¬ÔÙʹÓÃ֮ǰ´«½øÈëÀ´µÄold_sp½øÐлָ´£¬Ê¹ÓÃmemcpyÖ±½Ó¸´ÖÆ40×Ö½Ú¡£Ö®ºó¾ÍÊÇ»¹Ôr0,
r1, r2, r3, r9µÄÖµÁË¡£µ÷ÓÃentrypointÍêºó£¬½á¹û±£´æÔÚr0ºÍr1£¬ÔÙ·µ»Ø¸øartQuickToDispatcher¡£
ÖÁ´Ë£¬Õû¸öART Hook¾Í·ÖÎöÍê±ÏÁË¡£
0x4 4.4Óë5.XÉÏʵÏÖµÄÇø±ð
ÎÒµÄÕû¸ö·½°¸¶¼ÊÇÔÚ4.4ÉϲâÊԵģ¬Ö÷ÒªÊÇÒòΪÎÒÖ»ÓÐ4.4µÄÔ´Â룬¶øÇÒÓ²Å̿ռ䲻×㣬ʵÔÚ×°²»ÏÂ5.xµÄÔ´ÂëÁË¡£µ«Õû¸ö˼·£¬ÊÇÍêÈ«¿ÉÒÔÌ×ÓÃÓÃ5.XÉÏ¡£ÁíÍ⣬5.XµÄʵÏÖ´úÂë±È4.4Éϸ´ÔÓÁ˺ܶ࣬·ñÄÜÏñÎÒÕâÑùÔÚNDKϱàÒëÍê³É¾Í²»ÖªµÀÁË¡£
Õý³£µÄ4.4Ä£ÄâÆ÷ÊÇÒÔdalvikÆô¶¯µÄ£¬Òªµ½ÉèÖÃÀï¸ÄΪart£¬ÕâÀï»áÒªÇó½øÐÐÖØÆô£¬µ«Ò»°ãÎÞЧ£¬ÎÒÃÇÊÖ¶¯¹Ø±ÕÔÙÖØÐ´ò¿ª¾ÍOKÁË£¬µ«ÐèÒªµÈÉÏÒ»¶Îʱ¼ä²Å¿ÉÒÔ¡£
0x5 ½áÊø
ËäÈ»ÕâÆªÎÄÕÂÖ»ÊǽéÉÜÁËArt HookµÄ¼¼Êõ·½°¸£¬µ«ÆäÖеļ¼ÊõÔÀí£¬¶ÔÓÚÈçºÎÔÚARTÉϽøÐдúÂë¼Ó¹Ì¡¢¶¯Ì¬´úÂ뻹ԵȵÈÒ²ÊǺÜÓÐÆô·¢ÐÔ¡£
ÀÏÑù×Ó£¬Õû¸öÏîÄ¿µÄ´úÂ룬ÎÒÒѾÌá½»µ½https://github.com/boyliang/AllHookInOne£¬´ó¼ÒÓöµ½Ê²Ã´ÎÊÌ⣬»¶ÓÌáÎÊ£¬ÓÐÎÊÌâ¼ÇµÃ·´À¡¡£
¶ÔÁË£¬ÇëÓÃhttps://github.com/boyliang/ndk-patch¸øÄãµÄNDK´òÒ»ÏÂpatch¡£
|