-
-
[原创]加壳脱壳知识点总结--类加载、方法执行流程以及脱壳实战
-
2024-6-11 18:12 2273
-
1.前言
总结记录下ART环境下App启动流程中类加载流程以及相关知识,分析更深层次的脱壳点,加深对App加壳以及脱壳原理的理解。
环境:Android 8.0.0
2.相关知识总结
1.类加载流程
这里直接从loadClass开始,DexClassloader,PathClassloader,InMemoryDexClassLoader这三个类的父类是BaseDexClassLoader,BaseDexClassLoader的父类是Classloader,在ClassLoader这个类里实现了loadClass。
1 2 3 | public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } |
protected Class<?> loadClass(String name, boolean resolve),这里官方的源码注释解释的比较清楚了,首先确定这个类是否被加载过,没有的话会开始走双亲委派的流程调用parent.loadClass,如果父加载器都没有加载过,则自己调用findClass开始查找class并加载
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 | protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { / / First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c = = null) { try { if (parent ! = null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { / / ClassNotFoundException thrown if class not found / / from the non - null parent class loader } if (c = = null) { / / If still not found, then invoke findClass in order / / to find the class . c = findClass(name); } } return c; } |
findClass在BaseDexClassLoader中重写了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Override protected Class<?> findClass(String name) throws ClassNotFoundException { List <Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(name, suppressedExceptions); if (c = = null) { ClassNotFoundException cnfe = new ClassNotFoundException( "Didn't find class \"" + name + "\" on path: " + pathList); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; } return c; } |
DexPathList_public Class<?> findClass(String name, List<Throwable> suppressed) 这里的dexElements是在加载Dex时赋值的,保存的时当时加载的DexFile
1 2 3 4 5 6 7 8 9 10 11 12 13 | public Class<?> findClass(String name, List <Throwable> suppressed) { for (Element element : dexElements) { Class<?> clazz = element.findClass(name, definingContext, suppressed); if (clazz ! = null) { return clazz; } } if (dexElementsSuppressedExceptions ! = null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; } |
Element_public Class<?> findClass(String name, ClassLoader definingContext,List<Throwable> suppressed),这个Element类是DexPathList的一个内部类
1 2 3 4 5 | public Class<?> findClass(String name, ClassLoader definingContext, List <Throwable> suppressed) { return dexFile ! = null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) : null; } |
DexFile_loadClassBinaryName
1 2 3 | public Class loadClassBinaryName(String name, ClassLoader loader, List <Throwable> suppressed) { return defineClass(name, loader, mCookie, this, suppressed); } |
DexFile_defineClass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | private static Class defineClass(String name, ClassLoader loader, Object cookie, DexFile dexFile, List <Throwable> suppressed) { Class result = null; try { result = defineClassNative(name, loader, cookie, dexFile); } catch (NoClassDefFoundError e) { if (suppressed ! = null) { suppressed.add(e); } } catch (ClassNotFoundException e) { if (suppressed ! = null) { suppressed.add(e); } } return result; } |
DexFile_defineClassNative 这里进入了native层的DexFile中,这段代码在进行DexFile的注册。
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 | static jclass DexFile_defineClassNative(JNIEnv * env,jclass,jstring javaName,jobject javaLoader, jobject cookie,jobject dexFile) { ... for (auto& dex_file : dex_files) { const DexFile::ClassDef * dex_class_def = OatDexFile::FindClassDef( * dex_file, descriptor.c_str(), hash ); if (dex_class_def ! = nullptr) { ScopedObjectAccess soa(env); ClassLinker * class_linker = Runtime::Current() - >GetClassLinker(); StackHandleScope< 1 > hs(soa.Self()); Handle<mirror::ClassLoader> class_loader( hs.NewHandle(soa.Decode<mirror::ClassLoader>(javaLoader))); ObjPtr<mirror::DexCache> dex_cache = class_linker - >RegisterDexFile( * dex_file, class_loader.Get()); if (dex_cache = = nullptr) { / / OOME or InternalError (dexFile already registered with a different class loader). soa.Self() - >AssertPendingException(); return nullptr; } ObjPtr<mirror::Class> result = class_linker - >DefineClass(soa.Self(),descriptor.c_str(), hash ,class_loader, * dex_file, * dex_class_def); / / Add the used dex file . This only required for the DexFile.loadClass API since normal / / class loaders already keep their dex files live. class_linker - >InsertDexFileInToClassLoader(soa.Decode<mirror:: Object >(dexFile),class_loader.Get()); if (result ! = nullptr) { VLOG(class_linker) << "DexFile_defineClassNative returning " << result << " for " << class_name.c_str(); return soa.AddLocalReference<jclass>(result); } } } VLOG(class_linker) << "Failed to find dex_class_def " << class_name.c_str(); return nullptr; } |
__mirror::Class* ClassLinker::DefineClass__这里也是在初始化类,分配类空间等,有三个阶段SetupClass,LoadClass,LinkClass,关于类加载主要看LoadClass主要作用是将类的字段和方法信息从Dex文件中加载到内存中,并建立相应的数据结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | mirror::Class * ClassLinker::DefineClass(Thread * self ,const char * escriptor,size_t hash ,Handle<mirrr::ClassLoader> class_loader,const DexFile& dex_file,const DexFile::ClassDef dex_class_def) { ... klass - >SetDexCache(dex_cache); SetupClass( * new_dex_file, * new_class_def, klass, class_loader.Get()); ... / / Load the fields and other things after we arenserted in the table. This is so that we don't / / end up allocating unfree - able linear alloc rources and then lose the race condition. The / / other reason is that the field roots are onlvisited from the class table. So we need to be / / inserted before we allocate / fill in these field LoadClass( self , * new_dex_file, * new_class_def, klass); ... if (!LinkClass( self , descriptor, klass, interfaces, &h_new_class)) { / / Linking failed. if (!klass - >IsErroneous()) { mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self ); } return nullptr; } ... |
ClassLinker::LoadClass
1 2 3 4 5 6 7 8 9 10 | void ClassLinker::LoadClass(Thread * self , const DexFile& dex_file, const DexFile::ClassDef& dex_class_def, Handle<mirror::Class> klass) { const uint8_t * class_data = dex_file.GetClassData(dex_class_def); if (class_data = = nullptr) { return ; / / no fields or methods - for example a marker interface } LoadClassMembers( self , dex_file, class_data, klass); } |
ClassLinker::LoadClassMembers 这里开始加载类的成员变量,方法,方法的加载有两步LoadMethod和LinkCode。
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 | void ClassLinker::LoadClassMembers(Thread * self ,const DexFile& dex_file,const uint8_t * class_data,Handle<mirror::Class> klass) { { / / Note: We cannot have thread suspension until the field and method arrays are setup or else / / Class::VisitFieldRoots may miss some fields or methods. ScopedAssertNoThreadSuspension nts(__FUNCTION__); / / Load static fields. ... for (; it.HasNextStaticField(); it. Next ()) { uint32_t field_idx = it.GetMemberIndex(); DCHECK_GE(field_idx, last_field_idx); / / Ordering enforced by DexFileVerifier. if (num_sfields = = 0 || LIKELY(field_idx > last_field_idx)) { DCHECK_LT(num_sfields, it.NumStaticFields()); LoadField(it, klass, &sfields - >At(num_sfields)); + + num_sfields; last_field_idx = field_idx; } } / / Load instance fields. LengthPrefixedArray<ArtField> * ifields = AllocArtFieldArray( self ,allocator,it.NumInstanceFields()); size_t num_ifields = 0u ; last_field_idx = 0u ; for (; it.HasNextInstanceField(); it. Next ()) { uint32_t field_idx = it.GetMemberIndex(); DCHECK_GE(field_idx, last_field_idx); / / Ordering enforced by DexFileVerifier. if (num_ifields = = 0 || LIKELY(field_idx > last_field_idx)) { DCHECK_LT(num_ifields, it.NumInstanceFields()); LoadField(it, klass, &ifields - >At(num_ifields)); + + num_ifields; last_field_idx = field_idx; } } ... / / Set the field arrays. klass - >SetSFieldsPtr(sfields); DCHECK_EQ(klass - >NumStaticFields(), num_sfields); klass - >SetIFieldsPtr(ifields); DCHECK_EQ(klass - >NumInstanceFields(), num_ifields); / / Load methods. bool has_oat_class = false; const OatFile::OatClass oat_class = (Runtime::Current() - >IsStarted() && !Runtime::Current() - >IsAotCompiler()) ? OatFile::FindOatClass(dex_file, klass - >GetDexClassDefIndex(), &has_oat_class) : OatFile::OatClass::Invalid(); const OatFile::OatClass * oat_class_ptr = has_oat_class ? &oat_class : nullptr; klass - >SetMethodsPtr( AllocArtMethodArray( self , allocator, it.NumDirectMethods() + it.NumVirtualMethods()), it.NumDirectMethods(), it.NumVirtualMethods()); size_t class_def_method_index = 0 ; uint32_t last_dex_method_index = DexFile::kDexNoIndex; size_t last_class_def_method_index = 0 ; / / TODO These should really use the iterators. for (size_t i = 0 ; it.HasNextDirectMethod(); i + + , it. Next ()) { ArtMethod * method = klass - >GetDirectMethodUnchecked(i, image_pointer_size_); LoadMethod(dex_file, it, klass, method); LinkCode(this, method, oat_class_ptr, class_def_method_index); uint32_t it_method_index = it.GetMemberIndex(); if (last_dex_method_index = = it_method_index) { / / duplicate case method - >SetMethodIndex(last_class_def_method_index); } else { method - >SetMethodIndex(class_def_method_index); last_dex_method_index = it_method_index; last_class_def_method_index = class_def_method_index; } class_def_method_index + + ; } for (size_t i = 0 ; it.HasNextVirtualMethod(); i + + , it. Next ()) { ArtMethod * method = klass - >GetVirtualMethodUnchecked(i, image_pointer_size_); LoadMethod(dex_file, it, klass, method); DCHECK_EQ(class_def_method_index, it.NumDirectMethods() + i); LinkCode(this, method, oat_class_ptr, class_def_method_index); class_def_method_index + + ; } DCHECK(!it.HasNext()); } / / Ensure that the card is marked so that remembered sets pick up native roots. Runtime::Current() - >GetHeap() - >WriteBarrierEveryFieldOf(klass.Get()); self - >AllowThreadSuspension(); } |
ClassLinker::LoadMethod LoadMethod为ArtMethod的基础属性赋值,并检查是否是构造函数,这里可以看到在设置CodeItemOffset,以前的函数抽取壳会在这个阶段回填字节码,通过hook LoadMethod即可脱到完整的Dex。
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 | void ClassLinker::LoadMethod(const DexFile& dex_file,const ClassDataItemIterator& it,Handle<mirror::Class> klass,ArtMethod * dst) { uint32_t dex_method_idx = it.GetMemberIndex(); const DexFile::MethodId& method_id = dex_file.GetMethodId(dex_method_idx); const char * method_name = dex_file.StringDataByIdx(method_id.name_idx_); ScopedAssertNoThreadSuspension ants( "LoadMethod" ); dst - >SetDexMethodIndex(dex_method_idx); dst - >SetDeclaringClass(klass.Get()); dst - >SetCodeItemOffset(it.GetMethodCodeItemOffset()); dst - >SetDexCacheResolvedMethods(klass - >GetDexCache() - >GetResolvedMethods(), image_pointer_size_); uint32_t access_flags = it.GetMethodAccessFlags(); if (UNLIKELY(strcmp( "finalize" , method_name) = = 0 )) { } else if (method_name[ 0 ] = = '<' ) { / / Fix broken access flags for initializers. Bug 11157540. bool is_init = (strcmp( "<init>" , method_name) = = 0 ); bool is_clinit = !is_init && (strcmp( "<clinit>" , method_name) = = 0 ); if (UNLIKELY(!is_init && !is_clinit)) { LOG(WARNING) << "Unexpected '<' at start of method name " << method_name; } else { ... } } dst - >SetAccessFlags(access_flags); } |
ClassLinker::LinkCode LinkCode的主要工作是把,以及一些相关操作例如分配空间,关联引用等,还有Native函数的绑定,最后会调用SetEntryPointFromQuickCompiledCode设置方法被调用时的入口,method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge())这段代码就是当oat被阻断后会把方法入口设置为解释执行入口。
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 | static void LinkCode(ClassLinker * class_linker,ArtMethod * method,const OatFile::OatClass * oat_class,uint32_t class_def_method_index) REQUIRES_SHARED(Locks::mutator_lock_) { Runtime * const runtime = Runtime::Current(); if (runtime - >IsAotCompiler()) { / / The following code only applies to a non - compiler runtime. return ; } / / Method shouldn't have already been linked. DCHECK(method - >GetEntryPointFromQuickCompiledCode() = = nullptr); if (oat_class ! = nullptr) { / / 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(class_def_method_index); oat_method.LinkMethod(method); } / / Install entry point from interpreter. const void * quick_code = method - >GetEntryPointFromQuickCompiledCode(); bool enter_interpreter = class_linker - >ShouldUseInterpreterEntrypoint(method, quick_code); if (!method - >IsInvokable()) { EnsureThrowsInvocationError(class_linker, method); return ; } 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 - >SetEntryPointFromQuickCompiledCode(GetQuickResolutionStub()); } else if (quick_code = = nullptr && method - >IsNative()) { method - >SetEntryPointFromQuickCompiledCode(GetQuickGenericJniStub()); } else if (enter_interpreter) { / / Set entry point from compiled code if there's no code or in interpreter only mode. method - >SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge()); } if (method - >IsNative()) { / / Unregistering restores the dlsym lookup stub. method - >UnregisterNative(); if (enter_interpreter || quick_code = = nullptr) { / / We have a native method here without code. Then it should have either the generic JNI / / trampoline as entrypoint (non - static), or the resolution trampoline (static). / / TODO: this doesn't handle all the cases where trampolines may be installed. const void * entry_point = method - >GetEntryPointFromQuickCompiledCode(); DCHECK(class_linker - >IsQuickGenericJniStub(entry_point) || class_linker - >IsQuickResolutionStub(entry_point)); } } } |
2.方法执行
类加载完成之后,一般就要调用类的方法了,所以继续看一下方法执行的流程,Android 8.0中主要是两种方法执行方式,解释执行和OAT的快速执行,解释执行主要是根据字节码执行方法,快速执行通过OAT中预先编译成的本地机器码来执行方法,对于加壳来说,阻断了OAT流程后系统会转向解释执行。
在安卓中,Java 函数可以通过两种主要的方式进行调用。一种是通过自然的代码执行路径进行调用,即在 Java 虚拟机中直接调用,另一种是通过 JNI进行调用,即通过本地代码与 Java 代码进行交互来实现函数调用,先说JNI调用的流程。
JNI调用Java函数主要是通过一系列的CallXXXMethod(+V代表无返回值+A代表参数以数组形式传递)JNI函数,这里找几个看一下实现方式,可以看到都调用了InvokeVirtualOrInterfaceWithVarArgs。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | static jobject CallObjectMethod(JNIEnv * env, jobject obj, jmethodID mid, ...) { va_list ap; va_start(ap, mid); CHECK_NON_NULL_ARGUMENT(obj); CHECK_NON_NULL_ARGUMENT(mid); ScopedObjectAccess soa(env); JValue result(InvokeVirtualOrInterfaceWithVarArgs(soa, obj, mid, ap)); va_end(ap); return soa.AddLocalReference<jobject>(result.GetL()); } static jboolean CallBooleanMethod(JNIEnv * env, jobject obj, jmethodID mid, ...) { va_list ap; va_start(ap, mid); CHECK_NON_NULL_ARGUMENT_RETURN_ZERO(obj); CHECK_NON_NULL_ARGUMENT_RETURN_ZERO(mid); ScopedObjectAccess soa(env); JValue result(InvokeVirtualOrInterfaceWithVarArgs(soa, obj, mid, ap)); va_end(ap); return result.GetZ(); } |
InvokeVirtualOrInterfaceWithVarArgs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | JValue InvokeVirtualOrInterfaceWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, jobject obj, jmethodID mid, va_list args) { ... ObjPtr<mirror:: Object > receiver = soa.Decode<mirror:: Object >(obj); ArtMethod * method = FindVirtualMethod(receiver, jni::DecodeArtMethod(mid)); bool is_string_init = method - >GetDeclaringClass() - >IsStringClass() && method - >IsConstructor(); if (is_string_init) { / / Replace calls to String.<init> with equivalent StringFactory call. method = WellKnownClasses::StringInitToStringFactory(method); receiver = nullptr; } uint32_t shorty_len = 0 ; const char * shorty = method - >GetInterfaceMethodIfProxy(kRuntimePointerSize) - >GetShorty(&shorty_len); JValue result; ArgArray arg_array(shorty, shorty_len); arg_array.BuildArgArrayFromVarArgs(soa, receiver, args); InvokeWithArgArray(soa, method, &arg_array, &result, shorty); ... return result; } |
InvokeWithArgArray
1 2 3 4 5 6 7 8 9 10 | static void InvokeWithArgArray(const ScopedObjectAccessAlreadyRunnable& soa, ArtMethod * method, ArgArray * arg_array, JValue * result, const char * shorty) REQUIRES_SHARED(Locks::mutator_lock_) { uint32_t * args = arg_array - >GetArray(); if (UNLIKELY(soa.Env() - >check_jni)) { CheckMethodArguments(soa.Vm(), method - >GetInterfaceMethodIfProxy(kRuntimePointerSize), args); } method - >Invoke(soa.Self(), args, arg_array - >GetNumBytes(), result, shorty); } |
ArtMethod::invoke
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 | void ArtMethod::Invoke(Thread * self , uint32_t * args, uint32_t args_size, JValue * result, const char * shorty) { .. / / Push a transition back into managed code onto the linked list in thread. ManagedStack fragment; self - >PushManagedStackFragment(&fragment); Runtime * runtime = Runtime::Current(); if (UNLIKELY(!runtime - >IsStarted() || Dbg::IsForcedInterpreterNeededForCalling( self , this))) { if (IsStatic()) { art::interpreter::EnterInterpreterFromInvoke( self , this, nullptr, args, result, / * stay_in_interpreter * / true); } else { mirror:: Object * receiver = reinterpret_cast<StackReference<mirror:: Object > * >(&args[ 0 ]) - >AsMirrorPtr(); art::interpreter::EnterInterpreterFromInvoke( self , this, receiver, args + 1 , result, / * stay_in_interpreter * / true); } } else { DCHECK_EQ(runtime - >GetClassLinker() - >GetImagePointerSize(), kRuntimePointerSize); constexpr bool kLogInvocationStartAndReturn = false; bool have_quick_code = GetEntryPointFromQuickCompiledCode() ! = nullptr; if (LIKELY(have_quick_code)) { if (kLogInvocationStartAndReturn) { LOG(INFO) << StringPrintf( "Invoking '%s' quick code=%p static=%d" , PrettyMethod().c_str(), GetEntryPointFromQuickCompiledCode(), static_cast< int >(IsStatic() ? 1 : 0 )); } / / Ensure that we won't be accidentally calling quick compiled code when - Xint. if (kIsDebugBuild && runtime - >GetInstrumentation() - >IsForcedInterpretOnly()) { CHECK(!runtime - >UseJitCompilation()); const void * oat_quick_code = (IsNative() || !IsInvokable() || IsProxyMethod() || IsObsolete()) ? nullptr : GetOatMethodQuickCode(runtime - >GetClassLinker() - >GetImagePointerSize()); CHECK(oat_quick_code = = nullptr || oat_quick_code ! = GetEntryPointFromQuickCompiledCode()) << "Don't call compiled code when -Xint " << PrettyMethod(); } 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); }} ... / / Pop transition. self - >PopManagedStackFragment(fragment); } |
这就到了比较关键的地方,通过反射和JNI调用都会走到这里,Invoke会判断方法调用的环境,方法类型,如果不是静态方法会进入art_quick_invoke_stub若目标方法已编译为快速路径,则经 INVOKE_STUB_CALL_AND_RETURN的汇编代码后,通过 PtrSizedFields 中的 entry_point_from_quick_compiled_code_ 跳转到目标方法的入口(entry_point_from_quick_compiled_code_ 也是自然通过代码执行Java代码的入口)。
这里有两种入口,一种是编译好的本地机器指令入口直接开始执行,另一种就是解释执行入口,在LinkCode函数说到当OAT被阻断后会调用method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge()),这样的话entry_point_from_quick_compiled_code_ 所指向的就是art_quick_to_interpreter_bridge这个桥函数了,这个桥函数也是汇编实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ENTRY art_quick_to_interpreter_bridge SETUP_SAVE_REFS_AND_ARGS_FRAME / / Set up frame and save arguments. / / x0 will contain mirror::ArtMethod * method. mov x1, xSELF / / How to get Thread::Current() ??? mov x2, sp / / uint64_t artQuickToInterpreterBridge(mirror::ArtMethod * method, Thread * self , / / mirror::ArtMethod * * sp) bl artQuickToInterpreterBridge RESTORE_SAVE_REFS_AND_ARGS_FRAME / / TODO: no need to restore arguments in this case. fmov d0, x0 RETURN_OR_DELIVER_PENDING_EXCEPTION END art_quick_to_interpreter_bridge |
通过bl跳转到了artQuickToInterpreterBridge函数,之后的调用流程是artQuickToInterpreterBridge() → interpreter::EnterInterpreterFromEntryPoint() → interpreter::Execute()。
interpreter::Execute()
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 | static inline JValue Execute( Thread * self , const DexFile::CodeItem * code_item, ShadowFrame& shadow_frame, JValue result_register, bool stay_in_interpreter = false) REQUIRES_SHARED(Locks::mutator_lock_) { DCHECK(!shadow_frame.GetMethod() - >IsAbstract()); DCHECK(!shadow_frame.GetMethod() - >IsNative()); ... if (kInterpreterImplKind = = kMterpImplKind) { if (transaction_active) { / / No Mterp variant - just use the switch interpreter. return ExecuteSwitchImpl<false, true>( self , code_item, shadow_frame, result_register, false); ... } } else { DCHECK_EQ(kInterpreterImplKind, kSwitchImplKind); if (transaction_active) { return ExecuteSwitchImpl<false, true>( self , code_item, shadow_frame, result_register, false); } else { return ExecuteSwitchImpl<false, false>( self , code_item, shadow_frame, result_register, false); } } } ... } |
解释执行会通过kInterpreterImplKind的类型来判断执行哪种解释器,解释执行总的来说有三种方式goto型,汇编型和switch型,这三种的实现方式其实就和字面意思差不多,分别使用goto,汇编,switch来完成对字节码匹配执行,goto型在较早的版本还有,在本次分析的安卓8系统中已经不存在了,安卓8实现了汇编型和switch型,默认使用汇编型。
1 2 3 4 5 6 | enum InterpreterImplKind { kSwitchImplKind, / / Switch - based interpreter implementation. kMterpImplKind / / Assembly interpreter }; static constexpr InterpreterImplKind kInterpreterImplKind = kMterpImplKind; |
3.脱壳实战
上面分析了类的加载流程和Java方法的调用流程,这里面大量出现了Dexfile,ArtMethod,两个地方都是可以用于脱壳,通过Dexfile可以直接dump Dexfile,而通过ArtMethod也可以间接获取到Dexfile对象,这里随便找一个,loadClassMember方法进行实战,还是用的frida 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 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 | function hookartLoadClassMember() { var addrLoadClassMember = null; var symbols = Module.enumerateSymbolsSync( "libart.so" ); for (var i = 0 ; i < symbols.length; i + + ) { var symbol = symbols[i]; if (symbol.name.indexOf( "ClassLinker" ) > = 0 && symbol.name.indexOf( "DexFile" ) > = 0 && symbol.name.indexOf( "LoadClassMember" )> = 0 ) { addrLoadClassMember = symbol.address; break ; } } if (addrLoadClassMember ! = null) { Interceptor.attach(addrLoadClassMember, { onEnter: function (args) { this.dexfileptr = args[ 2 ]; }, onLeave: function (retval) { var dexfilebegin = null; var dexfilesize = null; if (this.dexfileptr ! = null) { dexfilebegin = Memory.readPointer(ptr(this.dexfileptr).add(Process.pointerSize * 1 )); dexfilesize = Memory.readU32(ptr(this.dexfileptr).add(Process.pointerSize * 2 )); var dexfile_path = savepath + "/" + dexfilesize + "_LoadClassMember.dex" ; var dexfile_handle = null; try { dexfile_handle = new File (dexfile_path, "r" ); if (dexfile_handle && dexfile_handle ! = null) { dexfile_handle.close() } } catch (e) { dexfile_handle = new File (dexfile_path, "a+" ); if (dexfile_handle && dexfile_handle ! = null) { var dex_buffer = ptr(dexfilebegin).readByteArray(dexfilesize); dexfile_handle.write(dex_buffer); dexfile_handle.flush(); dexfile_handle.close(); console.log( "[dumpdex]:" , dexfile_path); } } } var dexfileobj = new DexFile(dexfilebegin, dexfilesize); if (dex_maps[dexfilebegin] = = undefined) { dex_maps[dexfilebegin] = dexfilesize; console.log( "got a dex:" , dexfilebegin, dexfilesize); } } }); } } |
其实总结的知识点已经涉及到抽取壳脱壳了,抽取壳的脱壳后面再写。
4.参考文章
https://blog.csdn.net/u013989732/article/details/80670989
https://blog.csdn.net/hl09083253cy/article/details/78418651
https://blog.csdn.net/hl09083253cy/article/details/78418702
https://rk700.github.io/2017/06/30/hook-on-android-n/
https://blog.csdn.net/u013989732/article/details/80717762/
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课