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