Android Linker Detailed Explanation (2)

Android Linker Detailed Explanation (2)

Purpose of this article

Continuing from the previous Linker source code explanation (1), this article continues to analyze the linking process of Linker. In order to better understand the principle of Unidbg, we need to know a lot of details. Although a simulated binary execution framework has many drawbacks, it is also a good idea for future binary analysis.

In the previous article, we explained the loading of the Linker, and loaded the So file into the memory according to the instructions of the PT_LOAD section. Then in this article, we will analyze what is done after loading?

So's link

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

In the previous article, we entered the elf_reader.Load() function, read the loading source code of Linker, and when the loading is finished, assign a value to the soinfo structure (the header information of the So file/loading result), and insert it into the linked list, and then we return to Continue to the upper function

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

We can see from the above function that when the load_library function is called, the soinfo_link_image function is called. This function is also a main entry point of our analysis today – link

The following function is very long, I will remove the irrelevant code for everyone, let’s take a look at what this function is doing through the comments first

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

Although the above function is very long, the meaning it wants to express is very simple. Let's review what it does

  • Parse Dynamic segment information
  • Handle dependencies
  • ready for relocation

So relocation

Next, let's analyze its soinfo_relocate function. We see that it is called twice, but the input parameters are different, which are our relocation table and PLT relocation table.

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_*_NONE
            continue;
        }
        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 relocation
                       code 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 is
                       not 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_LINKER
        case 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;
}

The above function is processing information related to relocation. We see that the table related to relocation obtained from the Dynamic section will be processed by this function, and the address reference of So itself will be relocated so that it can be normal operation. In fact, in 32-bit So, there are not many relocation types that need to be processed. There are only 4 types that need to be processed, and there are two processing methods that are the same.

Now the relocation of So is completed, and now So can run, let's take a look at how to deal with the various initialization functions obtained from the Dynamic section, remember it

We go back to the do_dlopen function

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

At this point our find_library function has been processed, So has been loaded and linked, the last step it calls the CallConstructors function of soinfo, let's see what this function handles

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,我们看一下CallFunction
  CallFunction("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);
}

At this point, the Linker analysis is over

Summarize

We talked about a bug in the details of Unidbg at the end, but it has been fixed now, just as an extension. Let's take a look at the following code for Unidbg loading So

if (elfFile.file_type == ElfFile.FT_DYN) {
    
     // not executable
    int 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));
            }
        }
    }
}

If we read the source code of Linker carefully, we will find that what Unidbg handles here is inappropriate. At the end of this article, we saw the call of the initialization function. The DT_INIT function is executed first, and then the DT_INIT_ARRAY is processed. Here, Unidbg adds them all to a List and calls them together. This will cause a problem. In some packed So, its DT_INIT_ARRAY will have a value (repair) after the DT_INIT function is executed, so according to the Unidbg writing method, INIT_ARRAY or part of INIT_ARRAY cannot be executed. . The processing method is also very simple, the comment is above, just let DT_INIT execute first.

So that’s the end of this article’s explanation of Linker. If you find it useful, you can add a VX to learn it together: roy5ue

Guess you like

Origin blog.csdn.net/u010559109/article/details/120885650