PE文件格式(用于代码逆向分析)

PE文件格式

PE文件是指32位可执行文件,也称PE32;64位的可执行文件称为PE+或PE32+,是PE文件的一种扩展形式

种类主扩展名
可执行系列EXE 或 SCR
驱动程序系列SYS 或 VXD
库系列DLL 或 OCX 或 CPL 或 DRV
对象文件系列OBJ

PE文件基本结构

1、从DOS头(DOS header)到节区头(Section header)是PE头部分,其下的节区合称PE体,文件中使用偏移(offset),内存中使用VA(Virtual Address 虚拟地址)来表示位置。
2、PE头与各节区的尾部都存在一个区域,称为NULL填充(NULL padding)。
在这里插入图片描述

VA & RVA

1、VA指的是进程虚拟内存的绝对地址,RVA(Relative Virtual Address 相对虚拟地址)指从某个基准位置(ImageBase)开始的相对地址。其中VA与RVA满足:RVA + ImageBase = VA
2、PE头内部信息大都以RVA形式存在。

RVA to RAW(内存地址与文件偏移地址的映射)

1、PE文件加载到内存时,每个节区都要准确完成内存地址与文件偏移间的映射,一般称为RVA to RAW,方法如下:
(1)查找RVA所在节区,即节区头的虚拟地址VirtualAddress
(2)使用简单的公式计算文件偏移(RAW)
2、根据IMAGE_SECTION_HEADER结构体,换算公式:
RAW - PointerToRawData = RVA - VirtualAddress,其中PointerToRawData是磁盘上的PE文件节区偏移地址,VirtualAddress是内存上的PE文件节区相对基准位置ImageBase偏移地址。
在这里插入图片描述
案例:
若RVA = 5000时,则RAW = 5000(RVA) - 1000(VirtualAddress) + 400(PointerToRawData)

PE文件头

DOS头

typedef struct _IMAGE_DOS_HEADER{
	WORD e_magic; //DOS signature:4D5A("MZ")
	WORD e_cblp;
	WORD e_cp;
	WORD e_crlc;
	WORD e_cparhdr;
	WORD e_minalloc;
	WORD e_maxalloc;
	WORD e_ss;
	WORD e_sp;
	WORD e_csum;
	WORD e_ip;
	WORD e_cs;
	WORD e_lfarlc;
	WORD e_ovno;
	WORD e_res[4];
	WORD e_oemind;
	WORD e_oeminfo;
	WORD e_res2[10];
	LONG e_lfanew; //offset to NT(IMAGE_NT_HEADER) header
}IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

IMAGE_DOS_HEADER结构体大小为64(0x3CH)字节,其中e_magic指示DOS签名,e_lfanew指示NT头的偏移。

DOS存根

DOS存根(stub)在DOS头下方,是个可选项,且大小不固定,由代码与数据混合而成,灵活使用该特性可以在一个可执行文件中创建另一个文件,在DOS与Windows中都能运行(在DOS环境中运行16位DOS代码,在Windows环境中运行32Windows代码)。

NT头

#ifdef _WIN64
typedef IMAGE_NT_HEADERS64  IMAGE_NT_HEADERS;
typedef PIMAGE_NT_HEADERS64 PIMAGE_NT_HEADERS;
#else
typedef IMAGE_NT_HEADERS32  IMAGE_NT_HEADERS;
typedef PIMAGE_NT_HEADERS32 PIMAGE_NT_HEADERS;
#endif

typedef struct _IMAGE_NT_HEADERS{
	WORD Signature; //PE Signature:50450000("PE"00)
	IMAGE_FILE_HEADER FileHeader;
	IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

IMAGE_NT_HEADERS32结构体由3个成员组成,第一个成员为签名Signature,值位50450000H,另外两个成员分别为文件头(File Header)与可选头(Option Header)结构体。

IMAGE_FILE_HEADER FileHeader文件头

typedef struct _IMAGE_FILE_HEADER{
	WORD Machine;//每个CPU都拥有唯一的Machine码,兼容32位Intel x86芯片的Machine码为14C
	WORD NumberOfSections;//指出文件中存在的节区数量,该值必须大于0
	DWORD TimeDateStamp;//用于记录编译器创建此文件的时间
	DWORD PointerToSymbolTable;//COFF符号指针,程序调试信息
	DWORD NumberOfSymbols;//符号数
	WORD SizeOfOptionalHeader;//指出IMAGE_OPTIONAL_HEADER32结构体的大小,PE+与PE对应的结构体大小不一样
	WORD Charateristics;//标识文件的属性,是否是可运行、是否为DLL文件等信息,以bit OR形式组合
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

//Charateristics
#define IMAGE_FILE_RELOCS_STRIPPED         0x0001 //Relocation info stripped from file
#define IMAGE_FILE_EXECUTABLE_IMAGE        0x0002 //File is executable
#define IMAGE_FILE_LINE_NUMS_STRIPPED      0x0004 //Line numbers stripped from file
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED     0x0008 //Local symbols stripped from file
#define IMAGE_FILE_AGGRESIVE_WS_TRIM       0x0010 //Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE     0x0020 //App can handle >2Gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO  	   0x0080 //Byte of machine word are reversed
#define IMAGE_FILE_32BIT_MACHINE           0x0100 //32 bit word machine
#define IMAGE_FILE_DEBUG_STRIPPED          0x0200 //Debugging info stripped from file in .DBG file
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 //If iamge is on removable media,copy and run from the swap file
#define IMAGE_FILE_RNET_RUN_FROM_SWAP      0x0800 //If image is on net,copy and run from the swap file
#define IMAGE_FILE_SYSTEM                  0x1000 //System file
#define IMAGE_FILE_DLL				       0x2000 //File is a DLL
#define IMAGE_FILE_UP_SYSTEM_ONLY	       0x4000 //File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED	       0x8000 //Byte of machine word are reversed

IMAGE_OPTIONAL_HEADER32 OptionalHeader 可选头

typedef struct _IMAGE_DATA_DIRECTORY{
	DWORD VirtualAddress;//数据的起始地址(RVA)
	DWORD Size;//对应的数据大小
}IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16

typedef struct _IMAGE_OPTIONAL_HEADER{
	WORD Magic;//为IMAGE_OPTIONAL_HEADER32结构体时,Magic码为10B;为IMAGE_OPTIONAL_HEADER64结构体时,Magic码为20B
	BYTE MajorLinkerVersion;//创建文件连接器的主板本号
	BYTE MinorLinkerVersion;//创建文件连接器的次板本号
	DWORD SizeOfCode;//所有具有IMAGE_SCN_CNT_CODE属性的节的总大小
	DWORD SizeOfInitializedData;//所有包含已初始化数据的节的总大小
	DWORD SizeOfUninitialiedData;//所有包含未初始化数据的节的总大小
	                             //这个值总是0,因为连接器把未初始化的数据附加到常规数据节的末尾
	DWORD AddressOfEntryPoint;//持有EP的RVA值,指出程序最先执行的代码起始地址
	DWORD BaseOfCode;//第一个执行代码相对于ImageBase的位置
	DWORD BaseOfData;
	DWORD ImageBase;//PE文件被加载到内存时,指出文件的优先装入地址,EXE文件的ImageBase值为00400000H,DLL文件的ImageBase值为10000000
				    //执行PE文件时,PE装载器先创建进程,再将文件载入内存,然后把EIP寄存器的值设置为ImageBase + AddressOfEntryPoint。
	DWORD SectionAlignment;//指定节区在内存中的最小单位
	DWORD FileAlignment;//指定节区在磁盘文件中的最小单位
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;//指定PE Image在虚拟内存中所占空间的大小
	DWORD SizeOfHeader;//指出整个PE头的大小,该值必须是FileAlignment的整数倍
	DWORD CheckSum;
	WORD Subsystem;//区分系统驱动文件(*.sys)与普通的可执行文件(*.exe或*.dll)
				  //1:Driver文件,如ntfs.sys  2:GUI文件,如notepad.exe  3:CUI文件,如cmd.exe
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;
	DWORD SizeOfStackCommit;
	DWORD SizeOfHeapReserve;
	DWORD SizeOfHeapCommit;
	DWORD NumberOfRvaAndSizes;//指定DataDirectory数组的个数
	IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//每项数组都有不同的定义
}IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
//DataDirectory数组
DataDirectory[0] = EXPORT Directory
DataDirectory[1] = IMPORT Directory
DataDirectory[2] = RESOURCE Directory
DataDirectory[3] = EXCEPTION Directory
DataDirectory[4] = SECURITY Directory
DataDirectory[5] = BASERELOC Directory
DataDirectory[6] = DEBUG Directory
DataDirectory[7] = COPYRIGHT Directory
DataDirectory[8] = GLOBALPTR Directory
DataDirectory[9] = TLS Directory
DataDirectory[A] = LOAD_CONFIG Directory
DataDirectory[B] = BOUND_IMPORT Directory
DataDirectory[C] = IAT Directory
DataDirectory[D] = DELAY_IMPORT Directory
DataDirectory[E] = COM_DESCRIPTOR Directory
DataDirectory[F] = Reserved Directory

节区头

//每个节区头大小为40字节
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER{
	BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
	union {
		DWORD PhysicalAddress;//物理地址
		DWORD VirtualSize;//内存中该节区所占大小
	}Misc;
	DWORD VirtualAddress;//内存中节区起始地址(RVA)
	DWORD SizeOfRawData;//磁盘文件中节区所占大小
	DWORD PointerToRawData;//磁盘文件中节区起始位置
	DWORD PointerToRelocations;//段重定位表在文件中的位置
	DWORD PointerToLineNumbers;//段的行号表在文件中的位置
	WORD NumberOfRelocations;//
	WORD NumberOfLineNumbers;//
	DWORD Characteristics;//节区属性(bit OR)
}IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

//Charateristics
#define IMAGE_SCN_CNT_CODE               0x00000020 //Section contains code
#define IMAGE_SCN_CNT_INITIALIZED_DATA   0x00000040 //Section contains initialized data
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 //Section contains uninitialized data
#define IMAGE_SCN_MEM_EXECUTE            0x20000000 //Section is executable
#define IMAGE_SCN_MEN_READ               0x40000000 //Section is readable
#define IMAGE_SCN_MEM_WRITE              0x80000000 //Section is writable

1、VirtualAddress与PointerToRawData可以不带任何值,分别由(IMAGE_OPTIONAL_HEADER32)中的SectionAlignment与File Alignment确定。
2、Name成员不一定需要以NULL结束,也不一定是ASCII值

PE文件格式的应用

1、加载DLL的方式有两种:一种是“显式链接”(Explicit Linking),程序使用DLL是加载,使用完毕后释放内存;另一种是“隐式加载”(Implicit Linking),程序开始时一同加载DLL,程序终止时再释放占用内存。IAT提供的机制与隐式链接有关。
2、

IAT导入地址表

1、IAT保存的内容与Windows操作系统的核心进程、内存、DLL结构有关。
2、程序编译时,并不知道该程序要运行在那种Windows(7、XP),哪种语言(ENG、JPN),哪种服务包(ServicePack)下,因此,内核重要的DLL(如kernel32.dll)的版本不一样,对应的DLL中API函数地址也不相同;为了确保在所有环境中都能正常调用DLL中的API函数,编译器要预先保存API函数实际地址的位置,并记下CALL DWORD PTR DS:[Address]指令形式,其中Address是IAT数组中的某一项地址,执行文件时,PE装载器将对应的API函数地址写到Address位置上
3、DLL加载时,无法保证被加载到PE头指定的ImageBase地址处,需要DLL重定位(Windows系统的DLL文件拥有自身固定的ImageBase)。
4、EXE文件拥有独立的虚拟空间地址,因此能够准确加载到自身的ImageBase中。
5、微软制作服务包过程中重建相关系统文件,此时会硬编入准确地址(普通的DLL实际地址不会被硬编码到IAT中,通常带有与INT相同的值)。

//IMAGE_IMPORT_DESCRIPTOR结构体记录着PE文件要导入哪些库文件
//导入多少个库,就存在多少个IMAGE_IMPORT_DESCRIPTOR结构体,这些结构体形成数组,且结构体数组最后以NULL结构体结束
//IMAGE_OPTIONAL_HEADER32.DataDirectory[1].VirtualAddress的值就是IMAGE_IMPORT_DESCRIPTOR结构体数组的起始地址
typedef struct _IMAGE_IMPORT_DESCRIPTOR{
	union{
		DWORD Characteristics;
		DWORD OriginalFirstThunk;//INT(Import Name Table) address(RVA)
	};
	DWORD TimeDateStamp;
	DWORD ForwarderChain;
	DWORD Name;//Library name string address(RVA)
	DWORD FirstThunk;//IAT(Import Address Table) address(RVA)
}IMAGE_IMPORT_DESCRIPTOR;
//OriginalFirstThunk、FirstThunk指向的IAT、INT为长整形(4个字节数据类型)数组,以NULL结束
//INT数组中各元素的值是IMAGE_IMPORT__BY_NAME结构体指针
//IAT数组是4个字节的指针数组,指向IMAGE_THUNK_DATA类型,以NULL结束,指向DLL中的API函数地址
//INT数组和IAT数组大小应相同
typedef struct _IMAGE_IMPORT_BY_NAME{
	WORD Hint;//ordinal address(RVA),指向序号地址
	          //指向库中函数固有编号地址,在Ordinal序号的后面为函数名称字符串(以'\0'结束)
	BYTE Name[1];//function name string,基本为0000H形式存储
}IMAGE_IMPORT__BY_NAME, *PIMAGE_IMPORT__BY_NAME;

//有多少个函数被导入,就有多少个元素,以NULL结尾
typedef struct _IMAGE_THUNK_DATA32{
	union{
		DWORD ForwarderString; //一个RVA地址,指向forward string
		DWORD Function; //被导入的函数的入口地址
		DWORD Ordinal; //该函数的序号
		DWORD AddressOfData; //一个RVA地址,指向IAMGE_IMPORT_BY_NAME
	}u1;
}IMAGE_THUNK_DATA32;

在这里插入图片描述
图中,INT与IAT的各元素指向相同地址,但是实际很多情况下它们指向的地址不一致的。
IAT每个元素对应一个被导入的符号,元素的值在不同情况有不同的含义,在动态链接器刚完成映射还没有开始重定位和符号解析时,IAT中的元素值表示相对应的导入符号的序号或者符号名,当动态链接器完成该模块的链接时,元素值被动态链接器改写成该符号的真正地址。如若元素值最高位为1,则低31位值就是导入符号的序号值,如若没有,那么元素的值指向IMAGE_IMPORT__BY_NAME结构体的RVA

PE装载器把导入函数输入至IAT的顺序

  1. 读取IMAGE_IMPORT_DESCRIPTOR结构体的Name成员,获取库名称字符串(如"kernel32.dll")
  2. 装载相应库–>LoadLibrary(“kernel32.dll”)
  3. 读取IMAGE_IMPORT_DESCRIPTOR结构体的OriginalFirstThunk成员,获取INT地址
  4. 逐一读取INT数组的值,获取相应IMAGE_IMPORT_BY_NAME地址(RVA)
  5. 使用IMAGE_IMPORT_BY_NAME的Hint(Ordinal)或Name成员,获取相应函数的起始地址–>GetProcAddress(“GetCurrentThreadld”)从EAT中获取实际地址
  6. 读取IMAGE_IMPORT_DESCRIPTOR结构体的FirstThunk(IAT)成员,获取IAT数组地址
  7. 将第5步获得的函数地址输入相应IAT数组中
  8. 重复步骤4~7,直到INT结束(遇到NULL)

EAT导出地址表

1、只有通过EAT才能准确求得从相应库中导出函数的起始地址。
2、PE文件内的特定结构体(IMAGE_EXPORT_DIRECTORY)保存着导出信息,且PE文件中仅有一个用来说明EAT导出地址表的IMAGE_EXPORT_DIRECTORY结构体
3、用来说明IAT导入地址表的IMAGE_EXPORT_DIRECTORY结构体以数组形式存在,且拥有多个成员。
4、从库中获取函数地址的API为GetProcAddress()函数,该API引用EAT来获取指定API的地址。

//IMAGE_OPTIONAL_HEADER32.DataDirectory[0].VirtualAddress的值就是IMAGE_EXPORT_DIRECTORY结构体数组的起始地址
//结构体数组可能同时包含 一个EAT的IMAGE_EXPORT_DIRECTORY结构体 和 多个IAT的IMAGE_EXPORT_DIRECTORT结构体,且结构体数组最后以NULL结构体结束
//
typedef struct _IMAGE_EXPORT_DIRECTORY{
	DWORD Charateristics;
	DWORD TimeDateStamp;//creation time date stamp,创建时间戳
	WORD MajorVersion;
	WORD MinorVersion;
	DWORD Name;//address of library file name,库文件名称地址
	DWORD Base;//ordinal base
	DWORD NumberOfFunctions;//numbers of functions,实际导出函数的数量
	DWORD NumberOfNames;//number of names,导出函数有具体名称的函数个数
	DWORD AddressOfFunctions;//address of function start address array,导出函数地址数组(RVA)
	                          //4个字节数据数组,保存函数起始地址,用于填充IAT数组内容
	                          //数组元素个数 == NumberOfFunctions
	DWORD AddressofNames;//address of function name string array,函数名称地址数组(RVA)
                          //4个字节指针数组,数组元素个数 == NumberOfNames
	DWORD AddressOfNameOrdinals;//address of ordinal array,序号地址数组(RVA)
							     //2个字节数据数组,数组元素个数 == NumberOfNames
}IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

在这里插入图片描述
GetProcAddress()操作原理
9. 利用AddressOfNames成员转到“函数名称数组”
10. “函数名称数组”中存储着字符串地址,通过比较(strcmp)字符串,查找指定的函数名称(此时数组的索引称为name_index)
11. 利用AddressOfNameOrdinals成员,转到ordinal数组
12. 在ordinal数组中通过name_index查找相应的ordinal值
13. 利用addressOfFunctions成员转到“函数地址数组(EAT)
14. 在“函数地址数组”中将刚刚获得的ordinal用作数组索引,获得指定函数的起始地址
15. 指定函数的实际地址 = DLL的实际加载基址 +DLL指定函数的起始地址

**提示:**对于没有函数名称的导出函数,可以通过Ordinal查找到它们的地址。从Ordinal值重减去IMAGE_EXPORT_DIRECTORY.Base成员后得到一个值,使用该值作为“函数地址数组”的索引,即可查找到相应的函数地址。

TLS(Thread Local Storage 线程局部存储)回调函数

1、TLS回调函数的运行要先于EP代码的执行,常用于反调试;
2、TLS是各线程的独立的数据存储空间;
3、TLS回调函数,每当创建/终止进程的线程时会自动调用执行的函数,创建/终止进程的主线程也会自动调用回调函数,且其调用执行先于EP代码;

//IMAGE_OPTIONAL_HEADER32.DataDirectory[9].VirtualAddress的值就是IMAGE_EXPORT_DIRECTORY结构体数组的起始地址
typedef struct _IMAGE_TLS_DIRECTORY{
	DWORD StarAddressOfRawData;
	DWORD EndAddressOfRawData;
	DWORD AddressOfIndex;
	DWORD AddressOfCallBacks;//指向含有TLS回调函数地址(VA)的数组,该数组以NULL结束
	                         //进程启动运行时,(执行EP代码前)系统会一一调用存储在该数组中的函数
	DWORD SizeOfZeroFill;
	DWORD Characteristics;
}IMAGE_TLS_DIRECTORY, *PIMAGE_TLS_DIRECTORY;

//TLS回调函数的定义,与DLL中的DllMain()函数定义类似
typedef VOID (NTAPI *PIMAGE_TLS_CALLBACK) (PVOID DllHandle, DWORD Reason, PVOID Reserved);
//回调函数的Reason表示调用TLS回调函数的原因
#define DLL_PROCESS_ATTACH 1 //进程的主线程调用main()函数前,已经注册的TLS回调函数会先被调用执行
#define DLL_THREAD_ATTACH  2 //main()函数开始调用执行,创建用户线程前,TLS回调函数会被再次调用执行
#define DLL_THREAD_DETACH  3 //线程函数执行完毕,TLS回调函数被调用执行
#define DLL_PROCESS_DETACH 0 //main()函数终止时,TLS回调函数最后一次被调用执行

TEB(Thread Environment Block 线程环境块)

1、TEB指线程环境块,包含进程中运行线程的各种信息,进程中的每个进程都对应一个TEB结构体;
2、在不同Window OS下,TEB结构体的成员不一样;
3、offset 0x00 是NtTib成员结构体,意为“线程信息块”
4、offset 0x30 是ProcessEnvironmentBlock成员,指向PEB(Process Environment Block进程环境块),每个进程对应一个PEB结构体。

//TEB访问方法
//1、Ntdll.NtCurrentTeb()用来返回当前线程的TEB结构体的地址;
//2、FS段寄存器用来指示当前线程的TEB结构体,是根据持有SDT的索引,其实SDT位于内核内存区域,其地址存储在特殊的寄存器GDTR(Global Descriptor Table Resigter全局描述符表寄存器)中。
//FS:[0x00] = SEH起始地址
//FS:[0x18] = TEB起始地址
//FS:[0x00] = PEB起始地址
typedef struct _TEB{
	BYTE Reserved1[1952];
	PVOID Reserved2[412];
	PVOID TlsSlots[64];
	BYTE Reserved3[8];
	PVOID Reserved4[26];
	PVOID ReservedForOle;
	PVOID Reserved5[4];
	PVOID TlsExpansionSlots;
}TEB, *PTEB;

typedef struct _NT_TIB{
	struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;//指向_EXCEPTION_REGISTRATION_RECORD结构体组成的链表
	                                                     //用于Window OS的SEH
	PVOID StackBase;
	PVOID StackLimit;
	PVOID SubSystemTib;
	union{
		PVOID FiberData;
		DWORD Version;
	};
	PVOID ArbitraryUserPointer;
	struct _NT_TIB *Self;
}NT_TIB;
typedef NT_TIB *PNT_TIB;

SDT索引TEB结构体
在这里插入图片描述

PEB(Process Environment Block 线程环境块)

1、PEB是存放进程信息的结构体,尺寸非常大;
2、在不同Window OS下,PEB结构体的成员不一样;
3、offset 0x02 BeingDebugged:Uchar 指示该进程是否正在调试状态
4、offset 0x08 ImageBaseAddress:Ptr32 Void 进程被加载的ImageBase
5、offset 0x0C Ldr:Ptr32 _PEB_LDR_DATA 指向_PEB_LDR_DATA结构体指针,当模块DLL加载到进程后,通过PEB.Ldr成员可以直接获取该模块的加载地址
6、offset 0x18 ProcessHeap:Ptr32 Void 应用于反调试技术
7、offset 0x68 NtGlobalFlag:Uint4B 应用于反调试技术

//PEB访问方法
//1、直接获取PEB地址:MOV EAX, DWORD PTR FS:[30]
//2、先获取TEB地址,再通过ProcessEnvironmentBlock成员(+30H偏移)获取PEB地址
//FS:[0x00] = SEH起始地址
//FS:[0x18] = TEB起始地址
//FS:[0x30] = PEB起始地址
typedef struct _PEB{
	BYTE Reserved1[2];
	BYTE BeingDebugged;
	BYTE Reserved2[1];
	PVOID Reserved3[2];
	PPEB_LDR_DATA Ldr;
	BYTE Reserved4[104]
	PVOID Reserved5[52];
	PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
	BYTE Reserved6[128];
	PVOID Reserved7[1];
	ULONG SessionId;
}PEB, *PPEB;

//双向链表结构体
typedef struct _LIST_ENTRY{
	struct _LIST_ENTRY *Flink;
	struct _LIST_ENTRY *Blink;
}LIST_ENTRY, *pLIST_ENTRY;

//每个加载到进程中的DLL模块都有与之对应的_LDR_DATA_TABLE_ENTRY结构体,这些结构体相互链接,最终形成_LIST_ENTRY双向链表
typedef struct _LDR_DATA_TABLE_ENTRY{
	PVOID Reserved1[2];
	_LIST_ENTRY InMemoryOrderLinks;
	PVOID Reserved2[2];
	PVOID DllBase;
	PVOID EntryPoint;
	PVOID Reserved3;
	Unicode_STRING FullDllName;
	BYTE Reserved4[8];
	PVOID Reserved[3];
	union{
		ULONG CheckSum;
		PVOID Reserved6;
	}
	ULONG TimeDateStamp;
}LDR_DATA_TABLE_ENTRY, *pLDR_DATA_TABLE_ENTRY;

//_PEB_LDR_DATA结构体
typedef struct _PEB_LDR_DATA{
	UINT Length;
	BYTE Initialized;
	PVOID Sshandle;
	_LIST_ENTRY InLoadOrderModuleList;
	_LIST_ENTRY InMemoryOrderModuleList;
	_LIST_ENTRY InInitializationOrderModuleList;
	PVOID EntryInProgress;
	BYTE ShutdownInProgress;
	PVOID ShutdownThreadId;
}PEB_LDR_DATA, *pPEB_LDR_DATA;

SEH异常处理机制

1、进程正常运行时,若发生异常,OS会委托进程处理,若进程代码中存在具体的异常处理(SEH异常处理)代码,则能顺利处理相关异常,程序继续运行,但如果进程内部没有实现SEH,那么相关异常就无法处理,OS就会启动默认的异常处理机制,终止进程运行。
2、进程调试运行时,若被调试进程内部发生异常,OS会首先把异常抛给调试进程处理,调试器几乎拥有被调试者的所有权限,不仅可以运行、终止被调试者,还拥有被调试进程的虚拟内存、寄存器的读写权限(也就是说被调试者内部发生的所有异常都由调试器处理),所以调试过程中发生的所有异常都要先交给调试器管理(被调试者的SEH依据优先顺序推给调试器)。被调试者发生异常时,调试器会暂停运行,必须处理异常,完成后继续调试,可以通过直接修改异常代码、寄存器、内存等
3、将异常抛给被调试者处理:如果被调试者内部存在SEH(异常处理函数)能够处理异常,那么异常通知会发送给被调试者,由被调试者自行处理,与正常运行时的异常处理方式一样。
4、OS默认的异常处理机制:若调试器与被调试者都无法处理或故意不处理当前发生的异常,则OS的默认异常处理机制会处理它,终止被调试进程,同时结束调试。
5、SEH大量应用于压缩器、保护器、恶意程序(Malware),用来反调试。

//操作系统定义的异常
#define EXCEPTION_DATATYPE_MISALIGNMENT    (0x80000002)
#define EXCEPTION_BREAKPOINT               (0x80000003)//CPU尝试运行设置断点的代码发生异常,
                                                       //设置断点命令的汇编指令为INT3,机器指令为0xCC
                                                       //CPU运行代码,若遇到汇编指令INT3,触发EXCEPTION_BREAKPOINT异常
#define EXCEPTION_SINGLE_STEP              (0x80000004)//CPU进入单步模式后,每执行一条指令引发异常
#define EXCEPTION_ACCESS_VIOLATION         (0x80000005)//试图访问不存在或不具访问权限的内存区域
#define EXCEPTION_IN_PAGE_ERROR            (0x80000006)
#define EXCEPTION_ILLEGAL_INSTRUCTION      (0x8000001D)//CPU遇到无法解析的指令时引发异常
#define EXCEPTION_NONCONTINUABLE_EXCEPTION (0x80000025)
#define EXCEPTION_INVALID_DISPOSITION      (0x80000026)
#define EXCEPTION_ARRAY_BOUNDS_EXCEEDED    (0x8000008C)
#define EXCEPTION_FLT_DENORMAL_OPERAND     (0x8000008D)
#define EXCEPTION_FLT_DIVIDE_BY_ZERO       (0x8000008E)
#define EXCEPTION_FLT_INEXACT_RESULT       (0x8000008F)
#define EXCEPTION_FLT_INVALID_OPERATION    (0x80000090)
#define EXCEPTION_FLT_OVERFLOW             (0x80000091)
#define EXCEPTION_FLT_STACK_CHECK          (0x80000092)
#define EXCEPTION_FLT_UNDERFLOW            (0x80000093)
#define EXCEPTION_INT_DIVIDE_BY_ZEARO      (0x80000094)//除法运算,分母为0时引发异常
#define EXCEPTION_INT_OVERFLOW             (0x80000095)
#define EXCEPTION_PRIV_INSTRUCTION         (0x80000096)
#define EXCEPTION_STACK_OVERFLOW           (0x800000FD)
//SEH以链形式存在,第一个异常处理器中若未处理相关异常,就会被传递到下一个异常处理器,直到得到处理
typedef struct _EXCEPTION_REGISTERATION_RECORD{
	PEXCEPTION_REGISTERATION_RECORD Next;//指向下一个_EXCEPTION_REFISTERATION_RECORD结构体
	                                     //若Next = 0xFFFFFFFF,则表示链表最后一个节点
	PEXCEPTION_DISPOSITION Handler;//异常处理函数(异常处理器)
}EXCEPTION_REGISTERATION_RECORD, *PEXCEPTION_REGISTERATION_RECORD;

//异常处理器函数定义,是一个回调函数,返回PEXCEPTION_DISPOSITION枚举类型
PEXCEPTION_DISPOSITION _except_handler(
						EXCEPTION_RECORD *pRecord,
						EXCEPTION_REGISTERATION_RECORD *pFrame,
						CONTEXT *pContext,
						PVOID pValue);

//EXCEPTION_RECORD结构体
#define EXCEPTION_MAXIMUM_PARAMETERS 15
typedef struct _EXCEPTION_RECORD{
	DWORD ExceptionCode; //异常代码
	DWORD ExceptionFlags;
	struct _EXCEPTION_RECORD *ExceptionRecord;
	PVOID ExceptionAddress; //异常发生地址
	DWORD NumberParameters;
	ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]
}EXCEPTION_RECORD, *PEXCEPTION_RECORD;

//CONTEXT结构体的定义,用来备份CPU寄存器的值。每个线程内部都拥有1个CONTEXT结构体
//CPU暂时离开当前线程运行其他线程时,CPU寄存器的值就会保存到当前线程的CONTEXT结构体中,当CPU再次运行该线程时,使用CONTEXT中的值恢复CPU寄存器的值,并从之前暂停处继续运行。
#define MAXIMUM_SUPPORTED_EXTENSION 512
struct CONTEXT{
	DWORD ContextFlags;
	DWORD Dr0; //0x04h
	DWORD Dr1; //0x08h
	DWORD Dr2; //0x0Ch
	DWORD Dr3; //0x10h
	DWORD Dr6; //0x14h
	DWORD Dr7; //0x18h
	FLOATING_SAVE_AREA FloatSave;
	DWORD SegGs; //0x8Ch
	DWORD SegFs; //0x90h
	DWORD SegEs; //0x94h
	DWORD SegDs; //0x98h
	
	DWORD Edi; //0x9Ch
	DWORD Esi; //0xA0h
	DWORD Ebx; //0xA4h
	DWORD Edx; //0xA8h
	DWORD Ecx; //0xACh
	DWORD Eax; //0xB0h
	
	DWORD Ebp; //0xB4h
	DWORD Eip; //0xB8h
	DWORD SegCs; //0xBCh
	DWORD EFlags; //0xC0h
	DWORD Esp; //0xC4h
	DWORD SegSs; //0xC8h
	BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION ];
}
//PROCESS_INFOMATION结构体定义
typedef struct _PROCESS_INFOMATION{
	HANDLE hProcess;
	HANDLE hThread;
	DWORD dwProcessId;
	DWORD dwThreadId;
}PROCESS_INFOMATION, *LPROCESS_INFOMATION;

//PEXCEPTION_DISPOSITION枚举类型
typedef enum _EXCEPTION_DISPOSITION{
	ExceptionContinueExecution = 0, //继续执行异常代码
	ExceptionContinueSearch = 1, //运行下一个异常处理器
	ExceptionNestedException = 2, //在OS内部使用
	ExceptionCollidedUnwind = 3 //在OS内部使用
}EXCEPTION_DISPOSITION;

在这里插入图片描述

  • 异常发生时,执行异常代码的线程就会中断运行,转而运行SEH(异常处理器/异常处理函数),此时OS会把线程的CONTEXT结构体的指针传递给异常处理函数的相应参数,在CONTEXT结构体的Eip成员(偏移量:B8),将会被设置为其他地址,然后返回异常处理函数,这样,被暂停的线程就会执行新设置的EIP地址处的代码。
  • 异常处理器处理异常后返回ExceptionContinueExecution(0),从发生异常的代码处继续运行,若当前异常处理器无法处理异常,则返回ExceptionContinueSearch(1),将异常派送到SEH链的下一个异常处理器。
//SEH安装方法,也就是将自身的EXCEPTION_REGISTERATION_RECORD结构体链接到EXCEPTION_REGISTERATION_RECORD结构体链表中
//在C语言中使用__try、__except、__finally关键字就可以向代码添加SEH
//在汇编语言中添加SEH的方法
PUSH @MyHandler           //异常处理函数
PUSH DWORD PTR FS:[0]     //SEH链头
MOV DWORD PTR FS:[0], ESP //添加链表

在这里插入图片描述

PE重定位操作原理

PE重定位的基本操作原理

  1. 在应用程序中查找硬编码的地址位置,可以通过基址重定位表查找
  2. 读取值后,减去ImageBase(VA --> RVA)
  3. 加上实际加载地址(RVA --> VA)

PE加载器通过对一个IMAGE_BASE_RELOCATION结构体(位于节区.reloc)所有的TypeOffset重复上述处理,根据实际加载的内存地址修正后,将得到的值覆盖到同一位置。

基址钟定位表

基址重定位表地址位于PE头的DataDirectory数组的第六个元素(数组索引为5):IMAGE_OPTIONAL_HEADER32.DataDirectory[5].VirtualAddress的值就是IMAGE_BASE_RELOCATION结构体数组的起始地址

IMAGE_BASE_RELOCATION结构体

//若TypeOffset值为0,则表明一个IMAGE_BASE_RELOCATION结构体结束
//重定位表数组以NULL结构体结束
typedef struct _IMAGE_BASE_RELOCATION{
	DWORD VirtualAddress;//基准地址,指示TypeOffset数组偏移地址的基准地址(起始地址)
	DWORD SizeOfBlock;//指重定位块的大小,以数组形式存在,块末端显示为0
//	WORD TypeOffset[1];//该数组不是结构体成员,注释形式存在
                      //表示该结构体之下会出现WORD类型数组,并且该数组元素的值就是硬编码在程序中的地址偏移
                      //TypeOffset值为2个字节,是由4位Type和12位的Offset合成
                      //其中高4位,PE文件常见值为3(IMAGE_REL_BASE_HIGHLOW),64位的PE+常见值为A(IMAGE_REL_BASE_DIR64)
                      //TypeOffset低12位是真正的位移,基于VirtualAddress的偏移
                      //程序中硬编码地址的偏移(RVA) = VirtualAddress + (TypeOffset & 0x0FFF)
}IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED *PIMAGE_BASE_RELOCATION;

#define IMAGE_REL_BASE_ABSOLUTE       0
#define IMAGE_REL_BASE_HIGH           1
#define IMAGE_REL_BASE_LOW            2
#define IMAGE_REL_BASE_HIGHLOW        3
#define IMAGE_REL_BASE_HIGHADJ        4
#define IMAGE_REL_BASE_MIPS_JMPADDR   5
#define IMAGE_REL_BASE_MIPS_JMPADDR16 9
#define IMAGE_REL_BASE_IA64_IMM64     9
#define IMAGE_REL_BASE_DIR64          10

在这里插入图片描述

基址重定位表的分析方法

1、根据重定位表,查找程序中需要重定位的位置RVA = VirtualAddress + (TypeOffset & 0x0FFF)
2、读取硬编码地址的值后,减去ImageBase值(VR --> RVA)
3、加上实际加载地址(RVA --> VA)
4、将得到的值覆盖到重定位的位置上
5、对一个IMAGE_BASE_RELOCATION结构体的所有TypeOffset重复步骤1~4,直至TypeOffset的值为0
6、对PE文件中的所有IMAGE_BASE_RELOCATION结构体(衔接上一个结构体的TypeOffset的后面),重复步骤1~5,直至结构体为NULL(即最后一个结构体的TypeOffset为0时,后面连续有8个字节为0)
在这里插入图片描述

工具

PEView.exe

PEView.exe是一个分析PE文件的应用程序

UPack

Upack(Ultimate PE压缩器)是一款PE文件的运行时压缩器

Stud_PE

Stud_PE是一款分析PE文件的应用软件:http://www.cgsoftlabs.ro

DebugView

可以用来捕获并显示系统中运行的进程输出的所有调试字符串:https://technet.microsoft.com/en-us

PE Tools

一款功能强大的PE文件编辑工具,具有进程内存转储、PE文件头编辑、PE重建等功能,并且支持插件,带有插件编写示例:http://petools.org

CFF Explorer(重点使用)

提供多样化的功能,并且支持PE32+文件格式:https://ntcore.com,还提供PE编辑器、PE重建、RVA<–>RAW转换器、反汇编等综合功能。

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值