CImage辅助类

本文详细介绍了PE文件的加载过程,包括PE文件的结构解析、内存映射、重定位处理及导入表解析等关键技术。文章深入探讨了如何利用C++进行PE文件的加载和解析,提供了丰富的代码实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#include "Image.h"
#include<Shlwapi.h>
#pragma comment(lib,"shlwapi.lib")
CImage::CImage()
{
	m_hFile = INVALID_HANDLE_VALUE;
	m_hModule = NULL;
	m_pDosHeader = NULL;
	m_pFileHeader = NULL;
	m_pRelocTable = NULL;
	m_pSecHeader = NULL;
	m_pExportDir = NULL;
	m_pOptHeader = NULL;

	SYSTEM_INFO sysinfo;
	GetSystemInfo(&sysinfo);
	m_dwPageSize = sysinfo.dwPageSize;
}



PBYTE CImage::LoadImage(HANDLE hFile, BOOL bDoReloc, ULONG_PTR RelocBase, BOOL bDoImport)
{
	WORD i = 0;
	BYTE* pMemory = NULL;
	BYTE* MappedBase = NULL;
	PIMAGE_SECTION_HEADER pTmpSecHeader = NULL;
	BOOL bResult = FALSE;
	//一般PE文件大小不会超过4G
	DWORD dwFileSize = 0;
	DWORD dwIoCnt = 0;


	__try
	{
		m_hFile = hFile;
		//获取文件大小
		dwFileSize = GetFileSize(m_hFile, NULL);
		if (dwFileSize == 0)
		{
			lstrcpy(m_szErrorMsg, TEXT("文件大小为0!"));
			__leave;
		}
		//读取PE头
		DWORD dwSizeToRead = (dwFileSize > PEHEADER_SIZE)? PEHEADER_SIZE : dwFileSize;
		ZeroMemory(m_HeaderData, PEHEADER_SIZE);
		//读dwSizeToRead个字节,也就是读取PE头
		bResult = ReadFile(m_hFile, m_HeaderData, dwSizeToRead, &dwIoCnt, NULL);

		if (!bResult)
		{
			FormatErrorMsg(TEXT("读取文件失败!"), GetLastError());
			__leave;
		}
		if (!VerifyImage(m_HeaderData))
		{
			lstrcpy(m_szErrorMsg, TEXT("不是有效的PE映像"));
			__leave;
		}
		
		//解析各个PE头部结构
		InitializePEHeaders(m_HeaderData);


		pTmpSecHeader = m_pSecHeader;
		//开始申请内存,为避免麻烦,这里直接申请可读可写可执行的内存
		pMemory = m_hModule = (BYTE*)VirtualAlloc(NULL, m_TotalImageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
		if (m_hModule == NULL)
		{
			lstrcpy(m_szErrorMsg, TEXT("内存不足,申请内存失败!"));
			__leave;
		}
		//先拷贝PE头到申请的内存(MS-DOS头、PE文件头、区块表)
		memcpy(pMemory, m_HeaderData, m_pOptHeader->SizeOfHeaders);
		pMemory += GetAlignedSize(m_pOptHeader->SizeOfHeaders, m_pOptHeader->SectionAlignment);

		LARGE_INTEGER liFileOffset;
		for (int i = 0; i < m_SectionCnt; i++)
		{
			//得到文件偏移
			liFileOffset.QuadPart = pTmpSecHeader->PointerToRawData;
			//设置文件指针指向第一个区块
			bResult = SetFilePointerEx(m_hFile, liFileOffset, NULL, FILE_BEGIN);
			if (!bResult)
			{
				FormatErrorMsg(TEXT("设置文件读写位置失败!"), GetLastError());
				__leave;
			}

			//读取各个节 
			bResult = ReadFile(m_hFile, pMemory, pTmpSecHeader->SizeOfRawData, &dwIoCnt, NULL);
			if (!bResult)
			{
				FormatErrorMsg(TEXT("读取文件失败!"), GetLastError());
				__leave;
			}
			//pMemory指向下一个节存入的缓冲区的地址
			pMemory += GetAlignedSize(pTmpSecHeader->Misc.VirtualSize, m_pOptHeader->SectionAlignment);
			//pTmpSecHeader指向下一个区块表
			pTmpSecHeader++;
		}

		//重新解析PE头
		InitializePEHeaders(m_hModule);
		//开始处理重定位数据
		if (bDoReloc)
		{
			//如果RelocBase为0,则按实际加载位置进行重定位
			ULONG_PTR BaseToReloc = (RelocBase == 0) ? (DWORD)m_hModule : RelocBase;
			ProcessRelocTable(BaseToReloc);
		}

		//处理导入表
		if (bDoImport)
		{
			ProcessImportTable();
		}
		bResult = TRUE;//加载成功
	}
	__finally
	{
		if (!bResult)
		{
			if (m_hFile != INVALID_HANDLE_VALUE)
			{
				CloseHandle(m_hFile);
				m_hFile = INVALID_HANDLE_VALUE;
			}
		}
	}
	return m_hModule;
}

//以文件路径方式打开PE
PBYTE CImage::LoadImage(TCHAR* szPEPath, BOOL bDoReloc, ULONG_PTR RelocBase, BOOL bDoImport)
{
	//保存PE路径
	lstrcpy(m_szPEPath, szPEPath);
	//以只读方式打开文件
	m_hFile = CreateFile(szPEPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	if (m_hFile == INVALID_HANDLE_VALUE)
	{
		FormatErrorMsg(TEXT("打开文件失败!"), GetLastError());
		return NULL;
	}
	return LoadImage(m_hFile, bDoReloc, RelocBase, bDoImport);
}

//释放PE
VOID CImage::FreePE()
{
	VirtualFree(m_hModule, 0, MEM_RELEASE);
	m_hModule = NULL;
}
//得到指定大小对齐后的大小
DWORD CImage::GetAlignedSize(DWORD theSize, DWORD Alignment)
{
	DWORD dwAlignedVirtualSize = 0;
	dwAlignedVirtualSize = ALIGN_SIZE_UP(theSize, Alignment);
	return dwAlignedVirtualSize;//返回对齐后的大小
}
//得到指定指针对齐后的指针
ULONG_PTR CImage::GetAlignedPointer(ULONG_PTR uPointer, DWORD Alignment)
{
	DWORD dwAlignedAddress = 0;
	dwAlignedAddress = ALIGN_SIZE_UP(uPointer, Alignment);
	return dwAlignedAddress;//对齐后的指针
}

DWORD CImage::_GetProcAddress(PBYTE pModule, char* szFuncName)
{
	//自己实现GetProcAddress
	DWORD retAddr = 0;
	DWORD* namerav, * funrav;
	DWORD cnt = 0;
	DWORD max, min, mid;
	WORD* nameOrdinal;
	WORD nIndex = 0;
	int cmpresult = 0;
	char* ModuleBase = (char*)pModule;
	char* szMidName = NULL;
	PIMAGE_DOS_HEADER pDosHeader;
	PIMAGE_NT_HEADERS pNtHeader;
	PIMAGE_OPTIONAL_HEADER pOptHeader;
	PIMAGE_EXPORT_DIRECTORY pExportDir;

	if (ModuleBase == NULL)
		return 0;
	pDosHeader = (PIMAGE_DOS_HEADER)ModuleBase;
	if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
		return 0;
	pNtHeader = (PIMAGE_NT_HEADERS)(ModuleBase + pDosHeader->e_lfanew);
	if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)
		return 0;
	
	pOptHeader = &(pNtHeader->OptionalHeader);
	pExportDir = (PIMAGE_EXPORT_DIRECTORY)(ModuleBase + pOptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
	//得到函数名称指针数组的虚拟地址
	namerav = (DWORD*)(ModuleBase + pExportDir->AddressOfNames);
	//函数地址数组
	funrav = (DWORD*)(ModuleBase + pExportDir->AddressOfFunctions);
	//得到输出序列号数组的虚拟地址
	nameOrdinal = (WORD*)(ModuleBase + pExportDir->AddressOfNameOrdinals);
	//如果要查询的函数地址小于64KB,那么输入的函数地址作为序列号使用
	if ((DWORD)szFuncName < 0x0000FFFF)
	{
		//通过序列号得到输出函数地址
		retAddr = (DWORD)(ModuleBase + funrav[(WORD)szFuncName]);
	}
	else
	{
		//二分法查找
		max = pExportDir->NumberOfFunctions;
		min = 0;
		mid = (max + min) / 2;
		while (min<max)
		{
			szMidName = ModuleBase + namerav[mid];
			cmpresult = strcmp(szFuncName, szMidName);
			if (cmpresult < 0)
			{
				//比中值小,则取中值-1为最大值
				max = mid - 1;
			}
			else if(cmpresult>0)
			{
				//比中值大,则取中值+1为最小值
				min = mid + 1;
			}
			else
			{
				break;
			}
			mid = (max + min) / 2;
		}

		//如果找到就得到了该函数地址
		if (strcmp(szFuncName, ModuleBase + namerav[mid]) == 0)
		{
			//得到序列号
			nIndex = nameOrdinal[mid];
			//通过序列号得到函数地址
			retAddr = (DWORD)(ModuleBase + funrav[nIndex]);
		}
	}
	return retAddr;
}
//得到整个映射文件的大小
DWORD CImage::GetTotalImageSize(DWORD Alignment)
{
	DWORD TotalSize = 0;
	DWORD tmp = 0;
	PIMAGE_SECTION_HEADER pTmpSecHeader = m_pSecHeader;
	TotalSize += GetAlignedSize(m_pOptHeader->SizeOfHeaders, Alignment);
	for (int i = 0; i < m_SectionCnt; i++)
	{
		tmp = GetAlignedSize(pTmpSecHeader->Misc.VirtualSize, Alignment);
		TotalSize += tmp;
		pTmpSecHeader++;
	}
	return TotalSize;
}
//虚拟RVA转换为文件偏移
DWORD CImage::Rav2Raw(DWORD VirtualAddr)
{
	DWORD RawAddr = 0;
	//如果给的虚拟地址小于文件头的大小,那么虚拟RVA等于文件偏移
	if (VirtualAddr < m_pOptHeader->SizeOfHeaders)
	{
		RawAddr = VirtualAddr;
		return RawAddr;
	}

	//得到虚拟RVA位于哪个区块,并得到该区块头
	PIMAGE_SECTION_HEADER pTmpSecHeader = LocateSectionByRVA(VirtualAddr);
	if (pTmpSecHeader != NULL)
	{
		//FileOffset = RVA(不用怀疑就是RVA,相对于映射基地址的RVA) - 区块Rva + Raw
		RawAddr = VirtualAddr - pTmpSecHeader->VirtualAddress + pTmpSecHeader->PointerToRawData;
	}
	return RawAddr;
}
//文件偏移转换为虚拟RVA
DWORD CImage::Raw2Rav(DWORD RawAddr)
{
	DWORD RavAddr = 0;
	//同上面一样,小于PE头的大小,代表文件偏移等于虚拟RVA
	if (RawAddr < m_pOptHeader->SizeOfHeaders)
	{
		RavAddr = RawAddr;
		return RavAddr;
	}
	PIMAGE_SECTION_HEADER pTmpSecHeader = LocateSectionByRawOffset(RawAddr);
	if (pTmpSecHeader != NULL)
	{
		//文件偏移转换为虚拟RVA
		RavAddr = RawAddr - pTmpSecHeader->PointerToRawData + pTmpSecHeader->VirtualAddress;
	}
	return RavAddr;
}

VOID CImage::FormatErrorMsg(CONST TCHAR* szPrompt, DWORD ErrCode)
{
	LPVOID lpMsgBuf;
	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		ErrCode,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR)&lpMsgBuf,
		0,
		NULL
	);
	wsprintf(m_szErrorMsg, TEXT("%s 错误代码:%d 原因:%s"), szPrompt, ErrCode, (LPCTSTR)lpMsgBuf);
	LocalFree(lpMsgBuf);
}
VOID CImage::Cleanup()
{
	if (m_hFile != INVALID_HANDLE_VALUE);
	{
		CloseHandle(m_hFile);
		m_hFile = INVALID_HANDLE_VALUE;
	}
	if (m_hModule != NULL)
	{
		FreePE();
	}
}

VOID CImage::InitializePEHeaders(PBYTE pBase)
{
	//解析各个PE头部结构
	m_hModule = pBase;
	m_pDosHeader = (PIMAGE_DOS_HEADER)pBase;
	m_pNtHeaders = (PIMAGE_NT_HEADERS)(pBase + m_pDosHeader->e_lfanew);
	m_pFileHeader = &m_pNtHeaders->FileHeader;
	m_SectionCnt = m_pFileHeader->NumberOfSections;
	m_pOptHeader = &m_pNtHeaders->OptionalHeader;
	m_pRelocTable = &(m_pOptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]);
	m_pSecHeader = (PIMAGE_SECTION_HEADER)((BYTE*)m_pOptHeader + sizeof(IMAGE_OPTIONAL_HEADER));
	m_dwEntryPoint = m_pOptHeader->AddressOfEntryPoint;
	m_TotalImageSize = m_pOptHeader->SizeOfImage;
	m_ImageBase = (ULONG_PTR)m_pOptHeader->ImageBase;

	//导入表
	m_pImpDataDir = &m_pOptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
	//因为导入表可能会被修改,所以先保存旧的导入表数据
	m_OldImpDir.VirtualAddress = m_pImpDataDir->VirtualAddress;
	m_OldImpDir.Size = m_pImpDataDir->Size;
	if (m_pImpDataDir->VirtualAddress != NULL)
	{
		m_pImportDesp = (PIMAGE_IMPORT_DESCRIPTOR)(pBase + m_pImpDataDir->VirtualAddress);
	}

	//导出表
	m_pExpDataDir = &m_pOptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
	if (m_pExpDataDir->VirtualAddress != NULL)
	{
		m_pExportDir = (PIMAGE_EXPORT_DIRECTORY)(pBase + m_pExpDataDir->VirtualAddress);
	}
}
//处理重定位
VOID CImage::ProcessRelocTable(ULONG_PTR RelocBase)
{
	PIMAGE_BASE_RELOCATION pRelocBlock = NULL;
	if (m_pRelocTable->VirtualAddress != NULL)
	{
		//得到重定位数据块的虚拟地址
		pRelocBlock = (PIMAGE_BASE_RELOCATION)(m_hModule + m_pRelocTable->VirtualAddress);
		do
		{
			//处理一个接一个的重定位块,最后一个重定位块以RVA = 0结束
			//需要重定位的个数,是本块的大小减去块头的大小,结果是以DWORD表示的大小
			//而重定位数据是16位的,也就是2字节,除以2就是数据项个数
			int numofReloc = (pRelocBlock->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;
			//重定位数据是16位的
			WORD offset = 0;
			//指向重定位数据指针数组
			WORD* pRelocData = (WORD*)((BYTE*)pRelocBlock + sizeof(IMAGE_BASE_RELOCATION));
			for (int i = 0; i < numofReloc; i++)
			{
				ULONG_PTR* RelocAddress = 0;//需要重定位的地址
#ifdef _WIN64
				WORD RelocFlag = IMAGE_REL_BASED_DIR64;
#else
				WORD RelocFlag = IMAGE_REL_BASED_HIGHLOW;
#endif
				//IMAGE_REL_BASED_DIR64
				//重定位的高4位是重定位类型
				if (((*pRelocData) >> 12) == RelocFlag)//判断重定位类型是否为IMAGE_REL_BASED_HIGHLOW,x86
				{
					//计算需要进行重定位的地址
					//高4位清零得到小偏移
					offset = (*pRelocData) & 0xFFF;
					//重定位数据的低12位再加上本重定位块头的RAV即真正需要重定位的数据的RAV,RAV+IMAGEBASE得到需要重定位数据指针的虚拟地址
					RelocAddress = (ULONG_PTR*)(m_hModule + pRelocBlock->VirtualAddress + offset);
					//m_pOptHeader->ImageBase为程序默认的载入地址,重定位的数据等于 默认值+(重定位基地址-默认基地址)
					*RelocAddress = *RelocAddress + RelocBase - m_pOptHeader->ImageBase;
				}
				pRelocData++;
			}
			//指向下一个重定位块
			pRelocBlock = (PIMAGE_BASE_RELOCATION)((char*)pRelocBlock+pRelocBlock->SizeOfBlock);
		} while (pRelocBlock->VirtualAddress);//最后一个重定位块以RVA=0结束
	}
}
TCHAR* AsciiToUnicode(char* str)
{
	DWORD dwNum = 0;
	dwNum = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
	TCHAR* pwHostName = new TCHAR[dwNum];
	MultiByteToWideChar(CP_ACP, 0, str, -1, pwHostName, dwNum);
	return pwHostName;
}
//处理导入表
BOOL CImage::ProcessImportTable()
{
	BOOL bResult = TRUE;
	TCHAR szPreDirectory[MAX_PATH] = { 0 };
	TCHAR szCurDirectory[MAX_PATH] = { 0 };
	TCHAR szPrompt[256] = { 0 };
	PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor = m_pImportDesp;
	PIMAGE_THUNK_DATA NameThunk = NULL, AddrThunk = NULL;
	PIMAGE_IMPORT_BY_NAME pImpName = NULL;
	HMODULE hMod = NULL;
	TCHAR szImpModName[MAX_PATH] = {0};
	if (pImportDescriptor == NULL)
	{
		//无导入表,不需要处理
		return TRUE;
	}
	//更改当前路径,否则加载某些第三方DLL时将找不到模块
	//得到本进程的目录给szPreDirectory
	GetCurrentDirectory(MAX_PATH, szPreDirectory);
	//把加载的模块的路径赋值给szCurDirectory
	lstrcpy(szCurDirectory, m_szPEPath);
	/*
		PathRemoveFileSpec 函数的作用:将路径末尾的文件名和反斜杠去掉。
		我们可以获取EXE文件自身所在的文件夹
	*/
	//得到加载DLL的目录
	PathRemoveFileSpec(szCurDirectory);
	//(临时)设置工作目录为加载DLL的工作目录
	SetCurrentDirectory(szCurDirectory);
	//遍历IAT表,然后处理每一项
	while (pImportDescriptor->Name&&pImportDescriptor->OriginalFirstThunk)
	{
		//得到模块名,并转换为UNICODE字符
		char* tmp = (char*)m_hModule + pImportDescriptor->Name;
		TCHAR* Ttmp = AsciiToUnicode(tmp);
		lstrcpy(szImpModName, Ttmp);
		delete[] Ttmp;

		//加载模块
		hMod = LoadLibrary(szImpModName);
		if (hMod == NULL)
		{
			swprintf(szPrompt, TEXT("加载导入表模块 %s 失败!"), szImpModName);
			FormatErrorMsg(szImpModName, GetLastError());
			return FALSE;
		}
		//得到INT表
		NameThunk = (PIMAGE_THUNK_DATA)(m_hModule + (ULONG)pImportDescriptor->OriginalFirstThunk);
		//IAT表
		AddrThunk = (PIMAGE_THUNK_DATA)(m_hModule + (ULONG)pImportDescriptor->FirstThunk);
		//遍历IAT表
		while (NameThunk->u1.AddressOfData)
		{
			//处理IAT表
			bResult = SnapThunk(hMod, szImpModName, m_hModule, NameThunk, AddrThunk);
			if (!bResult)
			{
				bResult = FALSE;
				break;
			}
			NameThunk++;
			AddrThunk++;
		}
		if (!bResult)
		{
			break;
		}
		//指向下一个导入IID
		pImportDescriptor++;
	}
	//恢复工作目录
	SetCurrentDirectory(szPreDirectory);
	return bResult;
}

BOOL CImage::SnapThunk(HMODULE hImpMode, TCHAR* szImpModeName, PBYTE ImageBase, PIMAGE_THUNK_DATA NameThunk, PIMAGE_THUNK_DATA AddrThunk)
{
	BOOL bResult = FALSE;
	PIMAGE_IMPORT_BY_NAME pImpName = NULL;
	DWORD dwFunAddr = 0;
	ULONG Ordinal = 0;
	//如果ul的最高位为1,就代表函数是以序号方式输入的
	if (NameThunk->u1.AddressOfData & IMAGE_ORDINAL_FLAG32)
	{
		//得到函数序号
		Ordinal = IMAGE_ORDINAL(NameThunk->u1.Ordinal);
		//通过序号得到函数地址
		dwFunAddr = (DWORD)GetProcAddress(hImpMode, (LPCSTR)Ordinal);
		if (dwFunAddr == 0)
		{
			swprintf(m_szErrorMsg, TEXT("无法在导入模块%S中定位导入函数:%d(序号)"), szImpModeName, Ordinal);
		}
	}
	//如果ul的最高位为0,就代表函数是以名称方式导入的
	else
	{
		pImpName = (PIMAGE_IMPORT_BY_NAME)(m_hModule + (ULONG)NameThunk->u1.AddressOfData);
		dwFunAddr = (DWORD)GetProcAddress(hImpMode, (LPCSTR)pImpName->Name);
		if (dwFunAddr == 0)
		{
			swprintf(m_szErrorMsg, TEXT("无法在导入模块%S中定位导入函数:%s(名称)"), szImpModeName, pImpName->Name);
		}
	}
	//如果得到的地址存在就写入对应的IAT
	if (dwFunAddr != 0)
	{
		AddrThunk->u1.Function = dwFunAddr;
		bResult = TRUE;
	}

	return bResult;
}

//根据PE头文件,简单判断PE文件的有效性
BOOL CImage::VerifyImage(PVOID pBase)
{
	//解析各个PE头部结构
	m_pDosHeader = (PIMAGE_DOS_HEADER)pBase;
	if (m_pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
	{
		return FALSE;
	}

	m_pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)pBase + m_pDosHeader->e_lfanew);
	if (m_pNtHeaders->Signature != IMAGE_NT_SIGNATURE)
	{
		return FALSE;
	}
	return TRUE;
}

LPWSTR CImage::GetErrorMsg(TCHAR* szBuf, int BufSize)
{
	int len = lstrlen(m_szErrorMsg);
	if (len <= BufSize)
	{
		lstrcpy(szBuf, m_szErrorMsg);
		return szBuf;
	}
	return NULL;
}

//根据相对虚拟地址查找所在的节
PIMAGE_SECTION_HEADER CImage::LocateSectionByRVA(DWORD dwRVA)
{
	PIMAGE_SECTION_HEADER pTemp = m_pSecHeader;
	for (int i = 0; i < m_SectionCnt; i++)
	{
		if (pTemp->VirtualAddress <= dwRVA && dwRVA < (pTemp->VirtualAddress + pTemp->Misc.VirtualSize))
		{
			return pTemp;
		}
		pTemp++;
	}
	return NULL;
}
//根据文件偏移确定所在的节
PIMAGE_SECTION_HEADER CImage::LocateSectionByRawOffset(DWORD dwRawOffset)
{
	PIMAGE_SECTION_HEADER pTemp = m_pSecHeader;
	for (int i = 0; i < m_SectionCnt; i++)
	{
		if (pTemp->PointerToRawData <= dwRawOffset&&dwRawOffset<(pTemp->PointerToRawData+pTemp->SizeOfRawData))
		{
			return pTemp;
		}
		pTemp++;
	}
	return NULL;
}

//计算某个节按虚拟地址对齐后内存中的空隙大小
DWORD CImage::GetSectionVirtualPaddingSize(PIMAGE_SECTION_HEADER pSecHeader)
{
	DWORD AlignedSize = GetAlignedSize(pSecHeader->Misc.VirtualSize, m_pOptHeader->SectionAlignment);
	return AlignedSize - pSecHeader->Misc.VirtualSize;
}

//计算某个节按虚拟地址对齐后磁盘中的空隙大小
//VirtualSize和RawSize不确定那个比较大的情况
DWORD CImage::GetSectionPhysialPaddingSize(PIMAGE_SECTION_HEADER pSecHeader)
{
	DWORD dwPaddingSize = 0;
	if (pSecHeader->Misc.VirtualSize < pSecHeader->SizeOfRawData)
	{
		//节的内存大小小于文件大小
		/*
		.text name
		*/
		dwPaddingSize = pSecHeader->SizeOfRawData - pSecHeader->Misc.VirtualSize;
	}
	else
	{
		//节的内存大小大于等于文件中的大小,则认为不存在空隙(因为不好计算)
		dwPaddingSize = 0;
	}
	return dwPaddingSize;
}

//默认情况下是以只读方式打开目标文件的
BOOL CImage::MakeFileHandleWritable()
{
	BOOL bResult = FALSE;
	HANDLE hNew = INVALID_HANDLE_VALUE;
	//OpenProcess 函数用来打开一个已存在的进程对象,并返回进程的句柄。
	HANDLE hProc = OpenProcess(PROCESS_DUP_HANDLE, FALSE, GetCurrentProcessId());
	if (hProc = NULL)
	{
		return FALSE;
	}
	bResult = DuplicateHandle(
		hProc,
		m_hFile,
		hProc,
		&hNew,
		GENERIC_READ,
		FALSE,
		0
	);
	if (bResult)
	{
		CloseHandle(m_hFile);
		m_hFile = hNew;
	}
	else
	{
		FormatErrorMsg(TEXT("更改句柄权限失败"), GetLastError());
	}
	CloseHandle(hProc);
	return bResult;
}

//解析当前内存中的PE结构
VOID CImage::AttachToMemory(PVOID pMemory)
{
	if (pMemory != NULL)
		InitializePEHeaders((PBYTE)pMemory);
}

//解析其他进程中的PE结构
BOOL CImage::AttachToProcess(HANDLE hProc, PVOID ProcessImageBase)
{
	BOOL bResult = FALSE;
	SIZE_T dwIoCnt = 0;
	m_hProc = hProc;
	m_ImageBase = (ULONG_PTR)ProcessImageBase;
	bResult = ReadProcessMemory(m_hProc, (LPVOID)m_ImageBase, m_HeaderData, 0x1000, &dwIoCnt);
	if (!bResult)
	{
		FormatErrorMsg(TEXT("ReadProcessMemory失败!"), GetLastError());
		return FALSE;
	}
	//初始化PE结构
	InitializePEHeaders(m_HeaderData);
	return bResult;
}

//在文件中添加一个新区块并返回新节的数据
PIMAGE_SECTION_HEADER CImage::AddNewSectionToFile(char* szSectionName, DWORD SectionSize)
{
	//pNewSecHeader指向区块表的最后一项+1,把新区块的IMAGE_SECTION_HEADER添加到区块表的末尾(给区块表增加一项)
	PIMAGE_SECTION_HEADER pNewSecHeader = m_pSecHeader + m_SectionCnt;
	//pLastSecHeader指向区块表的最后一项
	PIMAGE_SECTION_HEADER pLastSecHeader = m_pSecHeader + m_SectionCnt - 1;
	DWORD dwSectionVA, dwSectionRawOffset, dwSectionSize;
	LARGE_INTEGER liFileOffset;
	BOOL bResult = FALSE;
	DWORD dwIoCnt = 0;
	//计算新节的起始虚拟内存偏移
	dwSectionVA = pLastSecHeader->VirtualAddress + GetAlignedSize(pLastSecHeader->Misc.VirtualSize, m_pOptHeader->SectionAlignment);
	//计算新节的物理地址起始偏移
	dwSectionRawOffset = pLastSecHeader->PointerToRawData + GetAlignedSize(pLastSecHeader->SizeOfRawData, m_pOptHeader->FileAlignment);
	//计算新节的大小,按文件对齐粒度对齐
	dwSectionSize = GetAlignedSize(SectionSize, m_pOptHeader->FileAlignment);

	//设置文件指针位置
	liFileOffset.QuadPart = dwSectionRawOffset + dwSectionSize;
	bResult = SetFilePointerEx(m_hFile, liFileOffset, NULL, FILE_BEGIN);
	if (!bResult)
	{
		FormatErrorMsg(TEXT("添加新节时设置文件指针错误!"), GetLastError());
		return NULL;
	}
	//设置文件尾
	bResult = SetEndOfFile(m_hFile);
	if (!bResult)
	{
		FormatErrorMsg(TEXT("添加新节时设置文件结束位置错误"), GetLastError());
		return NULL;
	}
	//填充SectionHeader
	ZeroMemory(pNewSecHeader, sizeof(IMAGE_SECTION_HEADER));
	strncpy((char*)pNewSecHeader->Name, szSectionName, 8);
	pNewSecHeader->Misc.VirtualSize = dwSectionSize;
	pNewSecHeader->VirtualAddress = dwSectionVA;
	pNewSecHeader->PointerToRawData = dwSectionRawOffset;
	pNewSecHeader->SizeOfRawData = dwSectionSize;
	//区块属性
	pNewSecHeader->Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_EXECUTE;
	//更新PE头中的节个数
	m_pFileHeader->NumberOfSections += 1;
	m_SectionCnt++;
	//更新PE头中的总映像大小
	m_pOptHeader->SizeOfImage += dwSectionSize;

	//保存PE头到文件中
	liFileOffset.QuadPart = 0;
	bResult = SetFilePointerEx(m_hFile, liFileOffset, NULL, FILE_BEGIN);
	if (!bResult)
	{
		FormatErrorMsg(TEXT("添加新节保存PE时设置文件指针错误!"), GetLastError());
		return NULL;
	}
	bResult = WriteFile(m_hFile, m_hModule, m_pOptHeader->SizeOfHeaders, &dwIoCnt, NULL);
	if (!bResult)
	{
		FormatErrorMsg(TEXT("添加新节保存PE时写入文件错误"), GetLastError());
		return NULL;
	}
	FlushFileBuffers(m_hFile);
	return pNewSecHeader;
}

//在内存中添加一个新节并返回新节的数据
PIMAGE_SECTION_HEADER CImage::AddNewSectionToMemory(char* szSectionName, DWORD SectionSize)
{
	PIMAGE_SECTION_HEADER pNewSecHeader = m_pSecHeader + m_SectionCnt;
	PIMAGE_SECTION_HEADER pLastSecHeader = m_pSecHeader + m_SectionCnt - 1;
	DWORD dwSectionVA, dwSectionRawOffset, dwSectionSize;
	BOOL bResult = FALSE;
	SIZE_T dwIoCnt = 0;
	HANDLE hProc = (m_hProc == NULL) ? GetCurrentProcess() : m_hProc;
	ULONG_PTR HighestUserAddress = 0;
	BYTE PEHeader[0x1000] = { 0 };

	//获取基本的地址空间信息
	SYSTEM_INFO sysinfo;
	ZeroMemory(&sysinfo, sizeof(SYSTEM_INFO));
	GetSystemInfo(&sysinfo);
	//应用程序可以访问的最高内存地址。
	HighestUserAddress = (ULONG_PTR)sysinfo.lpMaximumApplicationAddress;

	//计算新节的起始虚拟内存偏移
	dwSectionVA = pLastSecHeader->VirtualAddress + ALIGN_SIZE_UP(pLastSecHeader->Misc.VirtualSize, m_pOptHeader->SectionAlignment);
	//计算新节的物理起始偏移
	dwSectionRawOffset = pLastSecHeader->PointerToRawData + GetAlignedSize(pLastSecHeader->SizeOfRawData, m_pOptHeader->FileAlignment);
	//计算新节的大小,按文件对齐粒度对齐
	dwSectionSize = GetAlignedSize(SectionSize, m_pOptHeader->FileAlignment);

	//新节的起始内存地址
	ULONG_PTR dwNewSectionStartAddr = m_ImageBase + dwSectionVA;
	//dwNewSectionStartAddr 虚拟内存空间的粒度....按虚拟内存空间粒度对齐节的起始地址
	ULONG_PTR AddressToAlloc = NULL;//= GetAlignedPointer(dwNewSectionStartAddr, sysinfo.dwAllocationGranularity);
	PBYTE AllocatedMem = NULL;

	//从PE最后一个节开始,向后申请内存
	for (AddressToAlloc = dwNewSectionStartAddr; AddressToAlloc < HighestUserAddress; AddressToAlloc += sysinfo.dwAllocationGranularity)
	{
		//申请地址
		AllocatedMem = (PBYTE)VirtualAllocEx(hProc, (PVOID)AddressToAlloc, dwSectionSize, MEM_RESERVE | MEM_COMMIT | PAGE_EXECUTE_READWRITE);
		if (AllocatedMem != NULL)
		{
			break;
		}
	}
	if (AllocatedMem == NULL)
	{
		FormatErrorMsg(TEXT("添加新节时在目标进程中申请内存失败!"), GetLastError());
		return NULL;
	}
	//求出新分配的内存起始地址相对于m_ImageBase的偏移,也就是新节的RVA
	dwSectionVA = FILED_OFFSET(AllocatedMem, m_ImageBase);

	//填充SectionHeader
	ZeroMemory(pNewSecHeader, sizeof(IMAGE_SECTION_HEADER));
	strncpy((char*)pNewSecHeader->Name, szSectionName, 8);
	pNewSecHeader->Misc.VirtualSize = dwSectionSize;
	pNewSecHeader->VirtualAddress = dwSectionSize;
	pNewSecHeader->PointerToRawData = dwSectionRawOffset;
	pNewSecHeader->SizeOfRawData = dwSectionSize;
	pNewSecHeader->Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_EXECUTE;

	//更新PE头的节点个数
	m_pFileHeader->NumberOfSections += 1;
	//更新节的数量
	m_SectionCnt++;
	//更新PE头中的总映像大小
	m_pOptHeader->SizeOfImage += dwSectionSize;

	//更新目标进程的PE头
	DWORD dwOldProtect = 0;
	//修改目标进程内存属性为可写
	bResult = VirtualProtectEx(hProc, (LPVOID)m_ImageBase, m_pOptHeader->SizeOfHeaders, PAGE_READWRITE, &dwOldProtect);
	if (!bResult)
	{
		FormatErrorMsg(TEXT("修改目标进程内存属性"), GetLastError());
		return NULL;
	}
	//向目标进程的内存写入PE头
	bResult = WriteProcessMemory(hProc, (LPVOID)m_ImageBase, m_HeaderData, m_pOptHeader->SizeOfHeaders, &dwIoCnt);
	if (!bResult)
	{
		FormatErrorMsg(TEXT("向目标进程写入PE头数据时错误!"), GetLastError());
		return NULL;
	}
	return pNewSecHeader;
}


//对文件最后一个节进行扩展
PIMAGE_SECTION_HEADER CImage::ExtraLastSectionSizeToFile(DWORD SectionAddSize)
{
	//得到最后一个节的区块表
	PIMAGE_SECTION_HEADER pLastSecHeader = m_pSecHeader + m_SectionCnt - 1;
	DWORD dwSectionNewVirtualSize, dwSectionNewRawOffset, dwSectionNewRawSize;
	DWORD dwOldSectionVirtualSize = 0;
	LARGE_INTEGER liFileOffset;
	BOOL bResult = FALSE;
	DWORD dwIoCnt = 0;

	/*在扩展最后一个节的情况下,需要更改最后一个节的RawSize和VirtualSize,起始偏移均不变
	计算新节的物理大小,按文件对齐粒度对齐*/

	//得到最后一个节的文件偏移
	dwSectionNewRawOffset = pLastSecHeader->PointerToRawData;
	//得到最后一个节扩展后的大小
	dwSectionNewRawSize = GetAlignedSize(pLastSecHeader->SizeOfRawData + SectionAddSize, m_pOptHeader->FileAlignment);
	//得到最后一个节在内存中对齐后的大小(未扩展,也就是Old)
	dwOldSectionVirtualSize = dwSectionNewVirtualSize = GetAlignedSize(pLastSecHeader->Misc.VirtualSize, m_pOptHeader->SectionAlignment);

	//计算出新节的VirtualSize大小
	if (pLastSecHeader->Misc.VirtualSize < dwSectionNewRawSize)
	{
		dwSectionNewVirtualSize += SectionAddSize;
	}

	//设置文件指针位置为扩展后的末尾
	liFileOffset.QuadPart = dwSectionNewRawOffset + pLastSecHeader->SizeOfRawData + SectionAddSize;
	bResult = SetFilePointerEx(m_hFile, liFileOffset, NULL, FILE_BEGIN);
	if (!bResult)
	{
		FormatErrorMsg(TEXT("添加新节时设置文件指针错误"), GetLastError());
		return NULL;
	}
	//设置文件尾
	bResult = SetEndOfFile(m_hFile);

	if (!bResult)
	{
		FormatErrorMsg(TEXT("添加新节时设置文件结束位置错误!"), GetLastError());
		return NULL;
	}

	//填充SectionHeader
	pLastSecHeader->Misc.VirtualSize = dwSectionNewVirtualSize;
	pLastSecHeader->SizeOfRawData = dwSectionNewRawSize;
	pLastSecHeader->Characteristics |= IMAGE_SCN_MEM_READ;

	//更新PE头中的总映像大小
	m_pOptHeader->SizeOfImage = m_pOptHeader->SizeOfImage - dwOldSectionVirtualSize + GetAlignedSize(pLastSecHeader->Misc.VirtualSize, m_pOptHeader->SectionAlignment);

	//保存PE头到文件中
	liFileOffset.QuadPart = 0;
	bResult = SetFilePointerEx(m_hFile, liFileOffset, NULL, FILE_BEGIN);
	if (!bResult)
	{
		FormatErrorMsg(TEXT("添加新节保存PE时设置文件指针错误!"), GetLastError());
		return NULL;
	}

	bResult = WriteFile(m_hFile, m_hModule, m_pOptHeader->SizeOfHeaders, &dwIoCnt, NULL);

	if (!bResult)
	{
		FormatErrorMsg(TEXT("添加新节保存PE时写入文件错误!"), GetLastError());
		return NULL;
	}
	FlushFileBuffers(m_hFile);
	return pLastSecHeader;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值