#本人水平有限,大佬发现文章问题请批评改正#
PathClassLoader
PathClassLoader(String dexPath, ClassLoader parent)
Dalvik调用链:ApplicationLoaders (new)-> pathClassloader (继承) -> BaseDexClassLoader (new) -> DexPathList (构造中调用)-> makeDexElements (调用)-> loadDexFile (new或调用)-> DexFile或loadDex (调用)-> openDexFile (调用)-> openDexFileNative (调用)-> dvmRawDexFileOpen (调用)-> dvmOptimizeDexFile /*dvm优化Dex文件*/ fork子进程,获取root权限以拼接出路径 /system/bin/dexopt,启动dexopt程序 (调用)-> fromDex (调用)-> 1.dvmPrepForDexOpt(启动一个虚拟机进程) 2.dvmContinueOptimization(干活) (调用)-> rewriteDex(找不到后面了) 最后调用dvmRawDexFileOpen中的dvmOptimizeDexFile方法将优化后dex映射进内存。
Dalvik加载dex过程其实就是将一个类转换为ClassObject的一个实例化对象,将ClassObject添加到全局变量gDvm中的loadedClasses成员中;执行时通过dvmInterpret初始化解释器,执行字节码指令。
loadDexFile:判断是否有优化路径,没有则生成,有则直接使用优化路径调用loadDex,就是APP是否是第一次运行。
05-DALVIK加载和解析DEX过程 - Domefy - 博客园 (cnblogs.com)
ArtMethod结构 :android-9.0.0_r30
class ArtMethod {
…………
protect:
// Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
// The class we are a part of.
//GCroot添加在N,实际是个指针,这个字段即是表明所属类,一般用喜闻乐见的uint32_t代替
GcRoot<mirror::Class> declaring_class_;
// Access flags; low 16 bits are defined by spec.
// Getting and setting this flag needs to be atomic when concurrency is
// possible, e.g. after this method's class is linked. Such as when setting
// verifier flags and single-implementation flag.
//可以理解为该函数的标志位,如函数为public,private,static,native等。
//这个字段java2native,xposed hook的标志位。
//值得一提的是,9.0以后的Hid API,对应函数GetHiddenApiAccessFlags,不是这个,但是看到这里,可以考虑如何修改hide标志位
std::atomic<std::uint32_t> access_flags_;
/* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */
// Offset to the CodeItem.
//加固代码替换点。
//里面指向code_item指针,code_item存储的实际是dex当中的字节码.其用处本来是适配dalvik解释器,即无法编译成机器码的,用解释器来执行。
//
uint32_t dex_code_item_offset_;
// Index into method_ids of the dex file associated with this method.
//主要作为寻址替换用
uint32_t dex_method_index_;
/* End of dex file fields. */
// Entry within a dispatch table for this method. For static/direct methods the index is into
// the declaringClass.directMethods, for virtual methods the vtable and for interface methods the
// ifTable.
uint16_t method_index_;
// The hotness we measure for this method. Not atomic, as we allow
// missing increments: if the method is hot, we will see it eventually.
//方法被记录的频率热度,用在Android N+混合编译部分,根据该值来判断是否使用JIT编译。
uint16_t hotness_count_;
// Fake padding field gets inserted here.
// Must be the last fields in the method.
//没错,这个Fields9.0地址又改了,需适配,在下面详细介绍每个字段
struct PtrSizedFields {
// Depending on the method type, the data is
// - native method: pointer to the JNI function registered to this method
// or a function to resolve the JNI function,
// - conflict method: ImtConflictTable,
// - abstract/interface method: the single-implementation if any,
// - proxy method: the original interface method or constructor,
// - other methods: the profiling data.
//这个字段顾名思义,根据方法类型,会充当不同作用,是不是可以理解为以前几个指针的复合……
void* data_;
// Method dispatch from quick compiled code invokes this pointer which may cause bridging into
// the interpreter.
void* entry_point_from_quick_compiled_code_;
} ptr_sized_fields_;
…………
}
DexClassLoader
目标:基于frida写一个dexdump
在 Android 系统中,每个加载的 DEX(Dalvik Executable)文件都会在内存中被添加至一个叫做「DexPathList」的链表,用于存储加载的 DEX 文件的信息。这个链表是由类加载器(ClassLoader)的实现类「PathClassLoader」或「DexClassLoader」维护的,其中「DexPathList」是其内部类。当加载一个包含 DEX 文件的 APK 或者其它形式的 DEX 文件时,类加载器会将这些 DEX 文件添加到「DexPathList」链表中。
从DexClassLoader源码往里面跟,第一个关键地方 makeDexElements() ;该函数判断传入路径内是否存在 以 .dex 结尾文件,之后通过 loadDexFile 函数尝试加载dex文件,加载过后返回DexFile 对象,之后再将该dex加入由系统维护的 elements 数组中。
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
Element[] elements = new Element[files.size()];
int elementsPos = 0;
/*
* Open all files and load the (direct or contained) dex files up front.
*/
for (File file : files) {
if (file.isDirectory()) {
// We support directories for looking up resources. Looking up resources in
// directories is useful for running libcore tests.
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) {
String name = file.getName();
DexFile dex = null;
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
if (dex != null) {
elements[elementsPos++] = new Element(dex, null);
}
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + file, suppressed);
suppressedExceptions.add(suppressed);
}
} else {
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {
/*
* IOException might get thrown "legitimately" by the DexFile constructor if
* the zip file turns out to be resource-only (that is, no classes.dex file
* in it).
* Let dex == null and hang on to the exception to add to the tea-leaves for
* when findClass returns null.
*/
suppressedExceptions.add(suppressed);
}
if (dex == null) {
elements[elementsPos++] = new Element(file);
} else {
elements[elementsPos++] = new Element(dex, file);
}
}
if (dex != null && isTrusted) {
dex.setTrusted();
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}
so文件中的 fork 函数
int sub_83DC()
{
int i; // r0
__pid_t v1; // r5
__pid_t v2; // r0
FILE *v4; // r5
int v5; // r8
__pid_t pid; // [sp+4h] [bp-194h]
char v7[10]; // [sp+8h] [bp-190h] BYREF
char v8[118]; // [sp+12h] [bp-186h] BYREF
char s[128]; // [sp+88h] [bp-110h] BYREF
char format[128]; // [sp+108h] [bp-90h] BYREF
int v11; // [sp+188h] [bp-10h]
qmemcpy(format, &unk_F130, sizeof(format));
format[0] = 47;
for ( i = 1; i != 128; ++i )
format[i] ^= 0xE9u;
v1 = getpid();
sprintf(s, format, v1);
v2 = fork();/*此时在父进程中,fork()函数为 #创建自身进程副本# 所以子进程要执行的代码
与父进程是相同的,Linux系统会通过不同的fork函数的返回值帮助我们编写子进程与父进程的不同代码
fork函数在父进程中的返回值为新建的子进程的pid,但是在子进程中的返回值为0,
所以我们可以通过判断fork函数的返回值来区别子进程与父进程的执行逻辑*/
if ( !v2 ) //在子进程中返回值为0,!0为真,所以在子进程中会执行if中的内容
{
pid = v1;
if ( v1 == getppid() )
{
ptrace(PTRACE_TRACEME);
v4 = fopen(s, &format[16]);
if ( v4 )
{
while ( 1 )
{
while ( !fgets(v7, 128, v4) )
{
LABEL_11:
sleep(2u);
v4 = fopen(s, &format[16]);
if ( !v4 )
goto LABEL_12;
}
if ( !strncmp(v7, &format[18], 9u) )
{
v5 = atoi(v8);
fclose(v4);
if ( v5 )
break;
goto LABEL_11;
}
}
}
LABEL_12:
kill(pid, 9);
}
LABEL_13:
exit(1);
}
//在父进程中返回子进程的pid,进不去if判断,所以从这里开始执行
if ( v2 == -1 )
goto LABEL_13;
return _stack_chk_guard - v11;
}
linker加载ELF文件过程(自执行类型)
ps:mlbz,看错了,分析了一半多点,对付看吧
环境:ida7.6 Android 9 piexl 3
linker主函数:入口点 __linker_init()函数
extern "C" ElfW(Addr) __linker_init(void* raw_args) {
KernelArgumentBlock args(raw_args);
static uintptr_t linktime_addr = reinterpret_cast<uintptr_t>(&linktime_addr);
//linker文件在内存中实际映射的基地址,在Android 7之前,linker_addr是通过是直接从ELF辅助向量中读取AT_BASE获得。Android 8之后,通过定义静态变量linktime_addr是来计算linker_addr。
//ida动态调试时代码:v10 = &_dl__ZZ13__linker_initE13linktime_addr - _dl__ZZ13__linker_initE13linktime_addr;
ElfW(Addr) linker_addr = reinterpret_cast<uintptr_t>(&linktime_addr) - linktime_addr;
#if defined(__clang_analyzer__)
linker_addr += reinterpret_cast<uintptr_t>(raw_args);
#endif
//读取辅助向量,返回内核加载ELF文件的起始地址
ElfW(Addr) entry_point = args.getauxval(AT_ENTRY);
//解析ELF的hdr和phdr
ElfW(Ehdr)* elf_hdr = reinterpret_cast<ElfW(Ehdr)*>(linker_addr);
ElfW(Phdr)* phdr = reinterpret_cast<ElfW(Phdr)*>(linker_addr + elf_hdr->e_phoff);
//创建soinfo对象,每个由linker加载到内存的ELF,都会有一个soinfo对象表示,同一个动态库dlopen两次会创建两个soinfo对象,且文件会被映射到不同的内存中
//此时创建的soinfo对象保存在栈中,后面会使用get_libdl_info()函数在堆中分配一个soinfo对象,在放入系统solist中
//ida func:_dl__ZN6soinfoC1EP19android_namespace_tPKcPK4statli
soinfo linker_so(nullptr, nullptr, nullptr, 0, 0);
//linker的ELF文件格式解析
linker_so.base = linker_addr;
//传入program_header_table地址与header table的数量,计算整个PT_LOAD节的长度之和
linker_so.size = phdr_table_get_load_size(phdr, elf_hdr->e_phnum);
//计算load_bias,load_bias = elf_hdr + 第一个PT_LOAD节的p_offset - p_vaddr
linker_so.load_bias = get_elf_exec_load_bias(elf_hdr);
linker_so.dynamic = nullptr;
linker_so.phdr = phdr;
linker_so.phnum = elf_hdr->e_phnum;
//构建linker的soinfo时,此函数才会标记
linker_so.set_linker_flag();
// 解析.dynamic的符号表、字符串表、plt、got等到内存中,确定每个表的内存位置,大小和一些其他参数。
if (!linker_so.prelink_image()) __linker_cannot_link(args.argv[0]);
//重定位
if (!linker_so.link_image(g_empty_list, g_empty_list, nullptr)) __linker_cannot_link(args.argv[0]);
#if defined(__i386__)
__libc_init_sysinfo(args);
#endif
// libc中的tls相关
__libc_init_main_thread(args);
//设置最后一个program_header_table节属性
if (!linker_so.protect_relro()) __linker_cannot_link(args.argv[0]);
// Initialize the linker's static libc's globals
__libc_init_globals(args);
// store argc/argv/envp to use them for calling constructors
g_argc = args.argc;
g_argv = args.argv;
g_envp = args.envp;
//递归get_childred()函数返回的soinfo列表,然后执行ELF中的init_func_(DT_init段)和init_arrary(DT_init_array段)函数
linker_so.call_constructors();
//判断正在加载的ELF是不是linker,_start是linker的入口地址,entry_point是正在加载的ELF的地址
if (reinterpret_cast<ElfW(Addr)>(&_start) == entry_point) {
async_safe_format_fd(STDOUT_FILENO,
"This is %s, the helper program for dynamic executables.\n",
args.argv[0]);
exit(0);
}
//将ELF加入到 linker_linker_map 双向链表中
init_linker_info_for_gdb(linker_addr, kLinkerPath);
//拷贝栈上的soinfo到堆中,加入到系统solist中
sonext = solist = get_libdl_info(kLinkerPath, linker_so, linker_link_map);
g_default_namespace.add_soinfo(solist);
args.abort_message_ptr = &g_abort_message;
//这个函数是__linker_init()在返回前最后调用的函数,它要创建可执行文件对应的soinfo、调用find_librarys加载依赖库等工作,完成可执行文件运行所需的一系列准备工作。
ElfW(Addr) start_address = __linker_init_post_relocation(args);
INFO("[ Jumping to _start (%p)... ]", reinterpret_cast<void*>(start_address));
return start_address;
}
linker_so.protect_relro()函数
//linker_so.protect_relro() 调用 phdr_table_protect_gnu_relro(phdr, phnum, load_bias) 调用 _phdr_table_set_gnu_relro_prot(phdr_table, phdr_count, load_bias, PROT_READ)
static int _phdr_table_set_gnu_relro_prot(const ElfW(Phdr)* phdr_table, size_t phdr_count,ElfW(Addr) load_bias, int prot_flags) {
//phdr:program_header_table在内存中的起始地址
//phdr_limit:program_header_table节在内存中的结束地址
const ElfW(Phdr)* phdr = phdr_table;
const ElfW(Phdr)* phdr_limit = phdr + phdr_count;
//遍历找到program_header_table中的最后一个节
for (phdr = phdr_table; phdr < phdr_limit; phdr++) {
if (phdr->p_type != PT_GNU_RELRO) {
continue;
}
//设置p_vaddr和p_vaddr + p_memsz的页对齐,计算最后一个节在内存中的起始和结束地址
ElfW(Addr) seg_page_start = PAGE_START(phdr->p_vaddr) + load_bias;
ElfW(Addr) seg_page_end = PAGE_END(phdr->p_vaddr + phdr->p_memsz) + load_bias;
//使用mprotect函数改变最后一个节权限为只读
int ret = mprotect(reinterpret_cast<void*>(seg_page_start),
seg_page_end - seg_page_start,
prot_flags);
if (ret < 0) {
return -1;
}
}
return 0;
}
linker_so.call_constructors()函数
//call_constructors() -> call_array() -> call_function()(init_func_中的函数少了call_array的环节,直接从call_constructors中调用call_function函数执行)
static void call_function(const char* function_name __unused,linker_ctor_function_t function,const char* realpath __unused) {
if (function == nullptr || reinterpret_cast<uintptr_t>(function) == static_cast<uintptr_t>(-1)) {
return;
}
TRACE("[ Calling c-tor %s @ %p for '%s' ]", function_name, function, realpath);
//函数调用(此处的Android源码与动态ida调试的调用链不同)
//ida调用链:call_constructors()函数里面直接就会通过soinfo里面的偏移调用init相关函数,而不是像源码这样经过几个函数最终才会调用,可能是ida反编译的问题吧
function(g_argc, g_argv, g_envp);
TRACE("[ Done calling c-tor %s @ %p for '%s' ]", function_name, function, realpath);
}
init_linker_info_for_gdb函数
static link_map linker_link_map;
struct link_map {
ElfW(Addr) l_addr;
char* l_name;
ElfW(Dyn)* l_ld;
struct link_map* l_next;
struct link_map* l_prev;
};
static void init_linker_info_for_gdb(ElfW(Addr) linker_base, char* linker_path) {
//初始化linker_link_map双向链表中,正在加载的ELF文件的信息:ELF起始地址 与 ELF的路径名称
linker_link_map.l_addr = linker_base;
linker_link_map.l_name = linker_path;
//参数偏移计算
ElfW(Ehdr)* elf_hdr = reinterpret_cast<ElfW(Ehdr)*>(linker_base);
ElfW(Phdr)* phdr = reinterpret_cast<ElfW(Phdr)*>(linker_base + elf_hdr->e_phoff);
phdr_table_get_dynamic_section(phdr, elf_hdr->e_phnum, linker_base, &linker_link_map.l_ld, nullptr);
}
void phdr_table_get_dynamic_section(const ElfW(Phdr)* phdr_table, //program_header_table 在内存中的起始地址
size_t phdr_count, //program_header_table 中节的数量
ElfW(Addr) load_bias, //program_header_table 在内存中的起始地址
ElfW(Dyn)** dynamic, //解析出的动态节的存放位置
ElfW(Word)* dynamic_flags) //调试时为0,可能是linker的时候
{
*dynamic = nullptr;
for (size_t i = 0; i<phdr_count; ++i) {
const ElfW(Phdr)& phdr = phdr_table[i];
//遍历phdr寻找.dynamic段,ELF动态链接所需的函数符号,重定位等
if (phdr.p_type == PT_DYNAMIC) {
*dynamic = reinterpret_cast<ElfW(Dyn)*>(load_bias + phdr.p_vaddr);
if (dynamic_flags) {
*dynamic_flags = phdr.p_flags;
}
return;
}
}
}
/core/java/android/app/ActivityThread.java -> void handleBindApplication(AppBindData data)
//处理 Application 应用绑定方法 , 这是创建 Application 应用核心过程 ;