date: 2023/12/19

概述:x86系统采用动态的方式构建SEH结构,相比而言x64系统下采用静态的方式处理SEH结构,它保存在PE文件中,通常在.pdata区段。因此本文的例子采用x64编译过的程序。异常表在资源表的后面。

0x01 异常表解析

数据目录表的第四个元素指向异常表,RVA指向的是一个IMAGE_IA64_RUNTIME_FUNCTION_ENTRY的结构体,本例的RVA是0xA000(也就是.pdata段的起始位置),转换成offset是0x7a00。它的结构体是:

typedef struct _IMAGE_IA64_RUNTIME_FUNCTION_ENTRY {
    DWORD BeginAddress;      //与SEH相关代码的起始偏移地址
    DWORD EndAddress;        //与SEH相关代码的末尾偏移地址
    DWORD UnwindInfoAddress; //指向描述上面两个字段之间代码异常信息的UNWIND_INFO
} IMAGE_IA64_RUNTIME_FUNCTION_ENTRY, *PIMAGE_IA64_RUNTIME_FUNCTION_ENTRY;

​ BeginAddress 与 EndAddress之间,是异常处理函数的内容。UnwindInfoAddress 指向的位置是用来描述 BeginAddress 与 EndAddress 之间的代码异常属性信息的UNWIND_INFO。UNWIND_INFO 也叫作异常展开信息,此结构用来描述堆栈指针的记录属性与寄存器中保存的地址属性,它的结构体如下:

struct _UNWIND_INFO {
   UBYTE Version:3;        
   UBYTE Flags:5; 
   UBYTE SizeOfProlog;        
   UBYTE CountOfCodes;   
   UBYTE FrameRegister:4;
   UBYTE FrameOffset:4;
   UNWIND_CODE UnwindCode[1];
   union {
        // If (Flags & UNW_FLAG_EHANDLER)
       OPTIONAL ULONG ExceptionHandler;//异常/终止函数的映像相对地址指针
       // Else if (Flags & UNW_FLAG_CHAININFO)
       OPTIONAL ULONG FunctionEntry;//展开信息链的映像相对地址指针
   };
   // If (Flags & UNW_FLAG_EHANDLER)
   ULONG ExceptionData[1];//异常处理程序的数据
} UNWIND_INFO, *PUNWIND_INFO;

Version:异常展开信息的版本号,一般为0x001

Flags:共有四种标志:

  1. 当它为0x0的时候表示UNW_FLAG_NHANDLER,没有异常处理函数。
  2. 当它为0x1的时候表示UNW_FLAG_EHANDLER,有异常处理函数。
  3. 当它为0x2的时候表示UNW_FLAG_UHANDLER,有系统默认的处理函数。
  4. 当它为0x4的时候表示UNW_FLAG_CHAININFO,表示FunctionEntry指向的是前一个RUNTIME_FUNCTION的RAV。

SizeOfProlog:函数起始部分字节的长度

CountOfCodes:UNWIND_INFO结构包含的UNWIND_CODE结构数

FrameRegister:寄存器帧指针,为0则指定函数不使用框架

FrameOffset:若上面字段不为0,表示函数偏移

UnwindCode:指定永久性寄存器与RSP的数组项目数

ExceptionHandler:异常句柄

FunctionEntry:展开信息链(函数)的映像相对地址指针(如果设置了UNW_FLAG_CHAININFO标识)

ExceptionData:异常处理程序的数据

查找SEH表

以下代码说明:打开一个进程模块,然后查找 .pdata 段,依次遍历异常表。

const char	gPDataSectionName[] = ".pdata";
 
typedef struct _ModuleSectionInfo_T {
	VOID*	pBase;
	UINT	Size;
}ModuleSectionInfo_T;
 
// 查找 .pdata 段地址和大小
BOOL getModulePdataSection(
    __in HANDLE hProcess, 
    __in VOID * pModule, 
    __out ModuleSectionInfo_T * pModuleSectionInfo)
{
	IMAGE_DOS_HEADER		ImageDosHeader;
	IMAGE_NT_HEADERS		ImageNtHeaders;
	IMAGE_SECTION_HEADER*	pImageSectionHeader;
	UINT					SectionIdx;
	IMAGE_SECTION_HEADER	ImageSectionHeader;
	UCHAR*					pBase = (UCHAR*)pModule;
	BOOL					bResult = FALSE;
 
	if (!ReadProcessMemory(hProcess, pBase, &ImageDosHeader, sizeof(ImageDosHeader), NULL)) goto End;
	if (ImageDosHeader.e_magic != IMAGE_DOS_SIGNATURE) goto End;
	if (!ReadProcessMemory(hProcess, pBase + ImageDosHeader.e_lfanew, &ImageNtHeaders, sizeof(ImageNtHeaders), NULL)) goto End;
	if (ImageNtHeaders.Signature != IMAGE_NT_SIGNATURE) goto End;
	//
	// For 64bits, add the size of IMAGE_NT_HEADERS64 to its own base to get the base of the IMAGE_SECTION_HEADER table
	//
#ifdef _WIN64
#pragma warning(push)
#pragma warning(disable:4127)
	if (SQLPLUGIN_IS_WOW64 == TRUE) pImageSectionHeader = (IMAGE_SECTION_HEADER*)((UINT_PTR)pBase + ImageDosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS));
#pragma warning(pop)
	else
#endif		
		pImageSectionHeader = (IMAGE_SECTION_HEADER*)((UINT_PTR)pBase + ImageDosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS32));
 
	for (SectionIdx = 0; SectionIdx < ImageNtHeaders.FileHeader.NumberOfSections; SectionIdx++)
	{
		if (!ReadProcessMemory(hProcess, &pImageSectionHeader[SectionIdx], &ImageSectionHeader, sizeof(ImageSectionHeader), NULL)) goto End;
		const char* SectionName = (const char*)ImageSectionHeader.Name;
		if (strncmp(SectionName, gPDataSectionName, sizeof(gPDataSectionName)) == 0)
		{
			pModuleSectionInfo->pBase = pBase + ImageSectionHeader.VirtualAddress;
			pModuleSectionInfo->Size = ImageSectionHeader.Misc.VirtualSize;
			bResult = TRUE;
			break;
		}
	}
End:
	return (bResult);
}
 
 
BOOL getFunWithSepAddr(MODULEENTRY32 me32, ULONGLONG InputAddr, ULONGLONG& RetAddr)
{
	BOOL bRet = FALSE;
	ModuleSectionInfo_T ModuleSectionInfo;
	ULONGLONG ulModuleBase = (ULONGLONG)me32.modBaseAddr;
	ULONGLONG ulModuleSize = (ULONGLONG)me32.modBaseSize;
 
	if (!getModulePdataSection(m_hProcess, me32.hModule, &ModuleSectionInfo)) goto End;
 
	PVOID								pCandidate;
	IMAGE_IA64_RUNTIME_FUNCTION_ENTRY	RuntimeEntry;
 
#pragma warning(push)
#pragma warning(disable:4305)
	pCandidate = ModuleSectionInfo.pBase;
#pragma warning(pop)
	unsigned int iCount = 0;
 
    // 这里遍历的就是 SEH 表
	for (iCount = 0; iCount < ModuleSectionInfo.Size; iCount += sizeof(IMAGE_IA64_RUNTIME_FUNCTION_ENTRY))
	{
		if (!ReadProcessMemory(m_hProcess, pCandidate, &RuntimeEntry, sizeof(IMAGE_IA64_RUNTIME_FUNCTION_ENTRY), NULL))	goto NextCandidate;
 
		ULONGLONG begin = (ULONGLONG)ulModuleBase + RuntimeEntry.BeginAddress;
		ULONGLONG end = (ULONGLONG)ulModuleBase + RuntimeEntry.EndAddress;
 
		if (IsInThisFun(iCount, InputAddr, begin, end))
		{
			bRet = TRUE;
			RetAddr = begin;
			break;
		}
 
	NextCandidate:
		pCandidate = (PVOID)((ULONGLONG)pCandidate + sizeof(IMAGE_IA64_RUNTIME_FUNCTION_ENTRY));
	}
End:
	return bRet;
}