32位PE文件结构

MZ标记 : 0x5A4D   WORD

PE标记:0x00004550  DWORD

1、DOC头:

WORD e_magic * "MZ标记" 用于判断是否为可执行文件.
DWORD e_lfanew; * PE头相对于文件的偏移,用于定位PE文件

 

2、标准PE头:

WORD Machine; * 程序运行的CPU型号:0x0 任何处理器/0x14C 386及后续处理器
WORD NumberOfSections; * 文件中存在的节的总数,如果要新增节或者合并节 就要修改这个值.
DWORD TimeDateStamp; * 时间戳:文件的创建时间(和操作系统的创建时间无关),编译器填写的.
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader; * 可选PE头的大小,32位PE文件默认E0h 64位PE文件默认为F0h 大小可以自定义.
WORD Characteristics; * 每个位有不同的含义,可执行文件值为10F 即0 1 2 3 8位置1

3、可选PE头:

WORD Magic; * 说明文件类型:10B 32位下的PE文件 20B 64位下的PE文件
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;* 所有代码节的和,必须是FileAlignment的整数倍 编译器填的 没用
DWORD SizeOfInitializedData;* 已初始化数据大小的和,必须是FileAlignment的整数倍 编译器填的 没用
DWORD SizeOfUninitializedData;* 未初始化数据大小的和,必须是FileAlignment的整数倍 编译器填的 没用
DWORD AddressOfEntryPoint;* 程序入口
DWORD BaseOfCode;* 代码开始的基址,编译器填的 没用
DWORD BaseOfData;* 数据开始的基址,编译器填的 没用
DWORD ImageBase;* 内存镜像基址
DWORD SectionAlignment;* 内存对齐
DWORD FileAlignment;* 文件对齐
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;* 内存中整个PE文件的映射的尺寸,可以比实际的值大,但必须是SectionAlignment的整数倍
DWORD SizeOfHeaders;* 所有头+节表按照文件对齐后的大小,否则加载会出错
DWORD CheckSum;* 校验和,一些系统文件有要求.用来判断文件是否被修改.
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;* 初始化时保留的堆栈大小
DWORD SizeOfStackCommit;* 初始化时实际提交的大小
DWORD SizeOfHeapReserve;* 初始化时保留的堆大小
DWORD SizeOfHeapCommit;* 初始化时实践提交的大小
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;* 目录项数目

 

 4、 节表:

 节的属性标志对照表:

[值:00000020h] [IMAGE_SCN_CNT_CODE // Section contains code.(包含可执行代码)]
[值:00000040h] [IMAGE_SCN_CNT_INITIALIZED_DATA // Section contains initialized data.(该块包含已初始化的数据)]
[值:00000080h] [IMAGE_SCN_CNT_UNINITIALIZED_DATA // Section contains uninitialized data.(该块包含未初始化的数据)]
[值:00000200h] [IMAGE_SCN_LNK_INFO // Section contains comments or some other type of information.]
[值:00000800h] [IMAGE_SCN_LNK_REMOVE // Section contents will not become part of image.]
[值:00001000h] [IMAGE_SCN_LNK_COMDAT // Section contents comdat.]
[值:00004000h] [IMAGE_SCN_NO_DEFER_SPEC_EXC // Reset speculative exceptions handling bits in the TLB entries for this section.]
[值:00008000h] [IMAGE_SCN_GPREL // Section content can be accessed relative to GP.]
[值:00500000h] [IMAGE_SCN_ALIGN_16BYTES // Default alignment if no others are specified.]
[值:01000000h] [IMAGE_SCN_LNK_NRELOC_OVFL // Section contains extended relocations.]
[值:02000000h] [IMAGE_SCN_MEM_DISCARDABLE // Section can be discarded.]
[值:04000000h] [IMAGE_SCN_MEM_NOT_CACHED // Section is not cachable.]
[值:08000000h] [IMAGE_SCN_MEM_NOT_PAGED // Section is not pageable.]
[值:10000000h] [IMAGE_SCN_MEM_SHARED // Section is shareable(该块为共享块).]
[值:20000000h] [IMAGE_SCN_MEM_EXECUTE // Section is executable.(该块可执行)]
[值:40000000h] [IMAGE_SCN_MEM_READ // Section is readable.(该块可读)]
[值:80000000h] [IMAGE_SCN_MEM_WRITE // Section is writeable.(该块可写)]

 

 5、RVA转FOA计算公式

1)变量所在地址减去分配的内存基址

2)减完的结果判断变量在哪个节中,大于哪个节的VirtualAddress并且小于这个节的VirtualAddress+VirtualSize

3)使用减完的结果减去所在节的VirtualAddress,得到偏移量

4)使用得到的偏移量+所在节的PointerToRawData,得到文件对应的偏移地址

 

6、FileBuffer转ImageBuffer

1)根绝SizeOfImage分配内存空间

2)初始化内存空间把内容全部改成0

3)把SizeOfHEADERS对应的内容全部拷贝到新分配的内存空间

4)循环拷贝使用每个节的virtualAddress分割每个节的大小,把FileBuffer的每个节的SizeOfRawData对应的数据拷贝到每个VirtualAddress对应的节中

 

7、节空白区添加代码(手动)

1)E8 硬编码对应汇编指令 CALL

2)E9硬编码对应汇编指令 JMP

3)X = 真正要跳转的地址 - E8这条指令的下一行地址

4)修改OEP指向添加代码开头的地址

5)修改E9跳转的地址指向原来程序的OEP

6)修改时需要注意文件对齐和内存对齐问题

MESSAGEBOX硬编码 6A 00 6A 00 6A 00 6A 00 E8 00 00 00 00 E9 00 00 00 00 

8、编程方式实现任意代码区添加代码

1)按SizeOfImage申请内存空间

2)memset初始化新申请的内存空间全部初始化为0

3)FileBuffer和ImageBuffer的SizeOfHeaders是相同的没有任何变化可以直接把FileBuffer的SizeOfHeaders使用memcpy拷贝过来

4)按照每个节的VirtualAddress为起点拷贝大小为每个节的SizeOfRawData全部拷贝结束后拉伸文件完成返回ImageBuffer

5)设置一个全局的宏类型为BYTE类型里面放E8E9的硬编码

6)E8硬编码的值为本机(DWORD)MASSAGEBOXAADDR - (DWORD)(ImageOptionalHeader32->ImageBase + (ImageSectionHeader->VirtualAddress + ImageSectionHeader->Misc.VirtualSize + 0xD))

7)E9硬编码的值为(DWORD)(ImageOptionalHeader32->ImageBase + ImageOptionalHeader32->AddressOfEntryPoint) - (DWORD)(ImageOptionalHeader32->ImageBase + (ImageSectionHeader->VirtualAddress + ImageSectionHeader->Misc.VirtualSize + 0x12))

8)使用memcpy在ImageSectionHeader->VirtualAddress + ImageSectionHeader->Misc.VirtualSize后面添加信息;

需要判断SizeOfRawData-VirtualSize的值是否可以容纳下shellCode的大小,节的数量是否大于ImageFileHeader->NumberOfSections的数量,VirtualSize的大小是否超出了SizeOfRawData的大小

9)根据最后一个ImageSectionHeader->PointerToRawData + SizeOfRawData申请一个NewBuffer的空间把ImageBuffer通过每个节的PointerToRawData + SizeOfRawData的大小还原回去

10)新打开一个文件使用fwrite把NewBuffer的信息写入到新文件完成。

 

8、增加一个节(代码实现)

1)先判断SizeOfHeaders-(dos头+(dos头和标准PE头直接的垃圾数据大小)+ PE标志(4字节NT头) + 标准PE头 + 可选PE头 + 节表的大小)剩余的空间是否够80个字节(前40个字节存放新增的节表信息后40个字节全部为0{微软要求节表最后必须有40个字符全部为0的区域})

2)如果够80个字节在原有节表最后添加一个新的40字节的节表信息,后40个字节全部置0(如果不够80字节可以把NT头向后一直到节表结束区的数据全部提升到DOS头后面覆盖掉DOS头和NT头直接的垃圾数据区,也可以扩展最后一个节在最后一个节的扩展区域内添加代码)

3)修改SizeOfImage大小,修改VirtualAddress(注意文件对齐和内存对齐问题),修改VirtualSize为新增节的大小,修改SizeOfRawData,修改PointerToRawData,修改程序入口偏移,修改NumberOfSections完成

 

1)创建NewImageBuffer 分配空间大小为 SizeOfImage + 0x1000

2)判断最后一个节的SizeOfRawData和VirtualSize哪个大,使用内存对齐后大的那个值加上VirtualAddress 计算出添加硬编码的地址

3)添加硬编码然后修改程序入口偏移

4)修改最后一个节的SizeOfRawData、VirtualSize

5)修改SizeOfImage、Characteristics(完成)

 

提升除DOS头之外的所有头信息

1、拉伸文件到内存

2、提升的位置为ImageBuffer + 0x40

3、ImageBuffer + e_lfnew开始复制数据到ImageBuffer + 0x40复制的大小为

(4 + IMAGE_SIZEOF_FILE_HEADER + SIZEOF_OPTIONAL_HEADERS + (NUMBERSECTION * IMAGE_SIZEOF_SECTION_HEADER)

4、修改e_lfnew = 0x40

5、新增一个节

 

9、数据目录

1、我们所了解的PE分为头和节,在每个节中,都包含了我们写的一些代码和数据,但还有一些非常重要
的信息是编译器替我们加到PE文件中的,这些信息可能存在在任何可以利用的地方。

2、这些信息之所以重要,是因为这些信息包含了诸如:

PE程序的图标在哪里?

用到了哪些系统提供的函数?

为其他的程序提供哪些函数?

3、编译器添加了这么多信息,那程序是如何找到这些信息的呢?

答案就是:数据目录

4、数据目录定位:

可选PE头最后一个成员,就是数据目录.一共有16个:

typedef struct _IMAGE_DATA_DIRECTORY {    
DWORD VirtualAddress;    //内存偏移
DWORD Size;    //大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;    

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16    

 

分别是:

导出表、 #define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory

导入表、 #define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory

资源表、 #define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory

异常信息表、 #define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory

安全证书表、 #define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory

重定位表、 #define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table

调试信息表、 #define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory

版权所以表、 #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data

全局指针表 #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP

TLS表、 #define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory

加载配置表、 #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory

绑定导入表、 #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers

IAT表、 #define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table

延迟导入表、 #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors

COM信息表 #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

最后一个保留未使用。

和程序运行时息息相关的表有:

导出表

导入表

重定位表

IAT表

 

10、导出表

 

1、如何定位导出表:

数据目录项的第一个结构,就是导出表.

typedef struct _IMAGE_DATA_DIRECTORY {    
DWORD VirtualAddress;    
DWORD Size;    
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;    

 

VirtualAddress 导出表的RVA

Size 导出表大小

2、导出表结构

上面的结构,只是说明导出表在哪里,有多大,并不是真正的导出表.

如何在FileBuffer中找到这个结构呢?在VirtualAddress中存储的是RVA,如果想在FileBuffer中定位

必须要先将该RVA转换成FOA.

真正的导出表结构如下:

3、AddressOfFunctions说明:

该表中元素宽度为4个字节

该表中存储所有导出函数的地址

该表中个数由NumberOfFunctions决定

该表项中的值是RVA, 加上ImageBase才是函数真正的地址

定位:

IMAGE_EXPORT_DIRECTORY->AddressOfFunctions 中存储的是该表的RVA 需要先转换成FOA


4、AddressOfNames说明:

该表中元素宽度为4个字节

该表中存储所有以名字导出函数的名字的RVA

该表项中的值是RVA, 指向函数真正的名称

 

总结:

为什么要分成3张表?

1、函数导出的个数与函数名的个数未必一样.所以要将函数地址表和函数名称表分开.

2、函数地址表是不是一定大于函数名称表?

未必,一个相同的函数地址,可能有多个不同的名字.

3、如何根据函数的名字获取一个函数的地址?

 

 

 11、重定位表

 

特别说明:

1、一般情况下,EXE都是可以按照ImageBase的地址进行加载的.因为Exe拥有自己独立的4GB 的虚拟内存空间
但DLL 不是 DLL是有EXE使用它,才加载到相关EXE的进程空间的.

2、为了提高搜索的速度,模块间地址也是要对齐的 模块地址对齐为10000H 也就是64K

 打开一个程序,观察一下全局变量的反汇编

1、也就是说,如果程序能够按照预定的ImageBase来加载的话,那么就不需要重定位表
这也是为什么exe很少有重定位表,而DLL大多都有重定位表的原因

2、一旦某个模块没有按照ImageBase进行加载,那么所有类似上面中的地址就都需要修正,否则,引用的地址就是无效的.

3、一个EXE中,需要修正的地方会很多,那我们如何来记录都有哪些地方需要修正呢?

答案就是重定位表

重定位表的定位:


数据目录项的第6个结构,就是重定位表.

typedef struct _IMAGE_DATA_DIRECTORY {    
DWORD VirtualAddress;    
DWORD Size;    
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

RVA转FOA

typedef struct _IMAGE_BASE_RELOCATION {    
DWORD VirtualAddress;    
DWORD SizeOfBlock;    
} IMAGE_BASE_RELOCATION;    
typedef IMAGE_BASE_RELOCATION ,* PIMAGE_BASE_RELOCATION;

 

 

 12 移动导出表

 

第一步:在DLL中新增一个节,并返回新增后的FOA  新增节注意VirtualAddress的内存对齐不要使用文件对齐

第二步:复制AddressOfFunctions

长度:4*NumberOfFunctions

第三步:复制AddressOfNameOrdinals

长度:NumberOfNames*2

第四步:复制AddressOfNames

长度:NumberOfNames*4

第五步:复制所有的函数名

长度不确定,复制时直接修复AddressOfNames最绕的地方是这里

使用*AddressOfNames获取到函数名的地址后还需要RVA转FOA一次获取到函数名的地址,复制的时候修改AddressOfNames指向的地址。 


第六步:复制IMAGE_EXPORT_DIRECTORY结构

第七步:修复IMAGE_EXPORT_DIRECTORY结构中的

AddressOfFunctions

AddressOfNameOrdinals

AddressOfNames

第八步:修复目录项中的值,指向新的IMAGE_EXPORT_DIRECTORY

//移动导出表
LPVOID MoveExportTable(LPVOID NewFileBuffer)
{
    PIMAGE_DOS_HEADER ImageDosHeader = NULL;
    PIMAGE_NT_HEADERS ImageNTheader = NULL;
    PIMAGE_FILE_HEADER ImageFileHeader = NULL;
    PIMAGE_OPTIONAL_HEADER32 ImageOptionalHeader = NULL;
    PIMAGE_SECTION_HEADER ImageSectionHeader = NULL;
    PIMAGE_SECTION_HEADER LastImageSectionHeader = NULL;
    PIMAGE_SECTION_HEADER NewImageSectionHeader = NULL;
    PIMAGE_DATA_DIRECTORY ImageDataDirectory = NULL;
    PIMAGE_EXPORT_DIRECTORY ImageExportDirectory = NULL;
    PIMAGE_EXPORT_DIRECTORY NewImageExportDirectory = NULL;

    if (!NewFileBuffer)
    {
        printf("空间分配失败\n");
        return NULL;
    }
    if (*((PWORD)(DWORD)NewFileBuffer) != IMAGE_DOS_SIGNATURE)
    {
        printf("不是有效的MZ标志\n");
        free(NewFileBuffer);
        return NULL;
    }
    ImageDosHeader = (PIMAGE_DOS_HEADER)NewFileBuffer;
    if (*((PDWORD)((DWORD)NewFileBuffer + ImageDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
    {
        printf("不是有效的PE标志");
        free(NewFileBuffer);
        return NULL;
    }
    ImageNTheader = (PIMAGE_NT_HEADERS)((DWORD)NewFileBuffer + ImageDosHeader->e_lfanew);
    ImageFileHeader = (PIMAGE_FILE_HEADER)((DWORD)ImageNTheader + 4);
    ImageOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)ImageFileHeader + IMAGE_SIZEOF_FILE_HEADER);
    ImageSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)ImageOptionalHeader + ImageFileHeader->SizeOfOptionalHeader);
    LastImageSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)ImageSectionHeader + ((ImageFileHeader->NumberOfSections - 1) * IMAGE_SIZEOF_SECTION_HEADER));
    ImageDataDirectory = (PIMAGE_DATA_DIRECTORY)&ImageOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
    ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)NewFileBuffer + RVAToFOA(NewFileBuffer, ImageDataDirectory->VirtualAddress));
    //复制AddressOfFunctions
    memcpy((PNZCH)((DWORD)NewFileBuffer + LastImageSectionHeader->PointerToRawData), (PNZCH)((DWORD)NewFileBuffer + RVAToFOA(NewFileBuffer, ImageExportDirectory->AddressOfFunctions)),
        (ImageExportDirectory->NumberOfFunctions * 4));
    //复制AddressOfNameOrdinals
    memcpy((PNZCH)((DWORD)NewFileBuffer + (LastImageSectionHeader->PointerToRawData + (ImageExportDirectory->NumberOfFunctions * 4))), (PNZCH)((DWORD)NewFileBuffer + RVAToFOA(NewFileBuffer, ImageExportDirectory->AddressOfNameOrdinals)),
        (ImageExportDirectory->NumberOfNames * 2));
    //复制AddressOfNames
    memcpy((PNZCH)((DWORD)NewFileBuffer + (LastImageSectionHeader->PointerToRawData + ((ImageExportDirectory->NumberOfFunctions * 4) + (ImageExportDirectory->NumberOfNames * 2)))), 
        (PNZCH)((DWORD)NewFileBuffer + RVAToFOA(NewFileBuffer, ImageExportDirectory->AddressOfNames)),
        (ImageExportDirectory->NumberOfNames * 4));
    //复制函数名在新加节中的起始位置
    DWORD FunctionNameStartAddress = ((DWORD)LastImageSectionHeader->PointerToRawData + ((ImageExportDirectory->NumberOfFunctions * 4) + (ImageExportDirectory->NumberOfNames * 2)
        + (ImageExportDirectory->NumberOfNames * 4)));
    //FileBuffer中的原函数名的起始位置
    PDWORD FunctionNameAddress = (PDWORD)((DWORD)NewFileBuffer + RVAToFOA(NewFileBuffer, ImageExportDirectory->AddressOfNames));
    PDWORD NewFunctionNameAddress = (PDWORD)((DWORD)NewFileBuffer + (LastImageSectionHeader->PointerToRawData + (ImageExportDirectory->NumberOfFunctions*4) + (ImageExportDirectory->NumberOfNames*2)));
    //计算FunctionNameAddress偏移
    DWORD Offset = FunctionNameStartAddress - LastImageSectionHeader->PointerToRawData;
    for (size_t i = 0; i < ImageExportDirectory->NumberOfNames; i++)
    {
        //取出原函数名的起始位置
        
        PNZCH FunctionName = (PNZCH)(((DWORD)NewFileBuffer + RVAToFOA(NewFileBuffer, *FunctionNameAddress)));
        memcpy((PNZCH)((DWORD)NewFileBuffer + FunctionNameStartAddress), FunctionName,strlen(FunctionName));
        memset((PNZCH)((DWORD)NewFileBuffer + (FunctionNameStartAddress + strlen(FunctionName))), 0, 1);

        *NewFunctionNameAddress = (DWORD)LastImageSectionHeader->VirtualAddress + Offset;
        Offset = Offset + strlen(FunctionName) + 1;
        FunctionNameStartAddress = FunctionNameStartAddress + strlen(FunctionName) + 1;
        FunctionNameAddress = FunctionNameAddress++;
        NewFunctionNameAddress = NewFunctionNameAddress++;
        
    }
    //修复新的导出表数据
    NewImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)NewFileBuffer + (LastImageSectionHeader->PointerToRawData + Offset));
    memcpy(NewImageExportDirectory, ImageExportDirectory, sizeof(IMAGE_EXPORT_DIRECTORY));
    memcpy((PNZCH)((DWORD)NewFileBuffer + (LastImageSectionHeader->PointerToRawData + Offset) + sizeof(IMAGE_EXPORT_DIRECTORY)), NewExportFileName, sizeof(NewExportFileName));
    NewImageExportDirectory->Name = (LastImageSectionHeader->VirtualAddress + (Offset + sizeof(IMAGE_EXPORT_DIRECTORY)));
    NewImageExportDirectory->AddressOfFunctions = LastImageSectionHeader->VirtualAddress;
    NewImageExportDirectory->AddressOfNameOrdinals = (LastImageSectionHeader->VirtualAddress + (NewImageExportDirectory->NumberOfFunctions * 4));
    NewImageExportDirectory->AddressOfNames = (LastImageSectionHeader->VirtualAddress + (NewImageExportDirectory->NumberOfFunctions * 4) + (NewImageExportDirectory->NumberOfNames * 2));
    ImageDataDirectory->VirtualAddress = ((DWORD)LastImageSectionHeader->VirtualAddress + Offset);
    return NewFileBuffer;

}

 

13、移动重定位表

循环复制DataDirectory的VirtualAddress指向的FOA地址到新加节中,每次复制的大小为ImageBaseRelocation->SizeOfBlock

复制结束后修改DataDirectory的VirtualAddress指向新加节的开头位置

 

//移动重定位表
LPVOID MoveRelocateTable(LPVOID NewFileBuffer)
{
    PIMAGE_DOS_HEADER ImageDosHeader = NULL;
    PIMAGE_NT_HEADERS ImageNTheader = NULL;
    PIMAGE_FILE_HEADER ImageFileHeader = NULL;
    PIMAGE_OPTIONAL_HEADER32 ImageOptionalHeader = NULL;
    PIMAGE_SECTION_HEADER ImageSectionHeader = NULL;
    PIMAGE_SECTION_HEADER LastImageSectionHeader = NULL;
    PIMAGE_SECTION_HEADER NewImageSectionHeader = NULL;
    PIMAGE_DATA_DIRECTORY ImageDataDirectory = NULL;
    PIMAGE_BASE_RELOCATION ImageBaseRelocation = NULL;
    PIMAGE_BASE_RELOCATION NewImageBaseRelocationStartAddress = NULL;

    if (!NewFileBuffer)
    {
        printf("空间分配失败\n");
        return NULL;
    }
    if (*((PWORD)(DWORD)NewFileBuffer) != IMAGE_DOS_SIGNATURE)
    {
        printf("不是有效的MZ标志\n");
        free(NewFileBuffer);
        return NULL;
    }
    ImageDosHeader = (PIMAGE_DOS_HEADER)NewFileBuffer;
    if (*((PDWORD)((DWORD)NewFileBuffer + ImageDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
    {
        printf("不是有效的PE标志");
        free(NewFileBuffer);
        return NULL;
    }
    ImageNTheader = (PIMAGE_NT_HEADERS)((DWORD)NewFileBuffer + ImageDosHeader->e_lfanew);
    ImageFileHeader = (PIMAGE_FILE_HEADER)((DWORD)ImageNTheader + 4);
    ImageOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)ImageFileHeader + IMAGE_SIZEOF_FILE_HEADER);
    ImageSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)ImageOptionalHeader + ImageFileHeader->SizeOfOptionalHeader);
    LastImageSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)ImageSectionHeader + ((ImageFileHeader->NumberOfSections - 1) * IMAGE_SIZEOF_SECTION_HEADER));
    ImageDataDirectory = (PIMAGE_DATA_DIRECTORY)(&ImageOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]);
    ImageBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)NewFileBuffer + RVAToFOA(NewFileBuffer, ImageDataDirectory->VirtualAddress));
    //新的重定位表起始地址
    NewImageBaseRelocationStartAddress = (PIMAGE_BASE_RELOCATION)((DWORD)NewFileBuffer + LastImageSectionHeader->PointerToRawData);
    while (ImageBaseRelocation->VirtualAddress!=0 && ImageBaseRelocation->SizeOfBlock !=0)
    {

        memcpy((PNZCH)NewImageBaseRelocationStartAddress, (PNZCH)ImageBaseRelocation, ImageBaseRelocation->SizeOfBlock);
        NewImageBaseRelocationStartAddress = (PIMAGE_BASE_RELOCATION)((DWORD)NewImageBaseRelocationStartAddress + ImageBaseRelocation->SizeOfBlock);
        ImageBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)ImageBaseRelocation + ImageBaseRelocation->SizeOfBlock);

    }
    ImageDataDirectory->VirtualAddress = LastImageSectionHeader->VirtualAddress;
    return NewFileBuffer;
}

 

14、新加节并修改ImageBase

使用while (ImageBaseRelocation->VirtualAddress!=0&&ImageBaseRelocation->SizeOfBlock!=0)来遍历什么时候到重定位表的结尾

从ImageBaseRelocation + 8位置开始循环循环((ImageBaseRelocation->SizeOfBlock-8)/2)次因为ImageBaseRelocation + 8开始的数据都是WORD宽度的

把数据拆分成高4位和低12位,高4位用来判断地址是否需要修正,如果高4位的值为3表示数据需要修正

WORD FourHigh = ((*StartDataAddress & 0xF000)>>12);
WORD LowBit = (*StartDataAddress & 0x0FFF);

低12位用来表示偏移具体偏移位置修改FOA指向的位置的数据

if (FourHigh == 3)
{
*((PDWORD)((DWORD)NewFileBuffer + RVAToFOA(NewFileBuffer, ((DWORD)ImageBaseRelocation->VirtualAddress + LowBit)))) = *((PDWORD)((DWORD)NewFileBuffer + RVAToFOA(NewFileBuffer, ((DWORD)ImageBaseRelocation->VirtualAddress + LowBit)))) + 0x10000000;
}

修改高4位为3的所有数据使用原数据 + 增加的ImageBase的大小,修改完成

//移动重定位表并修改ImageBase
LPVOID MoveRelocateTableAndAlterImageBase(LPVOID NewFileBuffer)
{
    PIMAGE_DOS_HEADER ImageDosHeader = NULL;
    PIMAGE_NT_HEADERS ImageNTheader = NULL;
    PIMAGE_FILE_HEADER ImageFileHeader = NULL;
    PIMAGE_OPTIONAL_HEADER32 ImageOptionalHeader = NULL;
    PIMAGE_SECTION_HEADER ImageSectionHeader = NULL;
    PIMAGE_SECTION_HEADER LastImageSectionHeader = NULL;
    PIMAGE_SECTION_HEADER NewImageSectionHeader = NULL;
    PIMAGE_DATA_DIRECTORY ImageDataDirectory = NULL;
    PIMAGE_BASE_RELOCATION ImageBaseRelocation = NULL;

    if (!NewFileBuffer)
    {
        printf("空间分配失败\n");
        return NULL;
    }
    if (*((PWORD)(DWORD)NewFileBuffer) != IMAGE_DOS_SIGNATURE)
    {
        printf("不是有效的MZ标志\n");
        free(NewFileBuffer);
        return NULL;
    }
    ImageDosHeader = (PIMAGE_DOS_HEADER)NewFileBuffer;
    if (*((PDWORD)((DWORD)NewFileBuffer + ImageDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
    {
        printf("不是有效的PE标志");
        free(NewFileBuffer);
        return NULL;
    }
    ImageNTheader = (PIMAGE_NT_HEADERS)((DWORD)NewFileBuffer + ImageDosHeader->e_lfanew);
    ImageFileHeader = (PIMAGE_FILE_HEADER)((DWORD)ImageNTheader + 4);
    ImageOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)ImageFileHeader + IMAGE_SIZEOF_FILE_HEADER);
    ImageSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)ImageOptionalHeader + ImageFileHeader->SizeOfOptionalHeader);
    LastImageSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)ImageSectionHeader + ((ImageFileHeader->NumberOfSections - 1) * IMAGE_SIZEOF_SECTION_HEADER));
    ImageDataDirectory = (PIMAGE_DATA_DIRECTORY)(&ImageOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]);
    ImageOptionalHeader->ImageBase = 0x20000000;
    ImageBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)NewFileBuffer + RVAToFOA(NewFileBuffer, ImageDataDirectory->VirtualAddress));
    while (ImageBaseRelocation->VirtualAddress!=0&&ImageBaseRelocation->SizeOfBlock!=0)
    {
        PWORD StartDataAddress = PWORD((DWORD)ImageBaseRelocation + 8);
        for (size_t i = 0; i < ((ImageBaseRelocation->SizeOfBlock-8)/2); i++)
        {
            WORD FourHigh = ((*StartDataAddress & 0xF000)>>12);
            WORD LowBit = (*StartDataAddress & 0x0FFF);
            if (FourHigh == 3)
            {
                *((PDWORD)((DWORD)NewFileBuffer + RVAToFOA(NewFileBuffer, ((DWORD)ImageBaseRelocation->VirtualAddress + LowBit)))) = *((PDWORD)((DWORD)NewFileBuffer + RVAToFOA(NewFileBuffer, ((DWORD)ImageBaseRelocation->VirtualAddress + LowBit)))) + 0x10000000;
            }
            StartDataAddress++;
        }
        ImageBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)ImageBaseRelocation + ImageBaseRelocation->SizeOfBlock);
    }
    
    return NewFileBuffer;
}

 

15、IAT表

IAT表寻址有2种方式:

1)使用IMAGE_DATA_DIRECTORY寻址 #define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table

2)使用导入表寻址FirstThunk

IAT表的状态分为运行前和运行时2种状态

运行前IAT中存的是函数的序号或者是函数名的RVA

运行时IAT中存的值会被操作系统直接替换成Dll或exe中函数的基址

16、导入表

实现:

1、使用OD打开一个发布版的exe程序,定位到某个DLL的API

2、在没有加载的EXE中找到这个位置,观察加载前后的区别

导入表结构:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; //RVA 指向IMAGE_THUNK_DATA结构数组
};
DWORD TimeDateStamp; //时间戳
DWORD ForwarderChain;
DWORD Name; //RVA,指向dll名字,该名字已0结尾
DWORD FirstThunk; //RVA,指向IMAGE_THUNK_DATA结构数组
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

PE文件加载前:

PE文件加载后:

typedef struct _IMAGE_THUNK_DATA32 { 
union {    
PBYTE ForwarderString;    
PDWORD Function;    
DWORD Ordinal;    //序号    
PIMAGE_IMPORT_BY_NAME AddressOfData;    //指向IMAGE_IMPORT_BY_NAME    
} u1;    
} IMAGE_THUNK_DATA32;    
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;    


typedef struct _IMAGE_IMPORT_BY_NAME {    
WORD Hint;    //可能为空,编译器决定 如果不为空 是函数在导出表中的索引    
BYTE Name[1];    //函数名称,以0结尾    
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

 

 

//打印导入表
VOID PrintImportTable(LPVOID FileBuffer)
{

    PIMAGE_FILE_HEADER ImageFileHeader = NULL;
    PIMAGE_DOS_HEADER ImageDosHeader = NULL;
    PIMAGE_NT_HEADERS ImageNTheader = NULL;
    PIMAGE_OPTIONAL_HEADER32 ImageOptionalHeader32 = NULL;
    PIMAGE_DATA_DIRECTORY ImageDataDirectory = NULL;
    PIMAGE_IMPORT_DESCRIPTOR ImageImportDescriptor = NULL;

    if (*((PWORD)FileBuffer) != IMAGE_DOS_SIGNATURE)
    {
        printf("不是有效的MZ标记\n");
        free(FileBuffer);
        return ;
    }

    ImageDosHeader = (PIMAGE_DOS_HEADER)FileBuffer;
    if (*((PDWORD)((DWORD)FileBuffer + ImageDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
    {
        printf("不是有效的PE标志\n");
        free(FileBuffer);
        return ;
    }

    ImageNTheader = (PIMAGE_NT_HEADERS)((DWORD)FileBuffer + ImageDosHeader->e_lfanew);
    ImageFileHeader = (PIMAGE_FILE_HEADER)((DWORD)ImageNTheader + 4);
    ImageOptionalHeader32 = (PIMAGE_OPTIONAL_HEADER32)((DWORD)ImageFileHeader + IMAGE_SIZEOF_FILE_HEADER);
    ImageDataDirectory = (PIMAGE_DATA_DIRECTORY)&ImageOptionalHeader32->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    ImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)FileBuffer + RVAToFOA(FileBuffer, ImageDataDirectory->VirtualAddress));
    while (ImageImportDescriptor->Characteristics != 0)
    {
        PNZCH DllName = (PNZCH)((DWORD)FileBuffer + RVAToFOA(FileBuffer, ImageImportDescriptor->Name));
        printf("Dll名称->%s\n", DllName);
        printf("-------------------------------------------\n");
        printf("OriginalFirstThunk函数名或序号为\n");
        PDWORD OriginalFirstThunk = (PDWORD)((DWORD)FileBuffer + RVAToFOA(FileBuffer, ImageImportDescriptor->OriginalFirstThunk));
        PDWORD FirstThunk = (PDWORD)((DWORD)FileBuffer + RVAToFOA(FileBuffer, ImageImportDescriptor->FirstThunk));
        while (*OriginalFirstThunk != 0)
        {
            
            DWORD OriginalFirstThunkBestHighBit = (*OriginalFirstThunk & 0x80000000) >> 31;
            if (OriginalFirstThunkBestHighBit == 1)
            {
                DWORD OriginalFirstThunkSerialNumber = (*OriginalFirstThunk & 0x6FFFFFFF);
                printf("序号为->%x\n", OriginalFirstThunkSerialNumber);
            }
            else
            {
                PIMAGE_IMPORT_BY_NAME OriginalFirstThunkFunctionName = (PIMAGE_IMPORT_BY_NAME)((DWORD)FileBuffer + RVAToFOA(FileBuffer, *OriginalFirstThunk));
                printf("HIT为->%x\n", OriginalFirstThunkFunctionName->Hint);
                printf("函数名为->%s\n", OriginalFirstThunkFunctionName->Name);
            }
            OriginalFirstThunk++;
        }
        printf("FirstThunk函数名或序号为\n");
        while (*FirstThunk != 0)
        {

            DWORD FirstThunkBestHighBit = (*FirstThunk & 0x80000000) >> 31;
            if (FirstThunkBestHighBit == 1)
            {
                DWORD FirstThunkSerialNumber = (*FirstThunk & 0x6FFFFFFF);
                printf("序号为->%x\n", FirstThunkSerialNumber);
            }
            else
            {
                PIMAGE_IMPORT_BY_NAME FirstThunkFunctionName = (PIMAGE_IMPORT_BY_NAME)((DWORD)FileBuffer + RVAToFOA(FileBuffer, *FirstThunk));
                printf("HIT为->%x\n", FirstThunkFunctionName->Hint);
                printf("函数名为->%s\n", FirstThunkFunctionName->Name);
            }
            FirstThunk++;
        }
        ImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)ImageImportDescriptor + sizeof(IMAGE_IMPORT_DESCRIPTOR));
    }
    free(FileBuffer);
}

 17、绑定导入表


当IMAGE_BOUND_IMPORT_DESCRIPTOR结构中的TimeDateStamp与DLL文件标准PE头中的TimeDateStamp值不相符
时,或者DLL需要重新定位的时候,就会重新计算IAT中的值.

绑定导入表结构:

PE加载EXE相关的DLL时,首先会根据IMAGE_IMPORT_DESCRIPTOR结构中的TimeDateStamp来判断是否要重新
计算IAT表中的地址。

TimeDateStamp == 0 未绑定

TimeDateStamp == -1 已绑定 真正的绑定时间为IMAGE_BOUND_IMPORT_DESCRIPTOR的TimeDateStamp

绑定导入表的定位:

绑定导入表位于数据目录的第12项

typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR {                            
    DWORD   TimeDateStamp;                            
    WORD    OffsetModuleName;                            
    WORD    NumberOfModuleForwarderRefs;                            
// Array of zero or more IMAGE_BOUND_FORWARDER_REF follows                            
} IMAGE_BOUND_IMPORT_DESCRIPTOR,  *PIMAGE_BOUND_IMPORT_DESCRIPTOR;                            
                            
typedef struct _IMAGE_BOUND_FORWARDER_REF {                            
    DWORD   TimeDateStamp;                            
    WORD    OffsetModuleName;                            
    WORD    Reserved;                            
} IMAGE_BOUND_FORWARDER_REF, *PIMAGE_BOUND_FORWARDER_REF;    

绑定导入表的2个难点

第一:数据目录中存储的直接是FOA地址不需要RVA转FOA

第二:所有DLL名字的地址是第一个IMAGE_BOUND_IMPORT_DESCRIPTOR的地址加上每个IMAGE_BOUND_IMPORT_DESCRIPTOR或IMAGE_BOUND_FORWARDER_REF的OffsetModuleName

 

 18、导入表注入

注入的种类:

1、注册表注入 5、无DLL注入

2、导入表注入 6、Apc 注入

3、特洛伊注入 7、Windows挂钩注入DLL

4、远程线程注入 8、输入法注入


导入表注入原理:

当Exe被加载时,系统会根据Exe导入表信息来加载需要用到的DLL,导入表注入的原理就是修改exe导入表,将自己的DLL
添加到exe的导入表中,这样exe运行时可以将自己的DLL加载到exe的进程空间.


导入表注入的实现步骤:


第一步:

根据目录项(第二个就是导入表)得到导入表信息:

typedef struct _IMAGE_DATA_DIRECTORY {                    
    DWORD   VirtualAddress;                    
    DWORD   Size;                    
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;                    
                    
VirtualAddress :指向导入表结构                    
Size:导入表的总大小                    

这两个值都需要

第二步:

                                    
typedef struct _IMAGE_IMPORT_DESCRIPTOR {                                    
    union {                                    
        DWORD   Characteristics;                                               
        DWORD   OriginalFirstThunk;                                             
    };                                    
    DWORD   TimeDateStamp;                                                   
    DWORD   ForwarderChain;                                                  
    DWORD   Name;                                    
    DWORD   FirstThunk;                                                     
} IMAGE_IMPORT_DESCRIPTOR;                                    
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;                                    

判断哪一个节的空白区 > Size(原导入表的大小) + 20 + A + B + C + D

如果空间不够:可以将C/D 存储在其他的空白区

也就是,只要空白区 > Size + 0x20就可以了

如果仍然不够,就需要扩大最后一个节,或者新增节来解决.

第三步:

将原导入表全部Copy到空白区

第四步:

在新的导入表后面,追加一个导入表.

第五步:

追加8个字节的INT表 8个字节的IAT表

第六步:

追加一个IMAGE_IMPORT_BY_NAME 结构,前2个字节是0 后面是函数名称字符串

第七步:

将IMAGE_IMPORT_BY_NAME结构的RVA赋值给INT和IAT表中的第一项

第八步:

分配空间存储DLL名称字符串 并将该字符串的RVA赋值给Name属性

第九步:

修正IMAGE_DATA_DIRECTORY结构的VirtualAddress和Size

 

posted @ 2019-06-17 15:07  篓子  阅读(727)  评论(0编辑  收藏  举报