网络安全加壳脱壳知识点大放送,从零基础到精通,收藏这篇就够了!

一、前言:

各位网络安全界的大佬们,是不是还在对着晦涩难懂的加壳脱壳资料“啃生肉”?今天,咱就来点猛料,把ART环境下App启动流程中的类加载流程以及相关知识,用最通俗易懂的方式给您安排上!深入剖析脱壳的关键点,让您对App加壳与脱壳的原理理解更上一层楼!

环境:Android 8.0.0,安排!

二、知识点大爆炸:

1. 类加载流程:这可不是闹着玩儿的!

咱们直接从loadClass这个“老大哥”开始说起。要知道,DexClassloaderPathClassloaderInMemoryDexClassLoader这仨兄弟,他们的老父亲都是BaseDexClassLoader,而BaseDexClassLoader的老父亲则是Classloader。这loadClass的“重任”,就落在ClassLoader这个老家伙身上了!

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

看到没?这里调用了loadClass(name, false),继续往下看!

protected Class<?> loadClass(String name, boolean resolve),这才是重点!官方源码的注释已经说得很明白了:首先,它会确认这个类是不是已经被加载过了。如果没有,那就启动“双亲委派”模式,一层一层往上找,调用parent.loadClass。如果“老爹”们都没加载过,那就轮到自己出马,调用findClass开始查找并加载class!

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

注意,findClassBaseDexClassLoader中可是被重写过的!

@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

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的一个内部类。

public Class<?> findClass(String name, ClassLoader definingContext,
                List<Throwable> suppressed) {
    return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
            : null;
}

DexFile_loadClassBinaryName

public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
    return defineClass(name, loader, mCookie, this, suppressed);
}

DexFile_defineClass

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的注册。

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文件中加载到内存中,并建立相应的数据结构。

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

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这里开始加载类的成员变量,方法,方法的加载有两步LoadMethodLinkCode

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::LoadMethodLoadMethod为ArtMethod的基础属性赋值,并检查是否是构造函数,这里可以看到在设置CodeItemOffset,以前的函数抽取壳会在这个阶段回填字节码,通过hook LoadMethod即可脱到完整的Dex。

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::LinkCodeLinkCode的主要工作是把,以及一些相关操作例如分配空间,关联引用等,还有Native函数的绑定,最后会调用SetEntryPointFromQuickCompiledCode设置方法被调用时的入口,method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge())这段代码就是当oat被阻断后会把方法入口设置为解释执行入口。

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

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

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

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

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这个桥函数了。这个桥函数也是汇编实现的。
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()

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型,默认使用汇编型。

enum InterpreterImplKind {
  kSwitchImplKind,        // Switch-based interpreter implementation.
  kMterpImplKind          // Assembly interpreter
};

static constexpr InterpreterImplKind kInterpreterImplKind = kMterpImplKind;

三、脱壳,安排!

上面咱们分析了类的加载流程和Java方法的调用流程。这里面频繁出现DexfileArtMethod这两个关键角色,它们都可以用于脱壳!通过Dexfile可以直接dump Dexfile,而通过ArtMethod也可以间接获取到Dexfile对象。这里咱们随便找一个,用loadClassMember方法进行实战,还是用frida hook的方式。

这里代码用上次的改一下即可。

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

黑客/网络安全学习包

资料目录

  1. 成长路线图&学习规划

  2. 配套视频教程

  3. SRC&黑客文籍

  4. 护网行动资料

  5. 黑客必读书单

  6. 面试题合集

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

*************************************优快云大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*************************************

1.成长路线图&学习规划

要学习一门新的技术,作为新手一定要先学习成长路线图方向不对,努力白费

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图&学习规划。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。


因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

*************************************优快云大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*************************************

2.视频教程

很多朋友都不喜欢晦涩的文字,我也为大家准备了视频教程,其中一共有21个章节,每个章节都是当前板块的精华浓缩


因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

*************************************优快云大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*************************************

3.SRC&黑客文籍

大家最喜欢也是最关心的SRC技术文籍&黑客技术也有收录

SRC技术文籍:

黑客资料由于是敏感资源,这里不能直接展示哦!

4.护网行动资料

其中关于HW护网行动,也准备了对应的资料,这些内容可相当于比赛的金手指!

5.黑客必读书单

**

**

6.面试题合集

当你自学到这里,你就要开始思考找工作的事情了,而工作绕不开的就是真题和面试题。

更多内容为防止和谐,可以扫描获取~

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

*************************************优快云大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享*********************************

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值