android so殼入口淺析


本文轉自http://www.9hao.info/pages/2014/08/android-soke-ru-kou-q

前言

  開年來開始接觸一些加固樣本,基本都對了so進行了處理,拖入ida一看,要么沒有 JNI_OnLoad ,要么 JNI_OnLoad 匯編代碼羞澀難懂,讓人無法下手。 JNI_OnLoad 是真正入口么?

先看看幾個文檔

1 摘自屬性服務一節(《深入理解Android卷1》)

 利用gcc的constructor屬性,這個屬性指明了一個__libc_prenit函數(這個函數內部就將完成共享內存到本地進程的映射工作)。用法:當bionic libc庫被加載時,將自動調用__libc_prenit函數。這樣在bionic libc動態庫被裝載時,系統屬性緩沖區地址就被確定了,后續的API調用就能找對位置了。

/* We flag the __libc_preinit function as a constructor to ensure * that its address is listed in libc.so's .init_array section. * This ensures that the function is called by the dynamic linker * as soon as the shared library is loaded. */ 

//constructor屬性指示加載器加載該庫之后,首先調用__libc_prenit函數。這一點和windows上的動態庫的DllMain函數類似
void __attribute__((constructor)) __libc_prenit(void);

從英文說明里面提到到.init_array section,我們可以搜索一下這一節的說明

2 .init_array section

.init_array contains pointers to blocks of code that need to be executed when an application is being initialized (before main() is called). It is used for a number of things, but the primary use is in C++ for running static constructors; a secondary use that is sometimes used is to initialize IO systems in the C library.
If you are not using C++ you may (depending on your C library) be able to live without it entirely; but you’d need to hack your startup code to deal with this.
.init_array probably ends up in ram because its marked read/write — that happens because in a dynamic linking environment the dynamic linker has to fix up all the pointers it contains before it can be used. In a static environment you might be able to get away with forcing it into a read-only section.
來源: <http://blog.sina.com.cn/s/blog_a9303fd901019kvq.html>

3 摘自dlopen小結(《程序員的自我修養》)

動態連接器在加載模塊時,會執行".init"段的代碼,用以完成模塊的初始化工作,dlopen的加載過程基本跟動態連接器一致,在完成裝載、映射和重定向以后,就會執行".init"段的代碼然后返回

看完這個3段資料,我們可以知道在系統加載so,在完成裝載、映射和重定向以后,就首先執行.init.init_array段的代碼.

探本溯源,在源碼中追蹤

我們先從System.loadLibrary ->Runtime.loadLibrary

  public void loadLibrary(String libName) {
        loadLibrary(libName, VMStack.getCallingClassLoader());
   }
    /*
     * Loads and links a library without security checks.
     */
   void loadLibrary(String libraryName, ClassLoader loader) {
        代碼略...
        String error = nativeLoad(filename, loader);       
        代碼略...
 }

-> nativeLoad

static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,
  JValue* pResult)
{
   代碼略...
   StringObject* fileNameObj = (StringObject*) args[0];
   success = dvmLoadNativeCode(fileName, classLoader, &reason);
   代碼略...
}

來源: <http://androidxref.com/4.1.2/xref/dalvik/vm/native/java_lang_Runtime.cpp#72>

->dvmLoadNativeCode

bool dvmLoadNativeCode(const char* pathName, Object* classLoader,
        char** detail)
{
         代碼略...
        handle = dlopen(pathName, RTLD_LAZY);

         代碼略...
        vonLoad = dlsym(handle, "JNI_OnLoad");
        if (vonLoad == NULL) {
            ALOGD("No JNI_OnLoad found in %s %p, skipping init",
                pathName, classLoader);
        } 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.)
             */
            OnLoadFunc func = (OnLoadFunc)vonLoad;
            Object* prevOverride = self->classLoaderOverride;

            self->classLoaderOverride = classLoader;
            oldStatus = dvmChangeStatus(self, THREAD_NATIVE);
            if (gDvm.verboseJni) {
                ALOGI("[Calling JNI_OnLoad for \"%s\"]", pathName);
            }
            version = (*func)(gDvmJni.jniVm, NULL);
            dvmChangeStatus(self, oldStatus);
           self->classLoaderOverride = prevOverride;
    }
    代碼略...
}

來源: <http://androidxref.com/4.1.2/xref/dalvik/vm/Native.cpp#318>

通過dvmLoadNativeCode函數我們知道系統用dlopen加載so完成后,會查看有沒有JNI_OnLoad函數,有的話就調用.

我們再到dlopen函數探個究竟:

void *dlopen(const char *filename, int flag)
{
    soinfo *ret;

    pthread_mutex_lock(&dl_lock);
    /*find_library 會判斷so是否已經加載,如果沒有加載,對so進行加載,完成一些初始化工作,有興趣的讀者可自行分析 */
    ret = find_library(filename);
    if (unlikely(ret == NULL)) {
        set_dlerror(DL_ERR_CANNOT_LOAD_LIBRARY);
    } else {
        call_constructors_recursive(ret);
        ret->refcount++;
    }
    pthread_mutex_unlock(&dl_lock);
    return ret;
}

->call_constructors_recursive

void call_constructors_recursive(soinfo *si)
{
    if (si->constructors_called)
        return;

    // Set this 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 call_constructors_recursive() with the newly created
    //    soinfo for libc_malloc_debug_leak.so.
    // 4. The debug so depends on libc, so call_constructors_recursive() is
    //    called again with the libc soinfo. If it doesn't trigger the early-
    //    out above, the libc constructor will be called again (recursively!).
    si->constructors_called = 1;

    if (si->flags & FLAG_EXE) {
        TRACE("[ %5d Calling preinit_array @ 0x%08x [%d] for '%s' ]\n",
              pid, (unsigned)si->preinit_array, si->preinit_array_count,
              si->name);
        call_array(si->preinit_array, si->preinit_array_count, 0);
        TRACE("[ %5d Done calling preinit_array for '%s' ]\n", pid, si->name);
    } else {
        if (si->preinit_array) {
            DL_ERR("%5d Shared library '%s' has a preinit_array table @ 0x%08x."
                  " This is INVALID.", pid, si->name,
                   (unsigned)si->preinit_array);
       }
   }

    代碼略...

    if (si->init_func) {
        TRACE("[ %5d Calling init_func @ 0x%08x for '%s' ]\n", pid,
             (unsigned)si->init_func, si->name);
       si->init_func();
       TRACE("[ %5d Done calling init_func for '%s' ]\n", pid, si->name);
  }
  if (si->init_array) {
    TRACE("[ %5d Calling init_array @ 0x%08x [%d] for '%s' ]\n", pid,
            (unsigned)si->init_array, si->init_array_count, si->name);
    //遍歷函數數組並執行
    call_array(si->init_array, si->init_array_count, 0);
       TRACE("[ %5d Done calling init_array for '%s' ]\n", pid, si->name);
  //ps:看到這么多TRACE這么多調試信息,我們把調試開關打開,是不是能拿到諸多信息?
 }
}
來源: <http://androidxref.com/4.1.2/xref/bionic/linker/linker.c#1519>

通過可以函數我們知道si->init_funcsi->init_array存在的時候,會執行指向的函數
(不知道大家注意到么si->flags & FLAG_EXE時,還有si->preinit_array? 以后會不會有這方面的東西?)
再找下 si->init_funcsi->init_array 的賦值

 case DT_INIT:
           si->init_func = (void (*)(void))(si->base + *d);
           DEBUG("%5d %s constructors (init func) found at %p\n",
                  pid, si->name, si->init_func);

 case DT_INIT_ARRAY:
            si->init_array = (unsigned *)(si->base + *d);
      DEBUG("%5d %s constructors (init_array) found at %p\n",
                  pid, si->name, si->init_array);
           break;

DEBUG里面說明了constructors (init func)和constructors (init_array)。
  我們再看看一份文檔Android Dynamic Linker Design Notes

DT_INIT
      Points to the address of an initialization function
      that must be called when the file is loaded.
DT_INIT_ARRAY
      Points to an array of function addresses that must be
      called, in-order, to perform initialization. Some of
      the entries in the array can be 0 or -1, and should
      be ignored.

      Note: this is generally stored in a .init_array section

  通過層層分析,我們很清楚知道了系統加載so,在完成裝載、映射和重定向以后,就首先執行.init.init_array段的代碼.

前面有一篇文章我已經對so加殼進行簡單說明

把源碼的dlopen復制出來修改,在把自己so加載起來的時候 ,把自己內存里面某部分地址解密后,用自己的dlopen打開返回一個soinfo結構體 然后把當前soinfo結構體替換原來的soinfo結構體 

小結

  系統加載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下斷點

init_array  用ida可以看到,  可以對里面的函數數組下斷點
init ida有時沒識別出來,可用readelf查看入口點

xxx@xx:~$ readelf -a '/home/xxx/桌面/libsecexe.so'

 0x00000010 (SYMBOLIC)                   0x0
 0x0000000c (INIT)                       0x11401
 0x00000019 (INIT_ARRAY)                 0x28ca4
 0x0000001b (INIT_ARRAYSZ)               8 (bytes)
 0x0000001a (FINI_ARRAY)                 0x28cac
 0x0000001c (FINI_ARRAYSZ)               12 (bytes)
 0x00000004 (HASH)                       0xf4

我們看到 INIT 入口為    0x11401
(ps:有時你在0x11401是數據,你需要make code,由於對齊關系,要從0x11401+1開始)
樣本:梆梆 愛加密

參考: 
[1] 《深入理解Android卷1》
[2] 《程序員的自我修養-鏈接、裝載與庫》
[3] android linker 淺析
[4] Android Dynamic Linker Design Notes


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM