本文整理自《加密与解密》第10章
一、基本概念
基地址:映射文件的起始地址被称为模块句柄,可以通过模块句柄访问内存中其他的数据结构。这个初始内存地址被称为基地址
GetModuleHandle函数返回可执行文件的基地址。
相对虚拟地址(RVA):RVA只是内存中的一个简单的相对于PE文件装入地址的偏移位置。
实际的内存地址被称作虚拟地址(VA)
VA=ImageBase+RVA
文件偏移地址:PE文件存储在磁盘上时,某个数据的位置相对于文件头的偏移量,称为文件偏移地址(File Offset)或物理地址(RAWOffset)。用WinHex打开文件所显示的地址就是文件偏移地址。
二、PE文件头
PNTHeader=ImageBase+dosHeader->e_lfanew
IMAGE_NT_HEADERS
{
+0h DWORD Signature; PE00
+4h IMAGE_FILE_HEADER FileHeader;
+18h IMAGE_OPTIONAL_HEADER32 OptionalHeader;
}
Signature4个字节
IMAGE_FILE_HEADER
{
WORD Machine;
WORDNumberOfSections;区块数目
DWORDTimeDateStamp;时间戳
DWORDPointerToSymbols;
DWORDNumberOfSymbols;
WORDSizeOfOptionalHeader;指出了OptionalHeader的大小,32位PE一般是00E0h,64位是00F0h
WORDCharacteristics;
}
IMAGE_FILE_HEADER结构体一共20个字节
IMAGE_OPTIONAL_HEADER32
{
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUnInitializedData;
DWORD AddressOfEntryPoint; 程序执行入口RVA
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImgaeBase; 程序默认装入基地址
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingsystemversion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsybtemVersion;
WORD MinorSubsybtemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage; 映像.
装入内存后的总尺寸,一直是16
DWORD SizeoOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlages;
DWORD NumberOfRvaAndSizes; 数据目录表的项数
IMAGE_DATA_DIRECTORYDataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
}
一直到NumberOfRvaAndSizes结束,一共96字节。不加DataDirectory
此例中,DataDirectory一共128字节。
每个成员占8个字节,
IMAGE_DATA_DIRECTORY{
VirtualAddress DWORD//数据块的起始RVA
Size DWORD//数据块的长度
}
三、区块
IMAGE_SECTION_HEADER
{
//IMAGE_SIZEOF_SHORT_NAME=8
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];//节表名称,如“.text”
union
{
DWORD PhysicalAddress;//物理地址
DWORD VirtualSize;//指出实际的、被使用的区块大小,是区块在没对齐处理前的实际大小。
} Misc;
DWORD VirtualAddress;//该区块装载到内存中的RVA
DWORD SizeOfRawData;//该区块在磁盘文件中所占大小
DWORD PointerToRawData;//该区块在磁盘文件中的偏移,这个字段用于给出原始数据在文件中的偏移
DWORD PointerToRelocations;//重定位的偏移
DWORD PointerToLinenumbers;//行号表的偏移
WORD NumberOfRelocations;//重定位项数目
WORD NumberOfLinenumbers;//行号表的数目
DWORD Characteristics;//节属性如可读,可写,可执行等
}
每一个区块都有一个这样的结构。每个结构20字节。
3.1 区块的对齐值
3.2 文件偏移与虚拟地址转换
ImageBase=400000,VA=401112h
RVA=1112h,位于.text区块,RAW=1112h-1000h+400h=512h
四、输入表
4.1 输入表结构
输入表以一个IMAGE_IMPORT_DESCRIPTOR(简称IID)数组开始,每个被PE文件隐式地连接进来的DLL都有一个IID,在IID结构的最后,由一个内容为全0的IID结构作为结束。
IMAGE_IMPORT_DESCRIPTOR STRUC
union ; 00h
Characteristics DWORD
OriginalFirstThunk DWORD
ends
TimeDateStamp DWORD ;04h
ForwarderChain DWORD ;08h
Name DWORD ;0Ch
FirstThunk DWORD ;10h
IMAGE_IMPORT_DESCRIPTOR ENDS
IMAGE_THUNK_DATA是一个指针大小的联合,每一个IMAGE_THUNK_DATA对应于一个从可执行文件输入的函数。
IMAGE_THUNK_DATA STRUC
unionu1
ForwarderString DWORD ;
Function DWORD ;被输入的函数的内存地址
Ordinal DWORD ;被输入的APT的序数值
AddressOfData DWORD ;指向IMAGE_IMPORT_BY_NAME
ends
IMAGE_THUNK_DATA ENDS
IMAGE_IMPORT_BY_NAME STRUCT
Hint WORD;
指示本函数在其所主流DLL的输出表中的序号,该值不是必须的,一般设置为0。
Name BYTE;含有输入函数的函数名。ASCII字符串
IMAGE_IMPORT_BY_NAME ENDS
4.2 输入地址表(IAT)
OriginalFirstThunk所指向的INT是不可改写的,由FirstThunk所指向IAT是由PE装载器重写的。PE装载器首先搜索OriginalFirstThunk,找到每个IMAGE_IMPORT_BY_NAME结构所指向的输入函数的地址,然后用函数真正入口地址替代由FirstThunk指向的IMAGE_THUNK_DATA数组里的元素值。
4.3 输入表实例
PE文件头起始位置是B0h,输入表地址就在B0h+80h=130h处
130h处为00002040h,是输入表在内存中偏移量为2040h的地方,是RVA值,需转换为文件偏移量。
从下图可以看出,2040h在.rdata块中,转换为文件偏移是600h+(2040h-2000h)=640h
输入表大小为0x3C,一个IID是5个双字,一共有3个IID,最后一个IID全为0
每个都是4字节。
OrignalFirstThunk | TimeDateStamp | ForwardChain | Name | FirstThunk |
0000208C | 00000000 | 00000000 | 00002174 | 00002010 |
0000207C | 00000000 | 00000000 | 000021B4 | 00002000 |
00000000 | 00000000 | 00000000 | 00000000 | 00000000 |
RVA:2174h->RAW:174+600=774h,文件偏移774h处的字符是USER32.dll
RVA:208Ch->RAW:68Ch 文件偏移68Ch处的为IMAGE_THUNK_DATA数组,存储指向IMAGE_IMPORT_BY_NAME结构的地址,以一串00结束。每一个的大小是一个双字,4个字节。
00002110 | 0000211c | 000020f4 | 000020e0 |
00002150 | 00002164 | 00002102 | 000020ce |
000020bc | 0000212e | 00002142 | 00000000 |
RVA:2110h->RAW:710h
文件偏移710h处的存的是LoadIconA,前面有两个字节的空缺,是作为函数名(Hint)引用的,可以为0.
一开始的IID结构如下,可以看到,IAT数据中还是保存着AddressOfData的值
根据找到的函数名,调用GetProctorAddress函数,获取函数的入口地址,用函数入口地址去掉FirstThunk指向的地址串中对应的值(IAT)。
五、基址重定位
5.1 基址重定位结构定义
IMAGE_SBASE_RELOCATION STRUC
VirtualAddress DWORD;重定位数据开始RVA地址
SizeOfBlock DWORD;重定位块的长度
TypeOffset WORD;重定项位数组
IMAGE_BASE_RELOCATION ENDS
5.2 基址重定位结构实例分析
PE文件头起始位置是0x00000100,加上重定位表的偏移A0,文件偏移0x1A0处便是重定位表结构。RVA为0x5000,大小为0x10
.reloc的RVA是5000h,RAW是E00h。
VirtualAddress:0x1000
SizeOfBlock:0x10
一共有(10h-8h)/2h=4h个TypeOffset
注:VirtualAddress和SizeOfBlock都是4个字节,一个8字节,这个块大小是0x10,TypeOffset的大小是2个字节。
重定位数据1:300F
重定位数据2:3023h
重定位数据3:0000h
重定位数据4:0000h
六、输出表
IMAGE_EXPORT_DIRECTORY
Characteristics DWORD;未使用,总是0
TimeDateStamp DWORD;文件生成时间
MajorVersion WORD;主版本号,一般为0
MinorVersion WORD;次版本号,一般为0
Name DWORD;模块的真实名称
Base DWORD;基数,加上序数就是函数地址数组的索引值
NumberOfFunctions DWORD;AddressOfFunctions阵列中的元素个数
NumberOfNames DWORD;AddressOfNames阵列中的元素个数
AddressOfFunctions DWORD;指向函数地址数组,EAT的RVA
AddressOfNames DWORD;函数名字的指针地址ENT的RVA
AddressOfNameOrdinals DWORD;指向输出***数组,输出序数表的RVA
IMAGE_EXPORT_DIRECTORY ENDS
输出表实例:
0x0100+0x78=0x0178
输出表RVA:00004000h,大小0x45
RVA:4000h->RAW:C00(这个不是计算来的,从表中看来的)
输出表:
RVA RAW
Name: 0x4032 0xC32 DllDemo.DLL
NumberOfFunctions 0x01
NumberOfNames 0x01
AddressOfFunctions 0x4028 0xC28 0x1008
AddressOfNames 0x402c 0xC2C 0x403E->C3E->MsgBox
AddressOfNameOrdinals 0x4030 0xC30