加壳脱壳知识点总结——类加载、方法执行流程以及脱壳实战
2024-8-23 17:50:30 Author: mp.weixin.qq.com(查看原文) 阅读量:12 收藏


前言

总结记录下ART环境下App启动流程中类加载流程以及相关知识,分析更深层次的脱壳点,加深对App加壳以及脱壳原理的理解。

环境:Android 8.0.0

关知识总结

1.类加载流程

这里直接从loadClass开始,DexClassloader,PathClassloader,InMemoryDexClassLoader这三个类的父类是BaseDexClassLoader,BaseDexClassLoader的父类是Classloader,在ClassLoader这个类里实现了loadClass。
 
public Class<?> loadClass(String name) throws ClassNotFoundException {
return 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;
}

findClass在BaseDexClassLoader中重写了。
   
@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这里开始加载类的成员变量,方法,方法的加载有两步LoadMethod和LinkCode。
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方法的调用流程,这里面大量出现了Dexfile,ArtMethod,两个地方都是可以用于脱壳,通过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);
}
}
});
}
}


其实总结的知识点已经涉及到抽取壳脱壳了,抽取壳的脱壳后面再写。
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/

看雪ID:mb_edqxbbqv

https://bbs.kanxue.com/user-home-978849.htm

*本文为看雪论坛优秀文章,由 mb_edqxbbqv 原创,转载请注明来自看雪社区

# 往期推荐

1、Alt-Tab Terminator注册算法逆向

2、恶意木马历险记

3、VMP源码分析:反调试与绕过方法

4、Chrome V8 issue 1486342浅析

5、Cython逆向-语言特性分析

球分享

球点赞

球在看

点击阅读原文查看更多


文章来源: https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458569224&idx=2&sn=602ccf221a910cc34273c6f090cb7cd4&chksm=b18dfa8286fa739401779338517f0c9d7743314168276c3c0bc6e0872792ff2158d90f74a55d&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh