欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 时评 > FART 主动调用组件设计和源码分析

FART 主动调用组件设计和源码分析

2025/5/20 8:21:27 来源:https://blog.csdn.net/linchaolong/article/details/148077139  浏览:    关键词:FART 主动调用组件设计和源码分析

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

现有脱壳方法存在的问题

脱壳粒度集中在 DexFile 整体,当前对 apk 保护的粒度在函数粒度,这就导致了脱壳与加固的不对等,无法应对函数粒度的加固保护。

Dalvik 下的基于主动调用的自动化脱壳方案,首次将粒度下降到函数粒度

  • https://github.com/zyq8709/DexHunter

  • https://github.com/F8LEFT/FUPK3

存在问题:

1、Dalvik 慢慢淡出视野,无法应对 ART 环境

2、当前没有一个好的脱壳修复框架,无法应用到 VMP 函数的修复

主动调用相关概念

1、被动调用

指 app 正常运行过程中发生的调用,该过程只对 dex 中部分的类完成了加载,同时也只是对 dex 中的部分函数完成了调用。

2、主动调用

通过构造虚拟调用,从而达到欺骗 “壳” ,让壳误以为 app 在执行正常的函数调用流程从而达成对 dex 中所有类函数的虚拟调用。

被动调用也可以用来完成函数粒度的修复。如当前通过正常运行 app ,待 app 将 dex 中的类正常加载并完成相关的函数的正常调用后再进行 dex 的 dump 的脱壳方法。

被动调用脱壳的缺点: 存在修复函数不全的问题。由于测试用例无法覆盖 dex 中所有的函数,导致代码覆盖率低,只能对 app 运行过程中调用过的函数的修复。

主动调用的优点: 能够覆盖 dex 中所有的函数,从而完成更彻底的函数粒度的修复。同时,函数的修复准确度同主动调用链的构造深度有关。

FART 中要解决的三个问题

1、如何构造主动调用链并让每一个函数都到达主动调用过程,但是又不影响 app 的正常运行?

2、如何根据 ArtMethod 定位内存中对应的 CodeItem 的起始地址?

3、如何遍历 dex 中的所有函数并完成主动调用?

如何构造主动调用链?

标准 Java 函数调用示例代码:

// 1. 查找 Java 类
jclass clazz = env->FindClass("com/cyrus/example/jniexample/JNIExample");
if (clazz == nullptr) {LOGI("Class not found");return env->NewStringUTF("Class not found");
}// 2. 获取 Java 方法 ID
jmethodID methodId = env->GetStaticMethodID(clazz, "helloFromJava", "()Ljava/lang/String;");
if (methodId == nullptr) {LOGI("Method not found");return env->NewStringUTF("Method not found");
}// 3. 调用 Java 方法
jstring resultStr = (jstring) env->CallStaticObjectMethod(clazz, methodId);

标准流程:

  1. 通过 FindClass 得到 jclass

  2. 通过 GetStaticMethodID / GetMethodId 得到 jmethodID

  3. 通过一系列 Call 开头的函数完成调用(比如这里的 CallStaticObjectMethod)

FindClass

FindClass 的作用: 通过 ClassLinker 的 FindClass 取得目标类的 jclass

  static jclass FindClass(JNIEnv* env, const char* name) {CHECK_NON_NULL_ARGUMENT(name);Runtime* runtime = Runtime::Current();ClassLinker* class_linker = runtime->GetClassLinker();std::string descriptor(NormalizeJniClassDescriptor(name));ScopedObjectAccess soa(env);ObjPtr<mirror::Class> c = nullptr;if (runtime->IsStarted()) {StackHandleScope<1> hs(soa.Self());Handle<mirror::ClassLoader> class_loader(hs.NewHandle(GetClassLoader(soa)));c = class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader);} else {c = class_linker->FindSystemClass(soa.Self(), descriptor.c_str());}return soa.AddLocalReference<jclass>(c);}

https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/jni/jni_internal.cc;l=654

GetMethodID / GetStaticMethodID 的作用:根据 jclass 对象、方法名和签名信息取得对应的 ArtMethod 用于下一步的调用

 static jmethodID GetMethodID(JNIEnv* env, jclass java_class, const char* name, const char* sig) {CHECK_NON_NULL_ARGUMENT(java_class);CHECK_NON_NULL_ARGUMENT(name);CHECK_NON_NULL_ARGUMENT(sig);ScopedObjectAccess soa(env);return FindMethodID(soa, java_class, name, sig, false);}static jmethodID GetStaticMethodID(JNIEnv* env, jclass java_class, const char* name,const char* sig) {CHECK_NON_NULL_ARGUMENT(java_class);CHECK_NON_NULL_ARGUMENT(name);CHECK_NON_NULL_ARGUMENT(sig);ScopedObjectAccess soa(env);return FindMethodID(soa, java_class, name, sig, true);}

https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/jni/jni_internal.cc;l=980

FindMethodID

GetStaticMethodID 和 GetMethodID 最终都是走到 FindMethodID ,只是传参 is_static 不同

static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class,const char* name, const char* sig, bool is_static)REQUIRES_SHARED(Locks::mutator_lock_) {ObjPtr<mirror::Class> c = EnsureInitialized(soa.Self(), soa.Decode<mirror::Class>(jni_class));if (c == nullptr) {return nullptr;}ArtMethod* method = nullptr;auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();if (c->IsInterface()) {method = c->FindInterfaceMethod(name, sig, pointer_size);} else {method = c->FindClassMethod(name, sig, pointer_size);}if (method != nullptr && ShouldDenyAccessToMember(method, soa.Self())) {method = nullptr;}if (method == nullptr || method->IsStatic() != is_static) {ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static");return nullptr;}return jni::EncodeArtMethod(method);
}

https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/jni/jni_internal.cc;l=430

最后返回的是 jni::EncodeArtMethod(method),这是 ART 中将 ArtMethod* 转换为 jmethodID 的封装过程。

ALWAYS_INLINE
static inline jmethodID EncodeArtMethod(ArtMethod* art_method) {return reinterpret_cast<jmethodID>(art_method);
}

https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/jni/jni_internal.h;l=55

jmethodID 在 ART 中其实就是一个 ArtMethod*,而 EncodeArtMethod 就是把 ArtMethod* 安全地封装成 jmethodID。

CallObjectMethod

CallObjectMethod / CallStaticObjectMethod 函数源码如下:

  static jobject CallObjectMethod(JNIEnv* env, jobject obj, jmethodID mid, ...) {va_list ap;va_start(ap, mid);ScopedVAArgs free_args_later(&ap);CHECK_NON_NULL_ARGUMENT(obj);CHECK_NON_NULL_ARGUMENT(mid);ScopedObjectAccess soa(env);JValue result(InvokeVirtualOrInterfaceWithVarArgs(soa, obj, mid, ap));return soa.AddLocalReference<jobject>(result.GetL());}static jobject CallStaticObjectMethod(JNIEnv* env, jclass, jmethodID mid, ...) {va_list ap;va_start(ap, mid);ScopedVAArgs free_args_later(&ap);CHECK_NON_NULL_ARGUMENT(mid);ScopedObjectAccess soa(env);JValue result(InvokeWithVarArgs(soa, nullptr, mid, ap));jobject local_result = soa.AddLocalReference<jobject>(result.GetL());return local_result;}

https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/jni/jni_internal.cc;l=997

InvokeVirtualOrInterfaceWithVarArgs 与 InvokeWithVarArgs

JValue InvokeVirtualOrInterfaceWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa,jobject obj, jmethodID mid, va_list args) {// We want to make sure that the stack is not within a small distance from the// protected region in case we are calling into a leaf function whose stack// check has been elided.if (UNLIKELY(__builtin_frame_address(0) < soa.Self()->GetStackEnd())) {ThrowStackOverflowError(soa.Self());return JValue();}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);if (is_string_init) {// For string init, remap original receiver to StringFactory result.UpdateReference(soa.Self(), obj, result.GetL());}return result;
}JValue InvokeWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, jobject obj, jmethodID mid,va_list args)REQUIRES_SHARED(Locks::mutator_lock_) {// We want to make sure that the stack is not within a small distance from the// protected region in case we are calling into a leaf function whose stack// check has been elided.if (UNLIKELY(__builtin_frame_address(0) < soa.Self()->GetStackEnd())) {ThrowStackOverflowError(soa.Self());return JValue();}ArtMethod* method = 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);}ObjPtr<mirror::Object> receiver = method->IsStatic() ? nullptr : soa.Decode<mirror::Object>(obj);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);if (is_string_init) {// For string init, remap original receiver to StringFactory result.UpdateReference(soa.Self(), obj, result.GetL());}return result;
}

https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/reflection.cc;l=616

所以,无论是静态方法还是非静态方法最终都会调用到 InvokeWithArgArray

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()->IsCheckJniEnabled())) {CheckMethodArguments(soa.Vm(), method->GetInterfaceMethodIfProxy(kRuntimePointerSize), args);}method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, shorty);
}

https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/reflection.cc;l=450

结论:对于 Java 函数的调用,最终由该函数对应的 ArtMethod 对象的 Invoke 函数完成。

如何 dump CodeItem?

dumpMethodCode

DexFIle 类添加 native 函数 dumpMethodCode,用于提取指定 ArtMethod 对应的 Dex 字节码(CodeItem)。

static void DexFile_dumpMethodCode(JNIEnv* env, jclass, jobject method) {// 将当前线程从 kNative 状态切换到 kRunnable,表示线程可以安全访问 Java 对象(如 jobject)。ScopedFastNativeObjectAccess soa(env);// 如果 Java 传入的 Method 不为 nullif(method != nullptr){// 将 java.lang.reflect.Method 转换为 ART 内部的 ArtMethod 指针ArtMethod* artmethod = ArtMethod::FromReflectedMethod(soa, method);// 调用 myfartInvokemyfartInvoke(artmethod);}return;
}

libcore/dalvik/src/main/java/dalvik/system/DexFile.java

public final class DexFile {private static native void dumpMethodCode(Object m);
}

myfartInvoke 方法中主动调用 ArtMethod 的 Invoke 方法

extern "C" void myfartInvoke(ArtMethod * artmethod) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {JValue *result = nullptr;Thread *self = nullptr;uint32_t temp = 6;uint32_t *args = &temp;uint32_t args_size = 6;artmethod->Invoke(self, args, args_size, result, "fart");
}

在 ArtMethod::Invoke 方法中,检测到 self 是 nullptr,也就是 myfartInvoke 的主动调用时,调用 dumpArtMethod 把解密后的函数 dump 下来

void ArtMethod::Invoke(Thread * self, uint32_t * args, uint32_t args_size, JValue * result,const char *shorty) {if (self == nullptr) {dumpArtMethod(this);return;}...        
}

art/runtime/art_method.cc

获取 CodeItem 起始地址

CodeItem 起始地址就在 ArtMethod 的 dex_code_item_offset_ 字段。

不同 Android 版本略有不同

word/media/image1.png
https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/art_method.h;l=755

通过 GetCodeItemOffset() 方法拿到 CodeItem 的起始地址

word/media/image2.png
https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/art_method.h;l=378

CodeItem 结构定义

Android 源码中 CodeItem 的结构定义如下:

struct CodeItem : public dex::CodeItem {// DEX 字节码必须按照 4 字节对齐static constexpr size_t kAlignment = 4;private:// 方法使用的虚拟寄存器数量(包括本地变量和参数)uint16_t registers_size_;// 方法的入参占用的寄存器数量uint16_t ins_size_;// 方法调用其他方法时所需的最大出参寄存器数量(即调用其他方法时的参数空间)uint16_t outs_size_;// try-catch 块的数量。如果不为 0,则在 insns_ 后紧跟 try_item 和 catch_handler。uint16_t tries_size_;// 调试信息在 DEX 文件中的偏移,指向 debug_info 结构// 包括局部变量名、源码行号映射等uint32_t debug_info_off_;// 指令(insns_)数组长度,单位是 2 字节(code units)// 每条指令通常是 2 字节对齐,有些指令占用多个 code unituint32_t insns_size_in_code_units_;// 指令数组(实际大小是可变的,柔性数组)// 存放 DEX 字节码指令,insns_size_in_code_units_ 表示其长度uint16_t insns_[1];
};

https://cs.android.com/android/platform/superproject/+/android10-release:art/libdexfile/dex/standard_dex_file.h;l=35

https://cs.android.com/android/platform/superproject/+/android10-release:art/libdexfile/dex/compact_dex_file.h;l=87

CodeItem 前 16 字节是固定结构

字节偏移字段名含义说明大小(字节)
0x00registers_size_方法使用的寄存器数(本地变量 + 参数)2
0x02ins_size_方法参数占用的寄存器数(入参)2
0x04outs_size_调用其他方法所需的最大出参寄存器数(临时参数空间)2
0x06tries_size_try-catch 块数量,非 0 时表示有异常处理结构2
0x08debug_info_off_调试信息在 DEX 文件中的偏移4
0x0Cinsns_size_in_code_units_指令数组长度(单位为 2 字节 code unit)4
共计16 字节

CodeItem 前 16 字节是方法的执行元信息,后面的 insns_ 是变长的字节码数组,长度由 insns_size_in_code_units_ 决定,之后可能还有异常处理相关结构(try_items 和 catch_handlers)。

TryItem

TryItem 结构体源码如下 :

// Raw try_item.
struct TryItem {uint32_t start_addr_;uint16_t insn_count_;uint16_t handler_off_;
};

https://cs.android.com/android/platform/superproject/+/android10-release:art/libdexfile/dex/dex_file_structs.h;l=196

字段名类型说明
start_addr_uint32_t指向 code_item->insns[] 的某个偏移,表示 try 块的起始位置(单位为 16-bit 指令)
insn_count_uint16_ttry 块中包含多少条指令(单位为 16-bit 指令)
handler_off_uint16_t是 hander 结构的偏移,这个偏移是从 insns_ + sizeof(TryItem) * tries_size_ 开始的,用于描述 catch/finally 的处理逻辑

完整示例:带各种 TryItem 情况的 Kotlin 类

package com.cyrus.example.shellclass TryItemExample {// ✅ 0. 没有 try-catch(不会生成 TryItem)fun noTryCatch(): Int {val a = 1val b = 2return a + b}// ✅ 1. 简单 try-catch(一个 TryItem)fun simpleTryCatch(): String {return try {val x = 10 / 2"Result: $x"} catch (e: Exception) {"Caught Exception"}}// ✅ 2. 多个 catch 分支(一个 TryItem,多个 handler entry)fun multiCatch(input: String?): Int {return try {input!!.length} catch (e: NullPointerException) {-1} catch (e: Exception) {-2}}// ✅ 3. try-catch-finally(一个 TryItem + finally handler)fun tryCatchFinally(): Int {return try {1 / 0} catch (e: ArithmeticException) {-100} finally {println("finally block executed")}}// ✅ 4. 嵌套 try-catch(两个 TryItem,嵌套结构)fun nestedTryCatch(): String {return try {try {val data = "123".toInt()"Parsed: $data"} catch (e: NumberFormatException) {"Inner Catch"}} catch (e: Exception) {"Outer Catch"}}// ✅ 5. 只有 finally,无 catch(一个 TryItem,无 handler entry)fun onlyFinally(): Int {try {val x = 1 + 1} finally {println("executing finally without catch")}return 0}// ✅ 6. 多个独立 try 块(多个 TryItem,非嵌套)fun multipleTryBlocks(): Int {try {val a = 10 / 2} catch (e: Exception) {println("First catch")}try {val b = "abc".toInt()} catch (e: NumberFormatException) {println("Second catch")}return 0}
}

编译运行得到 dex 文件

word/media/image3.png

通过 GDA 查看 dex 中 multipleTryBlocks 函数的 TryItem 信息,可以看到 0004 到 0005 就是第一个 try 块的开始和结束地址

word/media/image4.png

在 010Editor 中打开 dex ,在如下路径

dex_class_defs/class_def[17]/class_def[11] public final com.cyrus.example.shell.TryItemExample/class_data/virtual_methods/method[1] public final int com.cyrus.example.shell.TryItemExample.multipleTryBlocks()/code/tries[2]

可以看到 TryItem 数据

word/media/image5.png

CatchHandlerItem

在 DEX 文件中,TryItem 表示一个异常捕获区域,其对应的异常处理器(handler)由 remaining_count_ 指示 catch/finally 块的数量和类型:

  • 如果 remaining_count_ <= 0 表示最后一个是 finally 块。

  • 如果 remaining_count_ > 0 表示只有 catch 块。

  • remaining_count_ 的绝对值表示 catch 块的个数。

  • 如果 remaining_count_ == 0 表示没有 catch 块,只有 finally 块。

CatchHandlerItem:表示一个 catch 分支(即一个异常类型和对应的处理地址)。

class CatchHandlerIterator {...struct CatchHandlerItem {dex::TypeIndex type_idx_;  // 捕获异常的类型索引(指向 Dex 文件中的 type_ids 表)uint32_t address_;         // 异常处理器的代码地址(从 code_item.insns 的偏移量)} handler_;const uint8_t* current_data_;  // the current handler in dex file.int32_t remaining_count_;   // number of handlers not read.bool catch_all_;            // is there a handler that will catch all exceptions in case// that all typed handler does not match.
};

https://cs.android.com/android/platform/superproject/+/android10-release:art/libdexfile/dex/dex_file_exception_helpers.h;l=67

在 010Editor 中打开 dex ,在如下路径

dex_class_defs/class_def[17]/class_def[11] public final com.cyrus.example.shell.TryItemExample/class_data/virtual_methods/method[1] public final int com.cyrus.example.shell.TryItemExample.multipleTryBlocks()/code/handlers

可以看到 CatchHandlerItem 数据

word/media/image6.png

一个 TryItem 对应一个 CatchHandlerIterator ,CatchHandlerIterator 中有一个或多个 CatchHandlerItem,共同组成 try catch/finally 块。

word/media/image7.png

CodeItem 的长度计算和 dump

CodeItem 的长度计算和 dump,两种情况:

  1. 无异常处理

  2. 有异常处理

具体实现代码在 FART 项目 art/runtime/art_method.cc 的 dumpArtMethod 函数里

extern "C" void dumpArtMethod(ArtMethod * artmethod) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {// 分配临时路径缓冲区char *dexfilepath = (char *) malloc(sizeof(char) * 2000);if (dexfilepath == nullptr) {LOG(INFO) << "ArtMethod::dumpArtMethod invoked, methodname: "<< PrettyMethod(artmethod).c_str()<< " malloc 2000 byte failed";return;}// 获取进程名(用于保存路径)int fcmdline = -1;char szCmdline[64] = { 0 };char szProcName[256] = { 0 };int procid = getpid();sprintf(szCmdline, "/proc/%d/cmdline", procid);fcmdline = open(szCmdline, O_RDONLY, 0644);if (fcmdline > 0) {read(fcmdline, szProcName, 256);close(fcmdline);}// 如果成功获取进程名if (szProcName[0]) {// 获取方法所属的 DexFileconst DexFile *dex_file = artmethod->GetDexFile();const char *methodname = PrettyMethod(artmethod).c_str();const uint8_t *begin_ = dex_file->Begin();  // dex 数据起始地址size_t size_ = dex_file->Size();            // dex 大小// 创建 fart 路径memset(dexfilepath, 0, 2000);sprintf(dexfilepath, "%s", "/sdcard/fart");mkdir(dexfilepath, 0777);// 创建 fart/进程名 路径memset(dexfilepath, 0, 2000);sprintf(dexfilepath, "/sdcard/fart/%s", szProcName);mkdir(dexfilepath, 0777);// 保存 dex 文件到 /sdcard/fart/进程名/xxx_dexfile.dexmemset(dexfilepath, 0, 2000);sprintf(dexfilepath, "/sdcard/fart/%s/%d_dexfile.dex", szProcName, (int)size_);int dexfilefp = open(dexfilepath, O_RDONLY, 0666);if (dexfilefp > 0) {// 如果文件已存在,不再写入close(dexfilefp);dexfilefp = 0;} else {// 写入完整 dex 数据dexfilefp = open(dexfilepath, O_CREAT | O_RDWR, 0666);if (dexfilefp > 0) {write(dexfilefp, (void *) begin_, size_);fsync(dexfilefp);close(dexfilefp);}}// 获取该方法的 CodeItem(即字节码结构体)const DexFile::CodeItem * code_item = artmethod->GetCodeItem();if (LIKELY(code_item != nullptr)) {int code_item_len = 0;uint8_t *item = (uint8_t *) code_item;// 如果有异常处理结构,计算尾部偏移if (code_item->tries_size_ > 0) {const uint8_t *handler_data = (const uint8_t *) (DexFile::GetTryItems(*code_item, code_item->tries_size_));// 解析 handler 数据结构,返回 handler 数据结束的指针位置uint8_t *tail = codeitem_end(&handler_data);code_item_len = (int) (tail - item);} else {// 没有 try 块,长度 = 16字节头部 + 指令长度(每个指令2字节)code_item_len = 16 + code_item->insns_size_in_code_units_ * 2;}// 组合保存路径memset(dexfilepath, 0, 2000);int size_int = (int) dex_file->Size();uint32_t method_idx = artmethod->get_method_idx();sprintf(dexfilepath, "/sdcard/fart/%s/%d_%ld.bin", szProcName, size_int, gettidv1());// 打开保存 CodeItem 信息的文件int fp2 = open(dexfilepath, O_CREAT | O_APPEND | O_RDWR, 0666);if (fp2 > 0) {lseek(fp2, 0, SEEK_END);// 写入方法信息(name、method_idx、offset、code_item_len)memset(dexfilepath, 0, 2000);int offset = (int)(item - begin_);sprintf(dexfilepath,"{name:%s,method_idx:%d,offset:%d,code_item_len:%d,ins:",methodname, method_idx, offset, code_item_len);int contentlength = strlen(dexfilepath);write(fp2, (void *) dexfilepath, contentlength);// base64 编码指令数据并写入long outlen = 0;char *base64result = base64_encode((char *) item, (long)code_item_len, &outlen);write(fp2, base64result, outlen);write(fp2, "};", 2);fsync(fp2);close(fp2);// 清理内存if (base64result != nullptr) {free(base64result);base64result = nullptr;}}}}// 释放路径缓冲区if (dexfilepath != nullptr) {free(dexfilepath);dexfilepath = nullptr;}
}

解析 CodeItem 的异常处理数据部分,返回 handler 数据结束的指针位置

uint8_t *codeitem_end(const uint8_t **pData) {// 读取 handler 列表的数量(即有多少个 try 块)uint32_t num_of_list = DecodeUnsignedLeb128(pData);// 遍历每个 try 块的 handler 列表for (; num_of_list > 0; num_of_list--) {// 读取当前 handler 列表中 handler 的数量,可能为负值,表示存在 catch-all handlerint32_t num_of_handlers = DecodeSignedLeb128(pData);// 取绝对值,得到实际的 type-handler 对数量int num = num_of_handlers;if (num_of_handlers <= 0) {num = -num_of_handlers;  // catch-all handler 也算在内,但单独处理}// 读取每个 handler 的 type_idx 和 addressfor (; num > 0; num--) {DecodeUnsignedLeb128(pData);  // type_idxDecodeUnsignedLeb128(pData);  // address}// 如果存在 catch-all handler,再额外读取一个地址if (num_of_handlers <= 0) {DecodeUnsignedLeb128(pData);  // catch-all address}}// 此时 *pData 已指向异常处理部分结束的位置return (uint8_t *)(*pData);
}

如何实现主动调用?

fart 函数中遍历当前 App 的 ClassLoader,拿到 ClassLoader 需要判断不是系统的 BootClassLoader,不然会把系统框架的 dex dump 下来

public static void fart() {ClassLoader appClassloader = getClassloader();if(appClassloader == null){Log.e("ActivityThread", "appClassloader is null");return;}if(appClassloader.toString().indexOf("java.lang.BootClassLoader") == -1){fartWithClassLoader(appClassloader);}ClassLoader tmpClassloader = appClassloader;ClassLoader parentClassloader = appClassloader.getParent();while(parentClassloader != null){if(parentClassloader.toString().indexOf("java.lang.BootClassLoader") == -1){fartWithClassLoader(parentClassloader);}tmpClassloader = parentClassloader;parentClassloader = parentClassloader.getParent();}
}

fartWithClassLoader 函数中遍历 ClassLoader 中的所有 DexFile,获取所有类名,并调用 native 方法 dumpMethodCode。

public static void fartWithClassLoader(ClassLoader appClassloader) {Log.i("ActivityThread", "fartWithClassLoader " + appClassloader.toString());// 用于存放获取到的 dexFile 对象List<Object> dexFilesArray = new ArrayList<Object>();// 获取 DexPathList 对象实例Object pathList_object = getFieldOjbect("dalvik.system.BaseDexClassLoader", appClassloader, "pathList");// 获取 dexElements 字段,类型为 DexPathList$Element[],每个 element 封装了 dexFileObject[] ElementsArray = (Object[]) getFieldOjbect("dalvik.system.DexPathList", pathList_object, "dexElements");// 声明 dexElements 中的 dexFile 字段Field dexFile_fileField = null;try {dexFile_fileField = (Field) getClassField(appClassloader, "dalvik.system.DexPathList$Element", "dexFile");} catch (Exception e) {e.printStackTrace();}// 通过类加载器反射获取 dalvik.system.DexFile 类Class DexFileClazz = null;try {DexFileClazz = appClassloader.loadClass("dalvik.system.DexFile");} catch (Exception e) {e.printStackTrace();}// 要调用的 native 方法Method getClassNameList_method = null;Method defineClass_method = null;Method dumpMethodCode_method = null;// 遍历 DexFile 类中的方法,设置需要的 native 方法强制可访问for (Method field : DexFileClazz.getDeclaredMethods()) {if (field.getName().equals("getClassNameList")) {getClassNameList_method = field;getClassNameList_method.setAccessible(true);}if (field.getName().equals("defineClassNative")) {defineClass_method = field;defineClass_method.setAccessible(true);}if (field.getName().equals("dumpMethodCode")) {dumpMethodCode_method = field;dumpMethodCode_method.setAccessible(true);}}// 获取 mCookie 字段(DexFile 中指向 native Dex 的句柄)Field mCookiefield = getClassField(appClassloader, "dalvik.system.DexFile", "mCookie");// 遍历每一个 dex element(即每个 dex 文件)for (int j = 0; j < ElementsArray.length; j++) {Object element = ElementsArray[j];Object dexfile = null;try {// 通过反射获取 DexFile 对象dexfile = (Object) dexFile_fileField.get(element);} catch (Exception e) {e.printStackTrace();}if (dexfile == null) {continue;}// 加入列表dexFilesArray.add(dexfile);// 获取 native mCookie 对象Object mcookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mCookie");if (mcookie == null) {continue;}// 调用 native 方法 getClassNameList(mcookie) 获取 dex 中包含的类名数组String[] classnames = null;try {classnames = (String[]) getClassNameList_method.invoke(dexfile, mcookie);} catch (Exception e) {e.printStackTrace();continue;} catch (Error e) {e.printStackTrace();continue;}// 遍历所有类名,对每个类调用 dumpMethodCodeif (classnames != null) {for (String eachclassname : classnames) {loadClassAndInvoke(appClassloader, eachclassname, dumpMethodCode_method);}}}return;
}

主动加载 dex 中的所有类需要对 dex 文件解析,获取 dex 中的所有类列表,两种解决方案:

  1. 手动解析 dex 文件

  2. 直接调用 aosp 源码中已有的 api:getClassNameList 即可(需要通过反射一步步获取当前 ClassLoader 当中的 mCookie)

拿到 ClassLoader 的 dexElements ,迭代 dexElements 通过反射拿到 DexFile 的 mCookie

word/media/image8.png
https://cs.android.com/android/platform/superproject/+/android10-release:libcore/dalvik/src/main/java/dalvik/system/DexPathList.java;l=69

调用 DexFile.getClassNameList 传递 mCookie 得到 dex 的 class list

word/media/image9.png
https://cs.android.com/android/platform/superproject/+/android10-release:libcore/dalvik/src/main/java/dalvik/system/DexFile.java;l=432

拿到 ClassNameList 后,调用 loadClassAndInvoke 进行主动加载;

LoadClassAndInvoke 中对类进行加载,遍历类中构造函数和普通函数,进行主动调用。

public static void loadClassAndInvoke(ClassLoader appClassloader, String eachclassname, Method dumpMethodCode_method) {Log.i("ActivityThread", "go into loadClassAndInvoke->" + "classname:" + eachclassname);Class resultclass = null;try {// 使用给定的 ClassLoader 加载指定类名的类resultclass = appClassloader.loadClass(eachclassname);} catch (Exception e) {e.printStackTrace();return;} catch (Error e) {e.printStackTrace();return;}// 如果类加载成功if (resultclass != null) {try {// 获取该类的所有构造方法(包括 private 的)Constructor<?> cons[] = resultclass.getDeclaredConstructors();for (Constructor<?> constructor : cons) {if (dumpMethodCode_method != null) {try {// 调用 native 方法 dumpMethodCode 传入构造方法对象// 发起主动调用 并 dump methoddumpMethodCode_method.invoke(null, constructor);} catch (Exception e) {e.printStackTrace();continue;} catch (Error e) {e.printStackTrace();continue;}} else {Log.e("ActivityThread", "dumpMethodCode_method is null ");}}} catch (Exception e) {e.printStackTrace();} catch (Error e) {e.printStackTrace();}try {// 获取该类的所有方法(包括 private 的)Method[] methods = resultclass.getDeclaredMethods();if (methods != null) {for (Method m : methods) {if (dumpMethodCode_method != null) {try {// 调用 native 方法 dumpMethodCodedumpMethodCode_method.invoke(null, m);} catch (Exception e) {e.printStackTrace();continue;} catch (Error e) {e.printStackTrace();continue;}} else {Log.e("ActivityThread", "dumpMethodCode_method is null ");}}}} catch (Exception e) {e.printStackTrace();} catch (Error e) {e.printStackTrace();}}
}

通过 loadClass 加载类,为什么不用 Class.forName?因为 Class.forName 会调用类的初始化方法,有一些对抗手段会在类初始化中执行,所以用 loadClass,loadClass 不会调用类的初始化方法。

resultclass = appClassloader.loadClass(eachclassname);

进入到 ArtMethod 的 Invoke 方法后,识别出是我们自己发起的主动调用后,执行 dumpArtMethod

void ArtMethod::Invoke(Thread* self, uint32_t* args,uint32_t args_size, JValue* result,const char* shorty) {// 若 self 为空,直接 dump 当前 ArtMethodif (self == nullptr) {dumpArtMethod(this);return;}...
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词