<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Android Linker詳解

    VSole2021-11-21 16:08:59

    簡介

    接上篇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(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(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(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(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(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(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(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(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(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(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(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(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;}
    

    上面這個函數就是在處理重定位相關的信息了,我們看到從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,我們看一下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);}
    

    至此,Linker就分析結束了。

    總結

    我們在最后說一個Unidbg細節的bug,但是現在已經被修復了,就是作為一個擴展吧。我們來看下面一段Unidbg加載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));            }        }    }}
    

    如果我們細心的閱讀Linker的源碼,就會發現Unidbg這里處理的是不恰當的。在本文的最后,我們看到了初始化函數的調用,是DT_INIT函數先被執行,后面再處理DT_INIT_ARRAY,而Unidbg這里就是將他們都添加到一個List,再一起調用。

    這樣就會產生一個問題,在某些加殼的So中,它的DT_INIT_ARRAY是在DT_INIT函數執行之后,才會有值的(進行修復),所以按照Unidbg這個寫法就無法執行INIT_ARRAY或部分INIT_ARRAY無法執行。處理方法也很簡單,注釋在上面了,只需要讓DT_INIT先執行就可以了。

    char函數重定位
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    VMPWN的入門系列-1
    2023-07-27 09:45:00
    今天的文章有點,圖片比較多,請耐心閱讀5.1 實驗一 VMPWN15.1.1 題目簡介這是一道基礎的VM相關題目,VMPWN的入門級別題目。
    漏洞的成因來自于Glibc在對重定向函數進行延遲綁定時,由于參數表被篡改導致的控制流篡改,本篇,筆者會盡可能通過例題和實際現象來闡釋 延遲綁定的底層實現 和 ret2dlresolve。
    進程注入的探索
    2022-07-29 08:22:06
    0x01 簡單描述進程注入就是給一個正在運行的程序開辟一塊內存,把shellcode放入內存,然后用一個線程去執行shellcode。
    因此參考了《黑客免殺攻防》的代碼對DLL型殼編寫的結構進行了一次歸納整理,并附上相應代碼解析。
    前言最近一段時間在研究Android加殼和脫殼技術,其中涉及到了一些hook技術,于是將自己學習的一些hook技術進行了一下梳理,以便后面回顧和大家學習。主要是進行文本替換、宏展開、刪除注釋這類簡單工作。所以動態鏈接是將鏈接過程推遲到了運行時才進行。
    知道了殺軟在ring0的監測原理,我們該如何進行繞過呢?
    如果加載器分配的基址和該程序文件記錄默認的ImageBase相同,則不需要修正,定位表對于該dll也是沒有效用的。PE結構PE文件大致可以分為兩部分,即數據管理結構及數據部分。PE頭是固定不變的,位于DOS頭部e_ifanew字段指出位置。節表主要是存儲了何種借的屬性、文件位置、內存位置等。
    另外需要說明一下,原文只是一個系列(https://sploitfun.wordpress.com/2015/06/26/linux-x86-exploit-development-tutorial-series/)的一篇文章:
    稍后將會基于linux 2.4.x內核環境,演示這種感染技術。不過,由于內核模塊是elf格式的,所以閱讀后續內容之前,先要熟悉elf格式,并且要重點理解其符號表,之后才好明白,向正常內核模塊注入感染代碼的原理。----[ 2.1 - The .symtab section.symtab節區(符號表)的內容,是一個供鏈接器使用的Elf32_Sym結構數組,Elf32_Sym結構定義在內核的/usr/include/elf.h頭文件:typedef struct
    lib文件在windows下有兩種形式出現,第一種就是普通的靜態庫,第二種是作為dll的導入庫。接下來我來分享一下如何在這兩種lib文件注入后門代碼,使程序編譯后生成的exe在運行時候自動。執行我們后門,并且不影響正常lib的功能。lib實際上就是一堆Obj文件打包在了一起,當然還有一些額外的信息,這個之后再說。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类