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查看入口点

[email protected]:~$ 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

时间: 2024-12-22 11:52:51

android so壳入口浅析的相关文章

React Native Android 源码框架浅析(主流程及 Java 与 JS 双边通信)

[工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果.私信联系我] 1 背景 有了前面<React Native Android 从学车到补胎和成功发车经历>和<React Native Android Gradle 编译流程浅析>两篇文章的学习我们 React Native 已经能够基本接入处理一些事情了,那接下来的事情就是渐渐理解 RN 框架的一些东西,以便裁剪和对 RN 有个更深入的认识,所以本篇总结了我这段时间阅读源码

Android开发之入口Activity

原文:Android开发之入口Activity Android开发之入口Activity Adnroid App是如何确定入口Activity的? 难道就因为class的类名叫MainActivity,布局文件叫activity_main.xml? 如果这样认为,就大错特错了. 之所以能够确定入口Activity,是因为在应用的清单文件中有所配置,系统会根据应用的清单文件(AndroidManifest.xml)来确立. 如何确立,标志是什么? 我们来看一下清单文件,便一目了然: 对了,系统能够

Android加壳原理分析

0x00 阅读本文前,建议读者首先阅读Android加壳原理,参考文章Android中的Apk的加固(加壳)原理解析和实现.如果没有看过这篇文章,本文理解起来比较困难. 0x01 下面我们来分析脱壳代码为什么要这样写,核心脱壳代码在ProxyApplication类里面,首先执行成员方法attachBaseContext,然后执行成员方法onCreate. 那么attachBaseContext是什么时候被执行的呢,为什么先于onCreate执行呢?那就需要看Android的源码了,我们选用的是

Android手势源码浅析-----手势绘制(GestureOverlayView)

Android手势源码浅析-----手势绘制(GestureOverlayView)

Android UI 绘制过程浅析(五)自定义View

前言 这已经是Android UI 绘制过程浅析系列文章的第五篇了,不出意外的话也是最后一篇.再次声明一下,这一系列文章,是我在拜读了csdn大牛郭霖的博客文章<带你一步步深入了解View>后进行的实践. 前面依次了解了inflate的过程,以及绘制View的三个步骤:measure, layout, draw.这一次来亲身实践一下,通过自定义View来加深对这几个过程的理解. 自定义View的分类 根据实现方式,自定义View可以分为以下3种类型. 自绘控件.View的绘制代码(onDraw

分享Android NDK技术详解及应用(Android加壳图片处理性能优化)

1.课程研发环境案例源代码编译和运行环境以JDK1.7和android-sdk-23以及android-ndk-10e版本为基准, ,以下环境都适用于项目.开发工具:android studio 1.5正式版, QT 5.0,SourceInsight 3.5;其他工具:使用到了IDEA PRO工具以及www.androidxref.com网站查看分析源码.2.内容简介本课程主要讲解NDK技术的基本使用方法,如基本常用的JNI函数.Android系统中能使用的本地库的使用方法和注意事项以及GCC

Android aidl Binder框架浅析

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38461079 ,本文出自[张鸿洋的博客] 1.概述 Binder能干什么?Binder可以提供系统中任何程序都可以访问的全局服务.这个功能当然是任何系统都应该提供的,下面我们简单看一下Android的Binder的框架 Android Binder框架分为服务器接口.Binder驱动.以及客户端接口:简单想一下,需要提供一个全局服务,那么全局服务那端即是服务器接口,任何程序即客

android apk壳

壳对于有过pc端加解密经验的同学来说并不陌生,android世界中的壳也是相同的存在(为什么要有壳这里就不描述咯).看下图(exe = dex):    概念清楚罗,我们就说下:壳最本质的功能就是实现加载器.你看加壳后,系统是先执行壳代码的.但我们想要的是执行原dex,可是系统此时是不会自动来执行的需要壳去将原dex加载到系统中.ok,壳就说到这里,看android apk壳: Android APK加壳技术方案[1] Android APK加壳技术方案[2] APK加壳[1]初步方案实现详解

Android View工作机制浅析(ppt)

Android View工作机制浅析(ppt)