zoukankan      html  css  js  c++  java
  • 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' ]
    ",
                  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' ]
    ", 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' ]
    ", pid,
                 (unsigned)si->init_func, si->name);
           si->init_func();
           TRACE("[ %5d Done calling init_func for '%s' ]
    ", pid, si->name);
      }
      if (si->init_array) {
        TRACE("[ %5d Calling init_array @ 0x%08x [%d] for '%s' ]
    ", 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' ]
    ", 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
    ",
                      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
    ",
                      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

  • 相关阅读:
    如何让position:fixed在IE6中工作 不抖动
    【javascript基础】之【宿主环境】
    用函数式编程技术编写优美的 JavaScript
    IE6下使用滤镜后链接不能点击的BUG
    什么是内存泄漏
    Best Practices for Speeding Up Your Web Site
    Object.prototype.toString.call()
    【前端优化】IE浏览器下同一网页多图片显示的瓶颈与优化
    get username
    open file and format readin
  • 原文地址:https://www.cnblogs.com/goodhacker/p/3985260.html
Copyright © 2011-2022 走看看