Android Linker详解(二)

  • Android Linker详解(二)

    • 本文目的
    • So的链接
    • So重定位
    • 总结

本文目的

接上篇Linker源码详解(一),本文继续来分析Linker的链接过程。为了更好的理解Unidbg的原理,我们需要了解很多细节。虽然一个模拟二进制执行框架的弊端很多,但也是未来二进制分析的一个很好的思路。

上篇文章我们讲解了Linker的装载,将So文件按PT_LOAD段的指示来将So加载到内存,那么我们这篇文章就来分析一下加载完之后又干了什么呢?

So的链接

http://androidxref.com/4.4.4_r1/xref/bionic/linker/linker.cpp#702

static soinfo* load_library(const char* name) {//...ElfReader elf_reader(name, fd);if (!elf_reader.Load()) {return NULL;}const char* bname = strrchr(name, '/');soinfo* si = soinfo_alloc(bname ? bname + 1 : name);if (si == NULL) {return NULL;}si->base = elf_reader.load_start();si->size = elf_reader.load_size();si->load_bias = elf_reader.load_bias();si->flags = 0;si->entry = 0;si->dynamic = NULL;si->phnum = elf_reader.phdr_count();si->phdr = elf_reader.loaded_phdr();return si;
}

上篇我们进入了elf_reader.Load()函数,阅读了Linker的装载源码,当装载结束后,对soinfo结构体进行赋值(So文件的头信息/装载的结果),并插入到链表,接着我们回到上层函数继续看

http://androidxref.com/4.4.4_r1/xref/bionic/linker/linker.cpp#751

static soinfo* find_library_internal(const char* name) {//...si = load_library(name);if (si == NULL) {return NULL;}// At this point we know that whatever is loaded @ base is a valid ELF// shared library whose segments are properly mapped in.TRACE("[ init_library base=0x%08x sz=0x%08x name='%s' ]",si->base, si->size, si->name);if (!soinfo_link_image(si)) {munmap(reinterpret_cast<void*>(si->base), si->size);soinfo_free(si);return NULL;}return si;
}

我们从上面这个函数中看到,当调用了load_library函数之后,又调用了soinfo_link_image这个函数。这个函数也就是我们今天分析的一个主要入口–链接

下面的这个函数很长,我给大家把不相关的代码去掉,大家先通过注释来看一遍这个函数在干什么

http://androidxref.com/4.4.4_r1/xref/bionic/linker/linker.cpp#1303


static bool soinfo_link_image(soinfo* si) {//拿到地址、段表指针、段表数Elf32_Addr base = si->load_bias;const Elf32_Phdr *phdr = si->phdr;int phnum = si->phnum;//...size_t dynamic_count;Elf32_Word dynamic_flags;//这个函数很简单,就是遍历段表,找到类型为PT_DYNAMIC的段phdr_table_get_dynamic_section(phdr, phnum, base, &si->dynamic,&dynamic_count, &dynamic_flags);if (si->dynamic == NULL) {if (!relocating_linker) {DL_ERR("missing PT_DYNAMIC in \"%s\"", si->name);}return false;} #ifdef ANDROID_ARM_LINKER//异常相关,有兴趣的同学可以看看(void) phdr_table_get_arm_exidx(phdr, phnum, base,&si->ARM_exidx, &si->ARM_exidx_count);
#endif//上面我们解析到了Dynamic段的地址跟数量,下面就开始遍历Dynamic信息uint32_t needed_count = 0;//DT_NULL表示结束for (Elf32_Dyn* d = si->dynamic; d->d_tag != DT_NULL; ++d) {DEBUG("d = %p, d[0](tag) = 0x%08x d[1](val) = 0x%08x", d, d->d_tag, d->d_un.d_val);switch(d->d_tag){case DT_HASH://哈希表si->nbucket = ((unsigned *) (base + d->d_un.d_ptr))[0];si->nchain = ((unsigned *) (base + d->d_un.d_ptr))[1];si->bucket = (unsigned *) (base + d->d_un.d_ptr + 8);si->chain = (unsigned *) (base + d->d_un.d_ptr + 8 + si->nbucket * 4);break;case DT_STRTAB://字符串表si->strtab = (const char *) (base + d->d_un.d_ptr);break;case DT_SYMTAB://符号表si->symtab = (Elf32_Sym *) (base + d->d_un.d_ptr);break;case DT_PLTREL://未处理if (d->d_un.d_val != DT_REL) {DL_ERR("unsupported DT_RELA in \"%s\"", si->name);return false;}break;case DT_JMPREL://PLT重定位表si->plt_rel = (Elf32_Rel*) (base + d->d_un.d_ptr);break;case DT_PLTRELSZ://PLT重定位表大小si->plt_rel_count = d->d_un.d_val / sizeof(Elf32_Rel);break;case DT_REL://重定位表si->rel = (Elf32_Rel*) (base + d->d_un.d_ptr);break;case DT_RELSZ://重定位表大小si->rel_count = d->d_un.d_val / sizeof(Elf32_Rel);break;case DT_PLTGOT://GOT全局偏移表,跟PLT延时绑定相关,此处未处理,在Unidbg中也没有处理此项si->plt_got = (unsigned *)(base + d->d_un.d_ptr);break;case DT_DEBUG://调试相关, Unidbg未处理,不必理会if ((dynamic_flags & PF_W) != 0) {d->d_un.d_val = (int) &_r_debug;}break;case DT_RELA://RELA表跟REL表在Unidbg中的处理方案是相同的,这两个值有哪个就用哪个,RELA只是比REL表多了一个adden常量DL_ERR("unsupported DT_RELA in \"%s\"", si->name);return false;case DT_INIT://初始化函数si->init_func = reinterpret_cast<linker_function_t>(base + d->d_un.d_ptr);DEBUG("%s constructors (DT_INIT) found at %p", si->name, si->init_func);break;case DT_FINI://析构函数si->fini_func = reinterpret_cast<linker_function_t>(base + d->d_un.d_ptr);DEBUG("%s destructors (DT_FINI) found at %p", si->name, si->fini_func);break;case DT_INIT_ARRAY://init.array 初始化函数列表,后面我们会看到这些初始化函数的调用顺序si->init_array = reinterpret_cast<linker_function_t*>(base + d->d_un.d_ptr);DEBUG("%s constructors (DT_INIT_ARRAY) found at %p", si->name, si->init_array);break;case DT_INIT_ARRAYSZ://init.array 大小si->init_array_count = ((unsigned)d->d_un.d_val) / sizeof(Elf32_Addr);break;case DT_FINI_ARRAY://析构函数列表si->fini_array = reinterpret_cast<linker_function_t*>(base + d->d_un.d_ptr);DEBUG("%s destructors (DT_FINI_ARRAY) found at %p", si->name, si->fini_array);break;case DT_FINI_ARRAYSZ://fini.array 大小si->fini_array_count = ((unsigned)d->d_un.d_val) / sizeof(Elf32_Addr);break;case DT_PREINIT_ARRAY://也是初始化函数,但是跟init.array不同,这个段大多只出现在可执行文件中,在So中我选择了忽略si->preinit_array = reinterpret_cast<linker_function_t*>(base + d->d_un.d_ptr);DEBUG("%s constructors (DT_PREINIT_ARRAY) found at %p", si->name, si->preinit_array);break;case DT_PREINIT_ARRAYSZ://preinit 列表大小si->preinit_array_count = ((unsigned)d->d_un.d_val) / sizeof(Elf32_Addr);break;case DT_TEXTREL:si->has_text_relocations = true;break;case DT_SYMBOLIC:si->has_DT_SYMBOLIC = true;break;case DT_NEEDED://当前So的依赖++needed_count;break;
#if defined DT_FLAGS// TODO: why is DT_FLAGS not defined?case DT_FLAGS:if (d->d_un.d_val & DF_TEXTREL) {si->has_text_relocations = true;}if (d->d_un.d_val & DF_SYMBOLIC) {si->has_DT_SYMBOLIC = true;}break;
#endif}}//... Sanity checks.//至此,Dynamic段的信息就解析完毕了,其中想表达的信息也被处理后放到了soinfo中,后面直接就可以拿来用了// 开辟依赖库的soinfo空间,准备处理依赖soinfo** needed = (soinfo**) alloca((1 + needed_count) * sizeof(soinfo*));soinfo** pneeded = needed;//再次遍历Dynamic段for (Elf32_Dyn* d = si->dynamic; d->d_tag != DT_NULL; ++d) {if (d->d_tag == DT_NEEDED) {//查找DT_NEEDED项const char* library_name = si->strtab + d->d_un.d_val;DEBUG("%s needs %s", si->name, library_name);//进行依赖处理,跟加载so一样的路线,还是已加载直接返回,未加载进行查找加载soinfo* lsi = find_library(library_name);if (lsi == NULL) {strlcpy(tmp_err_buf, linker_get_error_buffer(), sizeof(tmp_err_buf));DL_ERR("could not load library \"%s\" needed by \"%s\"; caused by %s",library_name, si->name, tmp_err_buf);return false;}*pneeded++ = lsi;}}*pneeded = NULL;//至此依赖库也已经加载完毕//处理重定位if (si->plt_rel != NULL) {DEBUG("[ relocating %s plt ]", si->name );if (soinfo_relocate(si, si->plt_rel, si->plt_rel_count, needed)) {return false;}}if (si->rel != NULL) {DEBUG("[ relocating %s ]", si->name );if (soinfo_relocate(si, si->rel, si->rel_count, needed)) {return false;}}//设置soinfo的LINKED标志,表示已进行链接si->flags |= FLAG_LINKED;DEBUG("[ finished linking %s ]", si->name);//...return true;
}

上面的函数虽然很长,但是它想表达的意思很简单,我们再来回顾下它干了什么事情

  • 解析Dynamic段信息
  • 处理依赖
  • 准备进行重定位

So重定位

下面我们就来分析它的soinfo_relocate函数,我们看到它调用了两次,只不过入参不同,分别是我们的重定位表和PLT重定位表

http://androidxref.com/4.4.4_r1/xref/bionic/linker/linker.cpp#848

static int soinfo_relocate(soinfo* si, Elf32_Rel* rel, unsigned count,soinfo* needed[])
{//拿到符号表和字符串表,定义一些变量Elf32_Sym* symtab = si->symtab;const char* strtab = si->strtab;Elf32_Sym* s;Elf32_Rel* start = rel;soinfo* lsi;//遍历重定位表for (size_t idx = 0; idx < count; ++idx, ++rel) {//拿到重定位类型unsigned type = ELF32_R_TYPE(rel->r_info);//拿到重定位符号unsigned sym = ELF32_R_SYM(rel->r_info);//计算需要重定位的地址Elf32_Addr reloc = static_cast<Elf32_Addr>(rel->r_offset + si->load_bias);Elf32_Addr sym_addr = 0;char* sym_name = NULL;DEBUG("Processing '%s' relocation at index %d", si->name, idx);if (type == 0) { // R_*_NONEcontinue;}if (sym != 0) {//如果sym不为0,说明重定位需要用到符号,先来找符号,拿到符号名sym_name = (char *)(strtab + symtab[sym].st_name);//下面这个函数大家有兴趣的可以看一下,就是根据符号名来从依赖so中查找所需要的符号s = soinfo_do_lookup(si, sym_name, &lsi, needed);if (s == NULL) {//如果没找到,就用本身So的符号s = &symtab[sym];if (ELF32_ST_BIND(s->st_info) != STB_WEAK) {DL_ERR("cannot locate symbol \"%s\" referenced by \"%s\"...", sym_name, si->name);return -1;}switch (type) {//下面是如果符号不为外部符号,就只能为以下几种类型
#if defined(ANDROID_ARM_LINKER)case R_ARM_JUMP_SLOT:case R_ARM_GLOB_DAT:case R_ARM_ABS32:case R_ARM_RELATIVE:    /* Don't care. */
#endif /* ANDROID_*_LINKER *//* sym_addr was initialized to be zero above or relocationcode below does not care about value of sym_addr.No need to do anything.  */break;#if defined(ANDROID_ARM_LINKER)case R_ARM_COPY:/* Fall through.  Can't really copy if weak symbol isnot found in run-time.  */
#endif /* ANDROID_ARM_LINKER */default:DL_ERR("unknown weak reloc type %d @ %p (%d)",type, rel, (int) (rel - start));return -1;}} else {//如果我们找到了外部符号,取到外部符号的地址sym_addr = static_cast<Elf32_Addr>(s->st_value + lsi->load_bias);}count_relocation(kRelocSymbol);} else {//如果sym为0,就说明当前重定位用不到符号s = NULL;}//下面根据重定位类型来处理重定位switch(type){
#if defined(ANDROID_ARM_LINKER)case R_ARM_JUMP_SLOT:count_relocation(kRelocAbsolute);MARK(rel->r_offset);TRACE_TYPE(RELO, "RELO JMP_SLOT %08x <- %08x %s", reloc, sym_addr, sym_name);//直接将需要重定位的地方,写入获取到的符号地址*reinterpret_cast<Elf32_Addr*>(reloc) = sym_addr;break;case R_ARM_GLOB_DAT:count_relocation(kRelocAbsolute);MARK(rel->r_offset);TRACE_TYPE(RELO, "RELO GLOB_DAT %08x <- %08x %s", reloc, sym_addr, sym_name);//直接将需要重定位的地方,写入获取到的符号地址,与R_ARM_JUMP_SLOT相同*reinterpret_cast<Elf32_Addr*>(reloc) = sym_addr;break;case R_ARM_ABS32:count_relocation(kRelocAbsolute);MARK(rel->r_offset);TRACE_TYPE(RELO, "RELO ABS %08x <- %08x %s", reloc, sym_addr, sym_name);//先读出需要重定位地方的数据,将其和符号地址相加,写入需要重定位的地方*reinterpret_cast<Elf32_Addr*>(reloc) += sym_addr;break;case R_ARM_REL32:count_relocation(kRelocRelative);MARK(rel->r_offset);TRACE_TYPE(RELO, "RELO REL32 %08x <- %08x - %08x %s",reloc, sym_addr, rel->r_offset, sym_name);//先读出需要重定位地方的数据,将其和符号地址相加,再与重定位的地址相减,重定位的写入需要重定位的地方。此处Unidbg并未处理,也可忽略,应该是用不到的*reinterpret_cast<Elf32_Addr*>(reloc) += sym_addr - rel->r_offset;break;
#endif /* ANDROID_*_LINKER */#if defined(ANDROID_ARM_LINKER)case R_ARM_RELATIVE:
#endif /* ANDROID_*_LINKER */count_relocation(kRelocRelative);MARK(rel->r_offset);if (sym) {DL_ERR("odd RELATIVE form...");return -1;}TRACE_TYPE(RELO, "RELO RELATIVE %08x <- +%08x", reloc, si->base);//先读出需要重定位地方的数据,将其和So的基址相加,写入需要重定位的地方*reinterpret_cast<Elf32_Addr*>(reloc) += si->base;break;#ifdef ANDROID_ARM_LINKERcase R_ARM_COPY://.. 进行了一些错误处理break;
#endif /* ANDROID_ARM_LINKER */default:DL_ERR("unknown reloc type %d @ %p (%d)",type, rel, (int) (rel - start));return -1;}}return 0;
}

上面这个函数就是在处理重定位相关的信息了,我们看到从Dynamic段中拿到的跟重定位相关的表,会经过这个函数来处理,将So本身的地址引用进行重定位,使其可以正常运行。其实在32位So中,需要处理的重定位类型并不是很多,就4种类型需要处理,而且还有两种处理方式相同

现在So就重定位完成了,现在So已经就可以跑起来了,下面我们就来看看从Dynamic段中拿到的各种初始化函数是怎么处理的,还记得吧

我们回到do_dlopen函数

http://androidxref.com/4.4.4_r1/xref/bionic/linker/linker.cpp#823

soinfo* do_dlopen(const char* name, int flags) {if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL)) != 0) {DL_ERR("invalid flags to dlopen: %x", flags);return NULL;}set_soinfo_pool_protection(PROT_READ | PROT_WRITE);soinfo* si = find_library(name);if (si != NULL) {si->CallConstructors();}set_soinfo_pool_protection(PROT_READ);return si;
}

此时我们的find_library函数已经处理完了,So已经被装载且链接过了,最后一步它调用了soinfo的CallConstructors函数,我们来看看这个函数处理了什么

http://androidxref.com/4.4.4_r1/xref/bionic/linker/linker.cpp#1192


void soinfo::CallConstructors() {if (constructors_called) {return;}constructors_called = true;if ((flags & FLAG_EXE) == 0 && preinit_array != NULL) {// The GNU dynamic linker silently ignores these, but we warn the developer.PRINT("\"%s\": ignoring %d-entry DT_PREINIT_ARRAY in shared library!",name, preinit_array_count);}//如果Dynamic段不为空,先处理依赖库的初始化if (dynamic != NULL) {for (Elf32_Dyn* d = dynamic; d->d_tag != DT_NULL; ++d) {if (d->d_tag == DT_NEEDED) {const char* library_name = strtab + d->d_un.d_val;TRACE("\"%s\": calling constructors in DT_NEEDED \"%s\"", name, library_name);find_loaded_library(library_name)->CallConstructors();}}}TRACE("\"%s\": calling constructors", name);//我们来看下面一句英文注释,非常重要。他说如果DT_INIT和DT_INIT_ARRAY都存在,DT_INIT应该在DT_INIT_ARRAY之前被调用// DT_INIT should be called before DT_INIT_ARRAY if both are present.//下面就是在调用两者,CallArray只是在循环调用CallFunction,我们看一下CallFunctionCallFunction("DT_INIT", init_func);CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);
}

http://androidxref.com/4.4.4_r1/xref/bionic/linker/linker.cpp#1172

void soinfo::CallFunction(const char* function_name UNUSED, linker_function_t function) {if (function == NULL || reinterpret_cast<uintptr_t>(function) == static_cast<uintptr_t>(-1)) {return;}TRACE("[ Calling %s @ %p for '%s' ]", function_name, function, name);//在这里被调用了,其他没啥好说的function();TRACE("[ Done calling %s @ %p for '%s' ]", function_name, function, name);// The function may have called dlopen(3) or dlclose(3), so we need to ensure our data structures// are still writable. This happens with our debug malloc (see http://b/7941716).set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
}

至此,Linker就分析结束了

总结

我们在最后说一个Unidbg细节的bug,但是现在已经被修复了,就是作为一个扩展吧。我们来看下面一段Unidbg加载So的代码

if (elfFile.file_type == ElfFile.FT_DYN) { // not executableint init = dynamicStructure.getInit();if (init != 0) {initFunctionList.add(new LinuxInitFunction(load_base, soName, init));//new LinuxInitFunction(load_base, soName, init).call(emulator);}int initArraySize = dynamicStructure.getInitArraySize();int count = initArraySize / emulator.getPointerSize();if (count > 0) {Pointer pointer = UnidbgPointer.pointer(emulator, load_base + dynamicStructure.getInitArrayOffset());if (pointer == null) {throw new IllegalStateException("DT_INIT_ARRAY is null");}for (int i = 0; i < count; i++) {Pointer func = pointer.getPointer((long) i * emulator.getPointerSize());if (func != null) {initFunctionList.add(new AbsoluteInitFunction(load_base, soName, ((UnidbgPointer) func).peer));}}}
}

如果我们细心的阅读Linker的源码,就会发现Unidbg这里处理的是不恰当的。在本文的最后,我们看到了初始化函数的调用,是DT_INIT函数先被执行,后面再处理DT_INIT_ARRAY,而Unidbg这里就是将他们都添加到一个List,再一起调用。这样就会产生一个问题,在某些加壳的So中,它的DT_INIT_ARRAY是在DT_INIT函数执行之后,才会有值的(进行修复),所以按照Unidbg这个写法就无法执行INIT_ARRAY或部分INIT_ARRAY无法执行。处理方法也很简单,注释在上面了,只需要让DT_INIT先执行就可以了。

那么本篇文章对Linker的讲解就到这里了,如果您觉得有用,可以加个VX一起学习呀:roy5ue

Android Linker详解(二)相关推荐

  1. Android Linker详解

    Android Linker详解 本文目的 Linker入口 So的装载 总结 本文目的 Unidbg在对So进行模拟执行的时候,需要先将So文件加载到内存,配置So的进程映像,然后使用CPU模拟器( ...

  2. android动画详解二 属性动画原理

    property动画是一个强大的框架,它几乎能使你动画任何东西.你可以定义一个动画来改变对象的任何属性,不论其是否被绘制于屏幕之上.一个属性动画在一定时间内多次改变一个属性(对象的一个字段)的值.要动 ...

  3. android菜单详解二:选项菜单

    创建一个选项菜单 选项菜单里应该包含基本的activity动作和必须的导航条目 (例如,一个打开程序设置的菜单项). 选项菜单的菜单项有两种不同的选择方法,一是菜单项按钮,二是通过 Action Ba ...

  4. Android Fragment详解(二):Fragment创建及其生命周期

    Fragments的生命周期 每一个fragments 都有自己的一套生命周期回调方法和处理自己的用户输入事件. 对应生命周期可参考下图: 创建片元(Creating a Fragment) To c ...

  5. android Fragments详解

    android Fragments详解一:概述 android Fragments详解二:创建Fragment 转载于:https://my.oschina.net/liangzhenghui/blo ...

  6. android子视图无菜单,Android 菜单详解

    Android中菜单分为三种,选项菜单(OptionMenu),上下文菜单(ContextMenu),子菜单(SubMenu) 选项菜单 可以通过两种办法增加选项菜单,一是在menu.xml中添加,该 ...

  7. 安卓 linux init.rc,[原创]Android init.rc文件解析过程详解(二)

    Android init.rc文件解析过程详解(二) 3.parse_new_section代码如下: void parse_new_section(struct parse_state *state ...

  8. Android init.rc文件解析过程详解(二)

    Android init.rc文件解析过程详解(二) 3.parse_new_section代码如下: void parse_new_section(struct parse_state *state ...

  9. Android Gradle 自定义Task详解二:进阶

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78523958 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...

最新文章

  1. log 框架 之间的关系
  2. c语言hellowwo所占字节数,哪个懂C语言?帮忙做~个题,跪求
  3. 【Python】5个方便好用的Python自动化脚本
  4. python文件的读取与写入_python中文件的读取与写入以及os模块
  5. 【Java】区分BigDecimal的toString()和toPlainString()
  6. On Error Resume Next是什么意思
  7. 境内外赌博网站被捣毁,程序员被抓!!
  8. popup弹出html页面,在页面加载时打开所有弹出窗口popup.html
  9. Vue模板,数据和指令
  10. jenkins 2.121.1 部署项目
  11. 数字通信(知识点)复习
  12. 利用 CSS 实现文字二次加粗和多重边框效果
  13. 飞凌OK6410A 多媒体视频编解码 player-qt4 QT视频播放器
  14. Win 10系统截图的7种方式【简单实用】
  15. 中国有嘻哈:网易云、虾米音乐歌词爬虫项目分享
  16. 成功解决ValueError: Duplicate plugins for name projector
  17. Office2007中简繁体转换功能按钮消失解决
  18. 索尼PS VR2体验:硬件素质不错,高质量游戏是关键
  19. 小米手机android评价,性能平均领先39.1% 双核小米手机评测
  20. iOS-CocoaPods

热门文章

  1. MTU MSS 详解
  2. FW:nbsp;男人必看必看的十部经典电影
  3. 公司萌萌哒言行重大事故经典案例
  4. 批量提取文件名到txt文档的方法
  5. (附源码)计算机毕业设计ssm共享自习室管理系统
  6. 利用计算机解决古代数学问题鸡兔同笼,古代数学-鸡兔同笼:7种解法,你发现了几种呢?...
  7. 程序员写好技术文章的几点小技巧
  8. 怎么用vuex-along插件实现数据持久化
  9. 心理学的166个现象---之三
  10. GPU 状态检测 Burn