                              ==Ph4nt0m Security Team==

                           Issue 0x02, Phile #0x0A of 0x0A

    |=---------------------------------------------------------------------------=|
    |=----------------------=[  pe/elf 文件加壳时的处理  ]=----------------------=|
    |=---------------------------------------------------------------------------=|
    |=---------------------------------------------------------------------------=|
    |=--------------------------=[      By dummy     ]=--------------------------=|
    |=-----------------------=[  <dummy_at_ph4nt0m.org>  ]=----------------------=|
    |=---------------------------------------------------------------------------=|

    前言：

        最初的壳是在感染型的病毒技术上发展出来的，加壳目的一般是压缩或加密。本文主要
    就x86平台下win32 pe和linux elf 加壳程序的实现做简单介绍和总结，以自己以前写相关
    程序做线索叙述，其中程序源码是开源的，有兴趣的朋友可以继续进行改进。

        ps: 其中有些地方很久没碰，可能有地方描述有误，还请见谅:)

    正文：

        -------------------------------------------------------
        slm        x86 win32 r3 pe packer
        mimisys    x86 win32 r0 pe packer
        elfp       x86 linux r3 elf packer
        -------------------------------------------------------

    一、一个壳的组成

        一个完整的壳程序主要由 2 个部分组成 packer 和 loader。它们具体的作用分别是：

        (1) packer

        负责将待加壳程序压缩和加密处理、把loader写到待加壳程序上。以slm的pakcer
        为例具体操作包括，pe有效性判断、优化可压缩数据、压缩和加密、添加loader、存放
        加壳参数和待加壳程序原数据（oep等等）、改写入口点等等。

        (2) loader

        主要工作是解压或解密被加壳的程序，以slm的loader为例具体的操作包括：获取自
        身位置、获取加壳参数、进行解压或解密、填充导入表、重定位、tls 初始化等等。

    二、slm (x86 win32 r3 pe packer)

    资料:
        http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx

    工具：
        lordpe    pe 文件格式查看编辑工具
        dumpbin    vc 自带coff文件格式查看工具
        ollydbg    r3 调试工具

    源码结构：
        ./slm/cm 公共头文件和模块
        ./slm/pk    packer 实现
        ./slm/sc    loader 实现

        在做这个时候对 pe 也是刚刚了解，所以 slm 很多地方现在看来有些问题:)。在第一
    节已经简单描述 slm 的工作流程，下面主要就我当初做的时候遇到的问题做一些描述：

        (1) 资源的处理

        slm 的资源处理做的比较烦琐，当初目的是为了把可压缩资源数据归并到一起，进
        行一次压缩，不可压缩单独存放。下面简单介绍一下资源的目录数据格式，详细还是看
        看微软的文档和相关源码:)

            从IMAGE_NT_HEADERS.IMAGE_DATA_DIRECTORY[IMAGE_DIRECTORY_ENTRY_RESOURCE]
        取出资源数据的地址res_rva，经过转换后第一个结构体是IMAGE_RESOURCE_DIRECTORY

            IMAGE_RESOURCE_DIRECTORY:

                NumberOfIdEntries       目录下 id 名称入口项个数
                NumberOfNamedEntries    目录下 name 名称入口项个数

            紧跟着IMAGE_RESOURCE_DIRECTORY后面是IMAGE_RESOURCE_DIRECTORY_ENTRY结构
        数组，这个数组的元素个数是 NumberOfIdEntries + NumberOfNamedEntries。

            IMAGE_RESOURCE_DIRECTORY_ENTRY:

                Id                  目录id，只有NameIsString非真才有效
                NameIsString        目录名称是否是字符串，如果为真NameOffset有效
                NameOffset          目录名称串的偏移, 偏移是相对与res_rva*的。
                DataIsDirectory     如果为真 OffsetToData 有效，否则OffsetToDirectory
                                    有效
                OffsetToData        指向资源数据，偏移类型rva
                OffsetToDirectory   指向子目录，偏移类型rva

            如果目录入口名称是字符串，通过NameOffset获取PIMAGE_RESOURCE_DIR_STRING_U
        的结构指针，目录名是unicode格式，并且不是以零结尾的字符串。如果目录名不是字符
        串而是id, 那么其值在winnt.h 定义。常见id有RT_ICON、RT_VERSION等等。

            结构大致简单描述完了，有点要注意OffsetToDirectory、OffsetToData修改时要
        进行 DWORD 对齐，否则会出现奇怪的现象。

        (2) 导入表处理

            从IMAGE_NT_HEADERS.IMAGE_DATA_DIRECTORY[IMAGE_DIRECTORY_ENTRY_IMPORT]
        取出导入表的地址imp_rva，经过转换后第一个结构体是IMAGE_IMPORT_DESCRIPTOR。

        IMAGE_IMPORT_DESCRIPTOR:

                Name               指向导入 dll 的名称，偏移类型 rva
                FirstThunk         指向 IMAGE_THUNK_DATA 结构体，偏移类型 rva
                OriginalFirstThunk 指向FirstThunk 的副本, 可以为空。偏移类型 rva

            导入由IMAGE_IMPORT_DESCRIPTOR结构数组组成，数组长度由一个Name域为空的结
        构表明。

            FirstThunk和OriginalFirstThunk都是指向以IMAGE_THUNK_DATA数组组成的数据
        结构，系统的加载器在进行导入表填充时，会把FirstThunk指向的结构修改掉。

        (3) TLS 处理

            这里说的tls是所谓的静态tls(在pe文件结构上进行实现)，关于什么是tls可以看看
        《windows 核心编程》线程那章。

            1、tls 是怎样的

            比如要在vc中声明一个tls变量需要这样__declspec(thread) int x = 0;在链接
        时这个变量会被链接器放入.tls的节中。这个节从外边看和其他的节没有什么不同，唯
        一的区别在IMAGE_DATA_DIRECTORY[IMAGE_DIRECTORY_ENTRY_TLS]指向的一个结构会对
        此节进行描述，这个结构是IMAGE_TLS_DIRECTORY。

            IMAGE_TLS_DIRECTORY:

                StartAddressOfRawData   tls数据开始地址类型va
                EndAddressOfRawData     tls数据结束地址类型va
                AddressOfIndex;         tls slot的地址，默认tls slot为0

                AddressOfCallBacks      指向一个PIMAGE_TLS_CALLBACK的数组，这个数组
                                        以0结尾，每个PIMAGE_TLS_CALLBACK都是va类型

                SizeOfZeroFill          数据区需要进行清 0 数据的大小
                Characteristics

            2、系统加载器怎样处理exe的tls

                系统加载器在完成重定位和输入表填充后，就开始处理tls。如果存在tls_dir，
        求出tls数据的大小EndAddressOfRawData - StartAddressOfRawData +
        SizeOfZeroFill, 按照大小分配一块内存，地址存入(PDWORD)fs:[0x2c] +
        tls_slot, 接着拷贝StartAddressOfRawData -> EndAddressOfRawData之间的数
        据到新分配的内存中，然后使用SizeOfZeroFill 清零剩下的数据，然后进行循环回
        调AddressOfCallBacks中的函数，PIMAGE_TLS_CALLBACK函数和DllMain原型很像，
        只是没有返回值。

            3、系统加载器怎样处理dll的tls

                首先明确dll是可以使用tls的，唯一的不同是AddressOfCallBacks调用方式会
        有些区别。如果目标dll是被静听链接到其他文件上，在进程创建完成时即被加载，
        那么他的tls callback会触发，而LoadLibrary方式加载不会触发。

        (4) rva & raw 转换

            pe 文件中许多结构域的指针类型是rva, rva是pe文件由系统加载后，方法相关数
        据的相对偏移量。而我们进行加壳处理时，是直接map的文件数据访问都是使用文件指
        针，这就需要rva进行转换。(每次做pe相关工具时，我都会习惯写一个这样的函数，现在
        不下10中版本，竟然没有一个可以保证是正确的 - -）

            下面这个是最新的rva2raw版本，不保证正确性。

    三、mimisys (x86 win32 r0 pe packer)

    资料：
        Windows Research Kernel
            wrk/base/ntos/mm/sysload.c:MmLoadSystemImage
    工具:
        syser     内核调试器，你也可以选择其他的r0调试器
        vmware    如果不想频繁重启，需要一个虚拟机

        文件格式的一些处理参考slm, 这里主要就r0 pe和r3 pe区别做介绍：

        (1) 节和页

            r0空间的内存常常很紧张，就导致sys section属性有几个特殊地方

            1、可换出和禁止换出

            在内存不足时，系统内存管理器，会枚举已加载的section object, 如果存在
        pageout属性，那么系统内存管理器就会换出这个节对应的页（这个节经过系统页对
        齐后换出内存）节对齐原则VirtualAddress向上、VirtualSize向下。禁止换出如
        名字所名这个节将永驻内存。

            2、节对齐小于一页

            大多数sys的节对齐指数都是小于一页，系统加载器在处理这类文件时相当很
        简单。加载后文件和磁盘上的文件布局基本一致。当节对齐小于一页时，
        SizeOfRawData必须大于等于VirtualSize，即不支持未初始化节。mimisys通过增
        加SizeOfImage在文件加载后分配一个未初始化的缓冲区，保证解压过程。

        (2) checksum校验

            一句话: 只有正确的checksum sys文件才允许被加载。

        (3) win2k相关问题

            win2k的系统加载器和其他nt系统有几处不同，r3和r0都会有一些区别，比如r3 pe
        的必须要有导入表，否则拒绝加载，r0 pe必须要有重定位信息，否则也会拒绝加载。这
        种情况需要构造一个空的重定位目录即可。

            mimisys采取的是合并节，导致加壳后只剩下两个节，第一个节是loader, 存放
        loader和各种加壳参数，第二个节是原程序优化压缩后的数据（移动重定位，移动资源等
        等）。两个节的属性都是不允许换出的。

    四、elfp (x86 linux r3 elf packer)

    资料：
        Tool Interface Standard (TIS) Executable and Linking Format
            http://www.x86.org/ftp/manuals/tools/elf.pdf
        毛德操 《漫谈内核兼容》8,9 ELF映像的装入
            http://linux.insigma.com.cn/jszl.asp?docid=132762762
            http://linux.insigma.com.cn/jszl.asp?docid=133617926
        linux 内核源码
            linux/fs/binfmt_elf.c:load_elf_binary

    工具:
        objdump    进行elf文件格式的结构查看
                   http://www.gnu.org/software/binutils/binutils.html

        ald        汇编级调试器，gdb无法调试没有调试信息文件的。
                   http://ald.sourceforge.net/

        elfp是在magiclinux完成的linux elf文件压缩壳。

        elf的格式是linux下主要的可执行文件格式，它也是在coff上基础上设计的，所以它和
    pe文件的格式很相似，下面的叙述过程中会和 pe 文件以对比形式进行描述。

        elf文件的第一个数据结构是以Elf32_Ehdr开始

        typedef struct
        {
          unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */
          Elf32_Half    e_type;                 /* Object file type */
          Elf32_Half    e_machine;              /* Architecture */
          Elf32_Word    e_version;              /* Object file version */
          Elf32_Addr    e_entry;                /* Entry point virtual address */
          Elf32_Off     e_phoff;                /* Program header table file offset */
          Elf32_Off     e_shoff;                /* Section header table file offset */
          Elf32_Word    e_flags;                /* Processor-specific flags */
          Elf32_Half    e_ehsize;               /* ELF header size in bytes */
          Elf32_Half    e_phentsize;            /* Program header table entry size */
          Elf32_Half    e_phnum;                /* Program header table entry count */
          Elf32_Half    e_shentsize;            /* Section header table entry size */
          Elf32_Half    e_shnum;                /* Section header table entry count */
          Elf32_Half    e_shstrndx;             /* Section header string table index */
        } Elf32_Ehdr;

        e_ident        在 elf.h 中对应的 ELFMAG 宏，长度是四个字节
        e_entry        入口点映像偏移(映像偏移即 pe 中所说的 rva)
        e_phoff        Elf32_Phdr 数组的文件偏移
        e_shoff        Elf32_Shdr 数组的文件偏移
        e_ehsize       Elf32_Ehdr 结构的大小
        e_phentsize    Elf32_Phdr 结构大小
        e_phnum        Elf32_Phdr 数组成员个数
        e_shentsize    Elf32_Shdr 结构大小
        e_shnum        Elf32_Shdr 数组成员个数

        在Elf32_Ehdr之后即Elf32_Phdr数组，Elf32_Phdr的地址通过Elf32_Ehdr.e_ehsize来
    确定。Elf32_Ehdr数组（或叫段表)，你可以把phdr看做pe的节表。

        typedef struct
        {
          Elf32_Word    p_type;            /* Segment type */
          Elf32_Off     p_offset;        /* Segment file offset */
          Elf32_Addr    p_vaddr;        /* Segment virtual address */
          Elf32_Addr    p_paddr;        /* Segment physical address */
          Elf32_Word    p_filesz;        /* Segment size in file */
          Elf32_Word    p_memsz;        /* Segment size in memory */
          Elf32_Word    p_flags;        /* Segment flags */
          Elf32_Word    p_align;        /* Segment alignment */
        } Elf32_Phdr;

        p_type    描述这个段的加载行为属性
        p_offset  段数据在文件中偏移，类似pe节中的PointerToRawData
        p_vaddr   段数据加载后在映像中偏移, 类似pe节中的VirtualAddress
        p_filesz  段数据在文件中大小，类型pe节中的SizeOfRawData
        p_memsz   段数据加载后在映像中大小，类型pe节中的VirtualSize
        p_flags   描述这个段的内存属性，类似pe节中的节属性
        p_align   段对齐粒度

        p_type主要的类型有

            PT_LOAD      这个段需要装载到内存中
            PT_PHDR      这个段存放的是Elf32_Phdr数组
            PT_INTERP    这个段存放一个解释器名，请求系统加载器把映像装载需求转给这
    	             个解释器，关于elf的解释器问题，可以理解为windows下ntdll装载
    		     pe文件，elf文件的解释器主要负责重定位、导入表填充等操作

        p_flags 主要类型有

            PF_X         这个段可执行
            PF_W         这个段可写
            PF_R         这个段可读

        在Elf32_Ehdr(段表)之后便是Elf32_Shdr数组(节表)，你可能到这里很奇怪了，怎么这
    个叫节表？如果你熟悉pe应该知道节表对pe文件的重要性，但这个可不是pe中的那个节表，你
    应该把它看做nt header中data_dir[]结构，加载器或调试器等工具会通过节名确定节具体
    用途，比如存储调试信息、版本信息、字符串表等等、elfp在加壳过程会丢弃节表。

        下面简单讲讲elfp的loader处理过程（加壳过程很简单），在一个elf文件被加载后，它的
    入口点在执行之前，堆栈中会由系统加载器push的一些参数。

        //  堆栈结构：
        //  +-------------------+
        //  |   return address  |        返回地址
        //  +-------------------+
        //  |   argc            |        参数个数
        //  +-------------------+
        //  |   argv[?], NULL   |        参数表，以 NULL 结尾
        //  +-------------------+
        //  |   envp[?], NULL   |        环境表，以 NULL 结尾
        //  +-------------------+
        //  |   auxv[?]         |        中文不知道叫什么它，这个主要是给解释器使用,
        //  +-------------------+        存放这个elf的相关信息如果被加壳程序需要解
                                         释器，你需要重写正确填写这个参数，让解释器可
    				     以正确的找到相关数据的地址。

        elfp壳loader的执行流程大致如下:

            申请内存-->把每个段解压到指定的地址上-->获取被加壳程序原始信息-->检查原
        始段表、重写 auxv-->加载解释器-->调用解释器

        关于 elf 的解释器，可以参考资料链接上的文字，那里比我描述的完整。

    五、附录

    [1] 本文代码
        ./pstzine_0A_01.zip

    -EOF-