导入表注入初步介绍

初识PE文件格式

img

DOS

img

Dos header

比较重要的有e_magic和e_lfanew两个属性。

其中e_magic属性用来判断这个文件是否为PE文件

e_lfnaw属性用来确定NT header相对于整个文件头的偏移量

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

Dos Stub(存根)

如果在 MS-DOS 中执行文件,则会调用存根程序。它通常显示合适的消息;但是,任何有效的 MS-DOS 应用程序都可以是存根程序。

NT Header

img
整个NT文件头由三个部分组成

typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

Signature

这个参数是一个标志。在一个有效的PE文件里, Signature字段被设置为 00004550h。

FileHeader

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;			//标志着运行平台
    WORD    NumberOfSections;	//节的数量
    DWORD   TimeDateStamp;		//This represents the date and time the image was created by the linker.
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;//可选头的大小
    WORD    Characteristics;	//说明了文件的可写、可读属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

OptionalHeader

下面列出了其中几个比较重要的属性

typedef struct _IMAGE_OPTIONAL_HEADER64 {
    DWORD       AddressOfEntryPoint;//指出文件被执行时的入口地址,注意这里是一个RVA地址
    ULONGLONG   ImageBase;			//程序的首选装载地址
    DWORD       SectionAlignment;	//内存中的区块的对齐大小
    DWORD       FileAlignment;		//文件中的区块的对齐大小
    DWORD       SizeOfImage;		//映像装入内存后的总尺寸
    WORD        DllCharacteristics; // DllMain()函数何时被调用
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
    //数据目录表。这个字段是一个指针,它由16个相同的 IMAGE_DATA_DIRECTORY结构组成。
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

IMAGE_DATA_DIRECTORY

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;	//虚拟地址(RVA)
    DWORD   Size;			//目录大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

其中每个目录有特殊的含义,比较重要的有:

  • 第0个 导出表
  • 第1个 导入表
  • 第5个 基址重定位
  • 第12个 IAT表

Section Table

img

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME]; //名字填充
    union {
            DWORD   PhysicalAddress;	   //文件地址
            DWORD   VirtualSize;		   //加载到内存中的节的总大小
    } Misc;
    DWORD   VirtualAddress;					
    //加载到内存中的部分的第一个字节的地址,相对于映像基。对于对象文件,这是在应用重定位之前的第一个字节的地址。
    DWORD   SizeOfRawData;					//在磁盘上的初始化的数据大小
    DWORD   PointerToRawData;				//COFF 文件中第一页的文件指针。
    DWORD   Characteristics;				//说明了节区的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

Import Table

img

想要找到导入表,我们首先找到Optional Header中的DataDirectory。

其次找到对应目录中的VirtualAddress。

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;           
        DWORD   OriginalFirstThunk;        //指向INT的RVA 
    } DUMMYUNIONNAME;
    DWORD   Name;						  	//指向导入映像文件的名称
    DWORD   FirstThunk;                     // 指向IAT的RVA
} IMAGE_IMPORT_DESCRIPTOR;

img

typedef struct _IMAGE_THUNK_DATA64 {
    union {
        ULONGLONG ForwarderString;  // PBYTE 
        ULONGLONG Function;         // PDWORD
        ULONGLONG Ordinal;
        ULONGLONG AddressOfData;    // PIMAGE_IMPORT_BY_NAME
    } u1;
} IMAGE_THUNK_DATA64;
typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64;

想要找到一个导入函数,我们首先通过OriginalFirstThunk找到我们存放Image_Thunk_Data,随后进一步找到我们需要的导入函数。

INT和IAT之间的区别

注意上图PE文件加载前,IAT表和INT表的完全相同的,所以此时IAT表也可以判断函数导出序号,或指向函数名字结构体。

而在加载后,差别就是IAT表发生变化,系统会先根据结构体变量Name加载对应的dll(拉伸),读取dll的导出表,对应原程序的INT表,匹配dll导出函数的地址,返回其地址,贴在对应的IAT表上,挨个修正地址(也就是GetProcAddress的功能)。

IMAGE_IMPORT_BY_NAME

typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;
    CHAR   Name[1];	//function name string
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

开始我们注入导入表的旅程吧

首先我们需要明确我们要做什么:

我们要将给定的DLL添加到一个应用程序中,并且当这个应用程序启动时,它会自动感染当前目录下的所有应用程序并将DLL文件分别添加到所有应用程序的导入表中。

根据我们上面提到的基础知识,我们首先需要指导一个导入表加载到IAT中的过程。
img

所以如果我们想要添加dll到导入表内,我们需要修改IAT和INT表、添加一个IMAGE_IMPORT_DESCRIPTOR和IMAGE_IMPORT_BY_NAME。

想要添加东西我们就必须要思考,是在原来的节区中添加还是新添加一个节区来存放我们需要的内容。最终我们选择新加一个节区,这里考虑到原来的节区可能存放的东西比较乱,不如我们新建一个节区比较好。

我们回顾一下我们整个程序需要修改的地方:

  1. 我们新添加一个节区头,肯定要修改NT头中Optional Header中的SizeOfImage

  2. 我们需要在原来的sectionHeader后面添加上我们新的文件头,然后把原来导入表所在的节区的节区头完全拷入到我们新建的节区头中。

  3. 添加新的节区头之后,我们肯定要把指向原来导入表的值修改为指向我们新加节头的地址。这里主要修改可选头中的DataDirectory[1]中的虚拟地址

  4. 修改完节点头之后,我们需要在整个文件后面添加新的节区。

    在这个新的节区中,我们主要添加7个部分:

    1.一个映像导入描述符。需要修改这个描述符的INT和IAT地址。

    2.一个空白的描述符,来说明描述符结束。

    3.一个INT。修改其AddressOfData属性,使其指向新添加得到IMAGE_IMPORT_BY_NAME

    4.一个空白的INT。表明INT结束。

    5.一个IAT,使其指向新添加的IMAGE_IMPORT_BY_NAME。

    6.一个空白的IAT,表明IAT结束。

    7.一个IMAGE_IMPORT_BY_NAME,修改其Name,Hint属性

有几个需要值得注意的地方:

  1. 新添加节的属性一定要包含IMAGE_SCN_MEM_WRITE属性。
  2. 注意区分32位和64位程序。
posted @ 2023-06-22 19:44  一点点高手  阅读(147)  评论(0)    收藏  举报