linker源碼解析

linker是Android系統動態庫so的加載器和連接器,也是Android脫殼一重要脫殼點,這裏介紹一下此部分的Android源碼,並介紹幾個脫殼點,及分析過程當中產生的反調試手段,學習Linker的加載和啓動原理,又須要介紹so的加載和啓動。html

系統 :Android4.4-r1 linker源碼的位置 : Android/bionic/linkerjava

0x00 加載與啓動so

一、Java層中聲明加載某個so文件以共享,則可在Java層聲明代碼:android

static{
    System.loadLibrary("libhello.so")
}
複製代碼

其對應的執行流程以下: 一、 定位到文件Dalvik/vm/native/java_lang_Runtime.cpp 二、 調用Dalvik_java_lang_Runtime_nativeLoad -> Dalvik/vm/Native.cpp:dvmLoadNativeCode 具體代碼以下,我略過一些錯誤判斷代碼,這段代碼以/*** 說明 ***/的形式省略git

bool dvmLoadNativeCode(const char* pathName, Object* classLoader,
        char** detail)
{
    SharedLib* pEntry;
    void* handle;
    bool verbose;

    /*********根據path查找so文件**********/
    pEntry = findSharedLibEntry(pathName);

    /*********加載指定so文件,並延遲加載*******/
    handle = dlopen(pathName, RTLD_LAZY);
    dvmChangeStatus(self, oldStatus);

    /* create a new entry */
    SharedLib* pNewEntry;
    /*.......*/

    /* try to add it to the list */
    /***當執行addShareLibEntry()方法的時候,若是還有線程B同時在加載該so,***/
    /***而且B線程先執行到了這裏,那麼就說明該so的信息已經添加過了,咱們就不須要再執行添加pNewEntry的操做***/
    SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);
         if (pNewEntry != pActualEntry) {
        ALOGI("WOW: we lost a race to add a shared lib (%s CL=%p)",
            pathName, classLoader);
        freeSharedLibEntry(pNewEntry);
        return checkOnLoadResult(pActualEntry);
    } else {
        if (verbose)
            ALOGD("Added shared lib %s %p", pathName, classLoader);

        bool result = false;
        void* vonLoad;
        int version;
       /***定位JNI_OnLoad()方法***/
        vonLoad = dlsym(handle, "JNI_OnLoad");//
        /***若是找不到則延遲加載,說明是用javah風格的代碼**/
        if (vonLoad == NULL) {
            ALOGD("No JNI_OnLoad found in %s %p, skipping init", pathName, classLoader);
            result = true;
        } else {
            /*
             * Call JNI_OnLoad.  We have to override the current class
             * loader, which will always be "null" since the stuff at the
             * top of the stack is around Runtime.loadLibrary().  (See
             * the comments in the JNI FindClass function.)
             */
            /***這裏省略了重寫相應類加載器的代碼,如上面雞腸文所示功能,不重要**/
            /***gDvm是一個全局變量,功能後面再補***/
            if (gDvm.verboseJni) {
                ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);
            }
            /***執行JNI_OnLoad***/
            version = (*func)(gDvmJni.jniVm, NULL);
            
           /***省略一大段代碼。。。***/
        dvmUnlockMutex(&pNewEntry->onLoadLock);
        return result;
    }
}
複製代碼

從上面的代碼咱們可看出 一、Android系統加載so文件時使用了dlopen函數; 二、定位JNI_OnLoad()方法,則dlsym(handle, "JNI_OnLoad"); 三、執行JNI_OnLoad()方法:(*func)(gDvmJni.jniVm, NULL) 咱們查找加載so文件的函數dlopen在bionic/linker/dlfcn.c中,而此函數有主要調用了do_dlopen函數,這裏對dlopen函數不詳細贅述,主要解析一下do_dlopen函數,而此關鍵函數正是在Linker中。接下來詳細分析Linker源碼。github

0x01 Linker源碼總覽

這裏寫圖片描述

0x02 do_dlopen

先貼上代碼:bash

soinfo* si = find_library(name);//**完成so的加載到內存的工做
  if (si != NULL) {
    si->CallConstructors();//**完成so及自己的構造函數的調用。完成so文件的加載
  }
複製代碼

嗯,註釋簡單明瞭,接下來解析find_library(name)與si->CallConstructors()。app

0x03 續0 find_library

做用:完成so的加載到內存的工做,成爲是否加載過該so的重要依據ionic

static soinfo* find_library(const char* name) {//成爲是否加載過該so的重要依據
  soinfo* si = find_library_internal(name);//尋找相應的so信息
  if (si != NULL) {
    si->ref_count++;
  }
  return si;
複製代碼

嗯,仍是很清晰,裏面的調用的方法先放下先,咱們現分析以下方法。ide

0x04 si->CallConstructors()

完成so及自己的構造函數的調用。完成so文件的加載函數

/***省略一大堆代碼,下面是主要函數****/
 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();
      }
       // DT_INIT should be called before DT_INIT_ARRAY if both are present.
  TRACE("\"%s\": calling constructors", name);
  CallFunction("DT_INIT", init_func);//調用本身的一系列構造函數後返回,這裏是so文件加殼的脫殼點
  CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);//dex文件的脫殼點
複製代碼

這主要是完成so文件的加載,而後遍歷全部動態節,再根據標籤d_tag ==DT_NEEDED調用依賴庫的構造函數,再調用本身的一系列構造函數,以及init_arry函數,其中後面兩個函數分別是so文件和dex文件的脫殼點,這樣就結束了so文件的加載,但分析遠遠沒有結束,咱們須要回過頭來解析find_library方法。

0x05 find_library 之find_library_internal()

操做:尋找相應的so信息

static soinfo* find_library_internal(const char* name) {
  if (name == NULL) {
    return somain;
  }
 
  soinfo* si = find_loaded_library(name);
  if (si != NULL) {
    if (si->flags & FLAG_LINKED) {
      return si;
    }
    DL_ERR("OOPS: recursive link to \"%s\"", si->name);
    return NULL;
  }
 
  TRACE("[ '%s' has not been loaded yet. Locating...]", name);
  si = load_library(name);//真正加載so文件的函數
  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;
  }
複製代碼

註釋很明白,整理一下基本流程: 一、find_loaded_library():尋找相應的so信息 二、load_library():真正加載so文件的函數 三、soinfo_link_image():處理動態節dynamic section,初始化動態節dynamic section的屬性

0x06 find_library_internal() 之 find_loaded_library()

//尋找相應的so信息 
 static soinfo *find_loaded_library(const char *name)
{
    soinfo *si;
    const char *bname;
 
    // TODO: don't use basename only for determining libraries // http://code.google.com/p/android/issues/detail?id=6670 bname = strrchr(name, '/');//查找一個字符"/"在另外一個字符串name中末次出現的位置並返回這個位置的地址 bname = bname ? bname + 1 : name; for (si = solist; si != NULL; si = si->next) {//判斷是否有加載這個so if (!strcmp(bname, si->name)) { return si; } } return NULL; } 複製代碼

0x07 find_library_internal() 之load_library()

操做:真正加載so文件的函數

static soinfo* load_library(const char* name) {
    // Open the file.
    int fd = open_library(name);
    if (fd == -1) {
        DL_ERR("library \"%s\" not found", name);
        return NULL;
    }
 
    // Read the ELF header and load the segments.
    ElfReader elf_reader(name, fd);
    if (!elf_reader.Load()) {//讀取elf的操做,源碼中看出只讀取了Program 段
        return NULL;
    }
     const char* bname = strrchr(name, '/');
     //在so文件加載完之後,接着就會調用soinfo_alloc函數爲so分配soinfo
    soinfo* si = soinfo_alloc(bname ? bname + 1 : name);
    if (si == NULL) {
        return NULL;
    }//利用裝載結果初始化soinfo對象
    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()方法:

bool ElfReader::Load() {
  return ReadElfHeader() &&
         VerifyElfHeader() &&
         ReadProgramHeader() &&
         ReserveAddressSpace() &&
         LoadSegments() &&
         FindPhdr();
}
複製代碼

能夠看出讀取elf的操做,源碼中看出只讀取了Program 段,這也是不少加固進行抹頭操做的緣由:IDA只經過加載section Header段,來讀取so文件,而實際上源碼只讀取了Program段,若是隻是將section Header段抹頭了,IDA便沒法正常解析so文件,而Android系統卻能夠正常解析so文件,這也是一種反調試手段。 再後來就進行一系列對so文件的初始化操做:

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;
複製代碼

其中si結構體以下(可略):

struct soinfo {
 public:
  char name[SOINFO_NAME_LEN];
  const Elf32_Phdr* phdr;
  size_t phnum;
  Elf32_Addr entry;
  Elf32_Addr base;
  unsigned size;

  uint32_t unused1;  // DO NOT USE, maintained for compatibility.

  Elf32_Dyn* dynamic;

  uint32_t unused2; // DO NOT USE, maintained for compatibility
  uint32_t unused3; // DO NOT USE, maintained for compatibility

  soinfo* next;
  unsigned flags;

  const char* strtab;
  Elf32_Sym* symtab;

  size_t nbucket;
  size_t nchain;
  unsigned* bucket;
  unsigned* chain;

  unsigned* plt_got;

  Elf32_Rel* plt_rel;
  size_t plt_rel_count;

  Elf32_Rel* rel;
  size_t rel_count;

  linker_function_t* preinit_array;
  size_t preinit_array_count;

  linker_function_t* init_array;
  size_t init_array_count;
  linker_function_t* fini_array;
  size_t fini_array_count;

  linker_function_t init_func;
  linker_function_t fini_func;

#if defined(ANDROID_ARM_LINKER)
  // ARM EABI section used for stack unwinding.
  unsigned* ARM_exidx;
  size_t ARM_exidx_count;
#elif defined(ANDROID_MIPS_LINKER)
  unsigned mips_symtabno;
  unsigned mips_local_gotno;
  unsigned mips_gotsym;
#endif

  size_t ref_count;
  link_map_t link_map;

  bool constructors_called;

  // When you read a virtual address from the ELF file, add this
  // value to get the corresponding address in the process' address space. Elf32_Addr load_bias; bool has_text_relocations; bool has_DT_SYMBOLIC; void CallConstructors(); void CallDestructors(); void CallPreInitConstructors(); private: void CallArray(const char* array_name, linker_function_t* functions, size_t count, bool reverse);//dex文件脫殼點 void CallFunction(const char* function_name, linker_function_t function);//so文件脫殼點 }; 複製代碼

0x08 find_library_internal() 之soinfo_link_image(si)

在si = load_library(name)得到了so文件的info以後,就開始進行一系列操做: 一、定位動態節; 二、解析動態節; 三、加載動態節 四、重定位

一、定位動態節:

phdr_table_get_dynamic_section(const Elf32_Phdr* phdr_table,
                               int               phdr_count,
                               Elf32_Addr        load_bias,
                               Elf32_Dyn**       dynamic,
                               size_t*           dynamic_count,
                               Elf32_Word*       dynamic_flags)
{
    const Elf32_Phdr* phdr = phdr_table;
    const Elf32_Phdr* phdr_limit = phdr + phdr_count;
 
    for (phdr = phdr_table; phdr < phdr_limit; phdr++) {
        if (phdr->p_type != PT_DYNAMIC) {//遍歷phdr尋找DYNAMIC段,存放了字符串,方法等偏移地址
            continue;
        }
 
        *dynamic = reinterpret_cast<Elf32_Dyn*>(load_bias + phdr->p_vaddr);
        if (dynamic_count) {
            *dynamic_count = (unsigned)(phdr->p_memsz / 8);
        }
        if (dynamic_flags) {
            *dynamic_flags = phdr->p_flags;
        }
        return;
    }
    *dynamic = NULL;
    if (dynamic_count) {
        *dynamic_count = 0;
    }
}
複製代碼

值得注意的是,這裏源碼加載動態節的時候只加載了第一個動態節,後面的動態節都沒有加載,所以咱們能夠本身自定義多個programm段中的動態節區,而後在section header中也改變相應的數據,這樣IDA解析的信息是有所誤差的,由於Android系統實際上只讀取了第一個動態節。 二、解析動態節。 解析代碼以下:

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){/初始化動態節dynamic section的屬性
        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:
            si->plt_rel = (Elf32_Rel*) (base + d->d_un.d_ptr);
            break;
        case DT_PLTRELSZ:
            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:
            /* Save this in case we decide to do lazy binding. We don't yet. */ si->plt_got = (unsigned *)(base + d->d_un.d_ptr); break; case DT_DEBUG: // Set the DT_DEBUG entry to the address of _r_debug for GDB // if the dynamic table is writable if ((dynamic_flags & PF_W) != 0) { d->d_un.d_val = (int) &_r_debug; } break; case DT_RELA: 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: 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: 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: si->fini_array_count = ((unsigned)d->d_un.d_val) / sizeof(Elf32_Addr); break; case DT_PREINIT_ARRAY: 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: 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: ++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 #if defined(ANDROID_MIPS_LINKER) case DT_STRSZ: case DT_SYMENT: case DT_RELENT: break; case DT_MIPS_RLD_MAP: // Set the DT_MIPS_RLD_MAP entry to the address of _r_debug for GDB. { r_debug** dp = (r_debug**) d->d_un.d_ptr; *dp = &_r_debug; } break; case DT_MIPS_RLD_VERSION: case DT_MIPS_FLAGS: case DT_MIPS_BASE_ADDRESS: case DT_MIPS_UNREFEXTNO: break; case DT_MIPS_SYMTABNO: si->mips_symtabno = d->d_un.d_val; break; case DT_MIPS_LOCAL_GOTNO: si->mips_local_gotno = d->d_un.d_val; break; case DT_MIPS_GOTSYM: si->mips_gotsym = d->d_un.d_val; break; default: DEBUG("Unused DT entry: type 0x%08x arg 0x%08x", d->d_tag, d->d_un.d_val); break; #endif } } 複製代碼

三、加載依賴庫

//********加載依賴庫 (NEEDED)       Shared library: [liblog.so]
    for (Elf32_Dyn* d = si->dynamic; d->d_tag != DT_NULL; ++d) {
        if (d->d_tag == DT_NEEDED) {
            const char* library_name = si->strtab + d->d_un.d_val;
            DEBUG("%s needs %s", si->name, library_name);
            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;
        }
    }
複製代碼

四、重定位操做

if (si->has_text_relocations) {//重定位操做
        /* Unprotect the segments, i.e. make them writable, to allow
         * text relocations to work properly. We will later call
         * phdr_table_protect_segments() after all of them are applied
         * and all constructors are run.
         */
        DL_WARN("%s has text relocations. This is wasting memory and is "
                "a security risk. Please fix.", si->name);
        if (phdr_table_unprotect_segments(si->phdr, si->phnum, si->load_bias) < 0) {
            DL_ERR("can't unprotect loadable segments for \"%s\": %s",
                   si->name, strerror(errno));
            return false;
        }
    }

    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;
        }
    }
複製代碼

其中具體的意義參見如下博客 blog.csdn.net/feibabeibei…

0xFF 收尾

一、dlopen執行完畢後 二、調用 vonLoad = dlsym(handle, "JNI_OnLoad")定位JNI_Onload 三、再執行version = (*func)(gDvmJni.jniVm, NULL);執行JNI_Onload 期間能夠有三個斷點: .init->.init_array->JNI_Onload->java_com_XX. 反調試思路: 一、section Header的抹頭操做 二、自定義多個動態節

這裏留個坑,就是未進行對全局變量gDvm的解析,師傅們多多指教哇~ 參考連接:

blog.csdn.net/feibabeibei… xianzhi.aliyun.com/forum/read/…blog.csdn.net/maspchen/ar…

相關文章
相關標籤/搜索