Android安全–linker加载so流程,在.init下断点

系统加载so,在完成装载、映射和重定向以后,就首先执行.init.init_array段的代码.

首先来看看加载so的流程:http://androidxref.com/4.4_r1/xref/bionic/linker/linker.cpp

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是否已经加载,如果没有加载,对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);
  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;
  }

782  return si;
783}

find_loaded_library:判断so文件是不是已经被加载了,如果是直接返回句柄,否则就是加载

load_library:加载so,包括打开so文件,读取so文件的一部分信息,给so文件分配一个soinfo类型的结构体

soinfo_link_image:加载so,对结构体变量进行赋值

void soinfo::CallConstructors() {
  if (constructors_called) {
    return;
  }

  // We set constructors_called before actually calling the constructors, otherwise it doesn’t
  // protect against recursive constructor calls. One simple example of constructor recursion
  // is the libc debug malloc, which is implemented in libc_malloc_debug_leak.so:
  // 1. The program depends on libc, so libc’s constructor is called here.
  // 2. The libc constructor calls dlopen() to load libc_malloc_debug_leak.so.
  // 3. dlopen() calls the constructors on the newly created
  //    soinfo for libc_malloc_debug_leak.so.
  // 4. The debug .so depends on libc, so CallConstructors is
  //    called again with the libc soinfo. If it doesn’t trigger the early-
  //    out above, the libc constructor will be called again (recursively!).
  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);
  }

  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 should be called before DT_INIT_ARRAY if both are present.
  CallFunction(“DT_INIT”, init_func);
  CallArray(“DT_INIT_ARRAY”, init_array, init_array_count, false);
}

这里的DT_INIT和DT_INIT_ARRAY到底是什么呢?

init_func和init_array都是结构体soinfo的成员变量。在soinfo_link_image加载so的时候进行赋值.

#define DT_INIT  12	/* Address of initialization function */
#define DT_INIT_ARRAY	25	/* Address of initialization function array */

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

进入CallFunction:

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

结论:系统加载so,在完成装载、映射和重定向以后,就首先执行.init.init_array段的代码,之后如果存在JNI_OnLoad就调用该函数.我们要对一个so进行分析,需要先看看有没有.init_array section.init section,so加壳一般会在初始化函数进行脱壳操作。

 

如何在.init.init_array段添加我们的函数

[1] 共享构造函数,在函数声明时加上"__attribute__((constructor))"属性
    void __attribute__((constructor)) init_function(void);
    对应有共享虚构函数,在程序exit()或者dlclose()返回前执行
    void __attribute__((destructor)) fini_function(void);

[2]c++ 静态构造函数

.init.init_array下断点

1.首先把/system/bin/的linker文件拿出来用IDA打开,找到我们刚刚所说的调用 function() 的位置。

可以直接搜索字符串Calling %s @ %p for ‘%s’

image

然后来到字符串所在的位置:

image

图中BLX  R4就是我们要找的调用function() 的位置。加下文件偏移:0x272C

2.附加调试程序

找到linker模块加载的基地址:0xB6F9F000

然后加上刚刚的文件偏移:0xB6F9F000+0x272C=0xB6FA172C

定位到该地址,下好断点:

image

jdb -connect com.sun.jdi.SocketAttach:port=8700,hostname=localhost

继续F9:

image

现在看到在加载libexec.so的时候,断在了我们的断点上,F7进去就是初始化段的地方了。

image

本文链接:http://www.alonemonkey.com/linker-load-so.html

2条评论

  1. vendanner

    不太懂 “首先把/system/bin/的linker文件拿出来用IDA打开,找到我们刚刚所说的调用 function() 的位置。

    可以直接搜索字符串Calling %s @ %p for ‘%s’”.怎么知道function位置可以通过搜字符串”Calling %s @ %p for ‘%s”来得到?

    1. AloneMonkey

      和源码对应的

Comments are closed.