隐藏模块(无模块注入)

 

模块隐藏那节课要求完成两个作业,都是隐藏模块,本文介绍两种方法分别如何实现。

方法一:往自己的进程注入游戏主模块

在这里插入图片描述

这个题目的意思是将程序的基址设置成高地址,将0x400000空出来,然后将游戏主模块拉伸,修复IAT之后,注入进去。这样做的好处主要是简单,因为不需要修复重定位表了。缺点也很明显,很多时候根本没办法在0x400000处申请足够大的内存容纳游戏程序。,还有就是有些程序跳转到OEP之后会卡住,具体原因我也不知道。我这里用dbgview.exe测试,能够正常启动,这足够说明我的代码没有大的错误。
在这里插入图片描述
任务管理器中只会看到控制台的进程,看不到dbgview的进程:
在这里插入图片描述

所谓修复IAT,就是指,将游戏PE的IAT表修复成函数地址,做法也很简单,遍历IAT表,loadlibary获取dll句柄,然后遍历函数名或函数序号,一个个getprocaddress,将获取到的函数地址写入到IAT表就算修复好了。

下面是我的代码,我只给出关键的两个函数,一个是修复IAT表,一个是注入的函数,完整代码放在文章最后。

注入代码

// 将游戏模块注入到我的进程
// 修改ImageBase=0x20000000,使本程序在高地址运行
// 这种方式非常不推荐,因为游戏程序只要稍微大一些,就很可能无法在0x400000处申请内存了
void GameInjectMe()
{	
	if ((DWORD)GetModuleHandle(NULL) != (DWORD)0x20000000)
	{
		printf("当前进程句柄: 0x%X\n", (DWORD)GetModuleHandle(NULL));
		printf("请修改链接设置,将ImageBase设置为0x20000000\n");
		return;
	}
	// 读取游戏PE,拉伸,修复IAT,写入到其ImageBase处
	LPVOID pFileBuffer = NULL;
	//FileToMemory(TEXT("C:\\Documents and Settings\\nixiang\\桌面\\DTDebug(VT-O)专业版V1.0.025\\DTDebug\\DTDebug.exe"), &pFileBuffer);
	//FileToMemory(TEXT("c:\\program32\\Helloworld.exe"), &pFileBuffer);
	FileToMemory(TEXT("c:\\program32\\dbgview.exe"), &pFileBuffer);
	LPVOID pImageBuffer = NULL;
	DWORD dwImageSize = FileBufferToImageBuffer(pFileBuffer, &pImageBuffer);
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));		
	LPVOID pImageBase = VirtualAlloc((LPVOID)pOptionHeader->ImageBase, dwImageSize,MEM_COMMIT, PAGE_READWRITE);	
	if (pImageBase != (LPVOID)pOptionHeader->ImageBase)
	{
		printf("错误码:0x%X 在ImageBase(0x%X)处VirtualAlloc失败\n",GetLastError(),pOptionHeader->ImageBase);
		return;
	}
	// 修复IAT表
	RepairIAT(pImageBuffer);	
	memcpy(pImageBase, pImageBuffer, dwImageSize);
	
	// 跳转到游戏的入口点
	DWORD dwOEP = pOptionHeader->AddressOfEntryPoint + pOptionHeader->ImageBase;
	__asm{
		jmp dwOEP
	}	
}

修复IAT代码

// 传入一个imagebuffer,修复它的IAT表
void RepairIAT(LPVOID pImageBuffer)
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pDosHeader + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
	PIMAGE_SECTION_HEADER pSectionHeader = \
		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
	
// 	PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pFileBuffer + \
// 		RvaToFoa(pFileBuffer, pOptionHeader->DataDirectory[1].VirtualAddress));
	PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pImageBuffer + \
		pOptionHeader->DataDirectory[1].VirtualAddress);
	
	// 严格来说应该是 sizeof(IMAGE_IMPORT_DESCRIPTOR) 个字节为0表示结束
	while (pImportTable->OriginalFirstThunk || pImportTable->FirstThunk)
	{
		// 打印模块名
		//printf("%s\n", (LPCSTR)(pImportTable->Name + (DWORD)pImageBuffer));
		// 获取模块句柄
		HMODULE hModule = LoadLibraryA((LPCSTR)(pImportTable->Name + (DWORD)pImageBuffer));
		if (NULL == hModule)
		{
			printf("获取模块句柄失败,模块名: %s\n",(LPCSTR)(pImportTable->Name + (DWORD)pImageBuffer));
		}
		// 修复IAT表
		//printf("--------------FirstThunkRVA:%x--------------\n", pImportTable->FirstThunk);		
		PIMAGE_THUNK_DATA32 pThunkData = (PIMAGE_THUNK_DATA32)((DWORD)pImageBuffer + \
			pImportTable->FirstThunk);
		while (*((PDWORD)pThunkData) != 0)
		{
			// IMAGE_THUNK_DATA32 是一个4字节数据
			// 如果最高位是1,那么除去最高位就是导出序号
			// 如果最高位是0,那么这个值是RVA 指向 IMAGE_IMPORT_BY_NAME
			if ((*((PDWORD)pThunkData) & 0x80000000) == 0x80000000)
			{
				//printf("按序号导入 Ordinal:%04x\n", (*((PDWORD)pThunkData) & 0x7FFFFFFF));
				DWORD dwProcAddress = (DWORD)GetProcAddress(hModule,MAKEINTRESOURCE((*((PDWORD)pThunkData) & 0x7FFFFFFF)));				
				*((PDWORD)pThunkData) = dwProcAddress;
			}
			else
			{
				PIMAGE_IMPORT_BY_NAME pIBN = (PIMAGE_IMPORT_BY_NAME)(*((PDWORD)pThunkData) + \
					(DWORD)pImageBuffer);
				
				//printf("按名字导入 Hint:%04x Name:%s\n", pIBN->Hint, pIBN->Name);
				DWORD dwProcAddress = (DWORD)GetProcAddress(hModule,(LPCSTR)pIBN->Name);
				*((PDWORD)pThunkData) = dwProcAddress;
			}
			pThunkData++;
		}
		pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pImportTable + sizeof(IMAGE_IMPORT_DESCRIPTOR));		
	}	
}

其实方法一的局限性我在博客开头已经分析过了,不过我想了一下发现,其实问题关键就是0x400000处可能会申请内存失败,实际上我们不需要非得在这个地方申请内存,而是可以在任意地方申请,然后根据重定位表,修复地址即可。而这种方式,在方法二里面用到了。

以上是第一种方法的做法,下面是第二种做法,往游戏里注入自己,然后跳转到入口函数:

方法二:往游戏进程注入自己的主模块

在这里插入图片描述
这种方式不需要手工修复IAT,因为自身进程启动时操作系统帮我们修复了,我们只需要修复重定位表即可。

原理上图已经解释的非常明白了,下面给出关键代码:
重定位表修复代码

// 修改 ImageBase 并修复重定位表
// 内存镜像版本
VOID SetNewImageBase2(LPVOID pImageBuffer, DWORD dwNewImageBase)
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pDosHeader + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
	PIMAGE_SECTION_HEADER pSectionHeader = \
		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
	
	PIMAGE_BASE_RELOCATION pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pImageBuffer + \
		pOptionHeader->DataDirectory[5].VirtualAddress);
	DWORD dwImageBaseDelta = dwNewImageBase - pOptionHeader->ImageBase; // 新旧ImageBase 的差值	
	
	// 重定位表的 VirtualAddress + 低12位偏移 = RVA
	// RVA + ImageBase 这个内存里存储了一个“指针”
	// 要修改的是这个“指针”的值,要让这个“指针”加上两个ImageBase的差值
	while (pRelocationTable->VirtualAddress || pRelocationTable->SizeOfBlock)
	{
		size_t n = (pRelocationTable->SizeOfBlock - 8) / 2; // 可能需要修改的地址数量(高4位==0011才要修改)
		PWORD pOffset = (PWORD)((DWORD)pRelocationTable + 8); // 2字节偏移的数组
		for (size_t i = 0; i < n; i++)
		{
			// 高4位等于0011才需要重定位
			if ((pOffset[i] & 0xF000) == 0x3000)
			{
				// 计算需要重定位的数据的RVA地址
				DWORD dwRva = pRelocationTable->VirtualAddress + (pOffset[i] & 0x0FFF);				
				// 计算在镜像中的地址
				PDWORD pData = (PDWORD)((DWORD)pImageBuffer + dwRva);
				// 重定位,即修正写死的地址				
				*pData += dwImageBaseDelta;
			}
		}		
		pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTable + pRelocationTable->SizeOfBlock);
	}
	// 修改 ImageBase
	pOptionHeader->ImageBase = dwNewImageBase;
}

注入代码

// 将我的模块注入到游戏进程
void MeInjectGame()
{
	// 获取自己的ImageBase和SizeOfImage
	HMODULE hModule = GetModuleHandle(NULL);
	LPVOID pImageBuffer = (LPVOID)hModule;	
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));	
	// 拷贝到新的缓冲区
	DWORD dwSizeOfImage = pOptionHeader->SizeOfImage;
	pImageBuffer = malloc(dwSizeOfImage);
	memcpy(pImageBuffer, hModule, dwSizeOfImage);
	// 在游戏进程申请内存
	HWND hWnd = FindWindowA(NULL, "LittleGame");
    DWORD dwPID = 0;
    GetWindowThreadProcessId(hWnd, &dwPID);
	printf("进程id: %d\n", dwPID);	
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPID);
	LPVOID pGameImageBase = VirtualAllocEx(hProcess,NULL,dwSizeOfImage,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
	if (NULL == pGameImageBase)
	{
		printf("在游戏进程申请内存失败,错误码: %d\n", GetLastError());
		return;
	}
	// 写入到游戏进程前,先修复重定位表
	SetNewImageBase2(pImageBuffer, (DWORD)pGameImageBase);
	WriteProcessMemory(hProcess,pGameImageBase,pImageBuffer,dwSizeOfImage,NULL);
	// 获取当前进程中,入口函数的地址(希望在注入后运行的那个函数),然后和基址相减得到偏移
	DWORD dwProcOffset = (DWORD)InjectEntry - (DWORD)hModule + (DWORD)pGameImageBase;
	// 创建远程线程,执行入口代码
	CreateRemoteThread(hProcess,NULL,NULL,(LPTHREAD_START_ROUTINE)dwProcOffset,NULL,NULL,NULL);
}

入口代码

DWORD WINAPI InjectEntry(LPVOID param)
{
	while (TRUE)
	{
		MessageBox(0,0,0,0);
		Sleep(5000);
	}
	return 0;
}

完整代码

最后,给出项目完整的代码。
PE.H

#ifndef PE_HPP_
#define PE_HPP_

/********************************************************************************
时间:2020年7月14日
作者:hambaga
说明:重新整理的PE工具函数,仅适用于32位程序
********************************************************************************/

#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif // !_CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <WINDOWS.H>
#include <STRING.h>
#include <MALLOC.H>

DWORD FileToMemory(LPCSTR lpszFile, LPVOID *pFileBuffer);
BOOL MemoryToFile(LPVOID pMemBuffer, DWORD dwSize, LPCSTR lpszFile);
BOOL Is32PEFile(LPVOID pFileBuffer, DWORD dwSize);
DWORD FileBufferToImageBuffer(LPVOID pFileBuffer, LPVOID *pImageBuffer);
DWORD ImageBufferToFileBuffer(LPVOID pImageBuffer, LPVOID *pFileBuffer);
DWORD Align(DWORD dwOffset, DWORD dwAlign);
DWORD RvaToFoa(LPVOID pFileBuffer, DWORD dwRva);
DWORD FoaToRva(LPVOID pFileBuffer, DWORD dwFoa);
DWORD MoveNTHeaderAndSectionHeadersToDosStub(LPVOID pFileBuffer);
VOID SetNewImageBase(LPVOID pFileBuffer, DWORD dwNewImageBase);
void RepairIAT(LPVOID pImageBuffer);

// 读取文件到内存中,返回读取的字节数;读取失败返回0
DWORD FileToMemory(LPCSTR lpszFile, LPVOID *pFileBuffer)
{
	FILE *pFile = NULL;
	DWORD dwFileSize = 0;
	pFile = fopen(lpszFile, "rb");
	if (pFile == NULL)
	{
		printf("打开文件失败\n");
		return 0;
	}
	fseek(pFile, 0, SEEK_END);
	dwFileSize = ftell(pFile);
	fseek(pFile, 0, SEEK_SET);
	*pFileBuffer = malloc(dwFileSize);
	if (*pFileBuffer == NULL)
	{
		printf("分配内存失败\n");
		fclose(pFile);
		return 0;
	}
	DWORD dwRead = fread(*pFileBuffer, 1, dwFileSize, pFile);
	fclose(pFile);
	if (dwRead != dwFileSize)
	{
		free(*pFileBuffer);
		return 0;
	}
	return dwRead;
}

// 验证是否是合法的32位PE文件
BOOL Is32PEFile(LPVOID pFileBuffer, DWORD dwSize)
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
	PIMAGE_SECTION_HEADER pSectionHeader = \
		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
	if (*((PWORD)pDosHeader) != IMAGE_DOS_SIGNATURE)
	{
		printf("不是有效的MZ标志\n");
		return FALSE;
	}
	if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)
	{
		printf("不是有效的PE标记\n");
		return FALSE;
	}
	return TRUE;
}

// 将 FileBuffer 拉伸成 ImageBuffer 并写入到新的缓冲区
// 返回 ImageBuffer 的大小;失败返回0
DWORD FileBufferToImageBuffer(LPVOID pFileBuffer, LPVOID *pImageBuffer)
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
	PIMAGE_SECTION_HEADER pSectionHeader = \
		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);

	*pImageBuffer = malloc(pOptionHeader->SizeOfImage);
	if (*pImageBuffer == NULL)
	{
		printf("分配内存失败\n");
		return 0;
	}
	memset(*pImageBuffer, 0, pOptionHeader->SizeOfImage);
	// 复制DOS头+PE头+可选PE头+节表+文件对齐
	memcpy(*pImageBuffer, pFileBuffer, pOptionHeader->SizeOfHeaders);
	// 遍历节表,复制所有节	
	for (int i = 0; i < pPEHeader->NumberOfSections; i++)
	{
		memcpy((LPVOID)((DWORD)(*pImageBuffer) + pSectionHeader[i].VirtualAddress), \
			(LPVOID)((DWORD)pFileBuffer + pSectionHeader[i].PointerToRawData), \
			pSectionHeader[i].SizeOfRawData);
	}
	return pOptionHeader->SizeOfImage;
}

// 将 ImageBuffer 变成文件对齐的 FileBuffer 写入新的缓冲区
// 返回复制的大小,失败返回0
DWORD ImageBufferToFileBuffer(LPVOID pImageBuffer, LPVOID *pFileBuffer)
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
	PIMAGE_SECTION_HEADER pSectionHeader = \
		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);

	// 最后一个节表
	PIMAGE_SECTION_HEADER pLastSectionHeader = pSectionHeader + pPEHeader->NumberOfSections - 1;
	// 计算要复制的字节
	// 这一步有BUG,当最后一个节后面还有数据时(多见于控制台程序),丢失数据
	DWORD dwFileBufferSize = pLastSectionHeader->PointerToRawData + pLastSectionHeader->SizeOfRawData;
	*pFileBuffer = malloc(dwFileBufferSize);
	if (*pFileBuffer == NULL)
	{
		printf("分配内存失败\n");
		return 0;
	}
	memset(*pFileBuffer, 0, dwFileBufferSize);
	// 复制DOS头+PE头+可选PE头+节表+文件对齐
	memcpy(*pFileBuffer, pImageBuffer, pOptionHeader->SizeOfHeaders);
	// 遍历节表,复制文件对齐后的节	
	for (int i = 0; i < pPEHeader->NumberOfSections; i++)
	{
		memcpy((LPVOID)((DWORD)(*pFileBuffer) + pSectionHeader[i].PointerToRawData), \
			(LPVOID)((DWORD)pImageBuffer + pSectionHeader[i].VirtualAddress), \
			pSectionHeader[i].SizeOfRawData);
	}
	return dwFileBufferSize;
}

// 内存数据写入文件
BOOL MemoryToFile(LPVOID pMemBuffer, DWORD dwSize, LPCSTR lpszFile)
{
	FILE *fp = NULL;
	fp = fopen(lpszFile, "wb+");
	if (fp == NULL)
	{
		printf("打开文件失败\n");
		return FALSE;
	}
	DWORD dwWritten = fwrite(pMemBuffer, 1, dwSize, fp);
	if (dwWritten != dwSize)
	{
		printf("写入了 %d 字节,不等于 %d\n", dwWritten, dwSize);
		fclose(fp);
		return FALSE;
	}
	fclose(fp);
	return TRUE;
}

// 计算对齐的函数,如偏移为900,对齐为1000h,返回1000h
DWORD Align(DWORD dwOffset, DWORD dwAlign)
{
	// 如果偏移小于对齐,向上取整
	if (dwOffset <= dwAlign) return dwAlign;
	// 如果偏移大于对齐且不能除尽,向上取整
	if (dwOffset % dwAlign)
	{
		return (dwOffset / dwAlign + 1) * dwAlign;
	}
	// 如果能除尽,直接返回offset
	return dwOffset;
}

// RVA 转 FOA
DWORD RvaToFoa(LPVOID pFileBuffer, DWORD dwRva)
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pFileBuffer + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
	PIMAGE_SECTION_HEADER pSectionHeader = \
		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);

	// RVA在文件头中或者文件对齐==内存对齐时,RVA==FOA  错!第一句是对的,第二句是错的
	if (dwRva < pOptionHeader->SizeOfHeaders)
	{
		return dwRva;
	}

	// 遍历节表,确定偏移属于哪一个节	
	for (int i = 0; i < pPEHeader->NumberOfSections; i++)
	{
		if (dwRva >= pSectionHeader[i].VirtualAddress && \
			dwRva < pSectionHeader[i].VirtualAddress + pSectionHeader[i].Misc.VirtualSize)
		{
			int offset = dwRva - pSectionHeader[i].VirtualAddress;
			return pSectionHeader[i].PointerToRawData + offset;
		}
	}
	printf("找不到RVA %x 对应的 FOA,转换失败\n", dwRva);
	return 0;
}

// FOA 转 RVA
DWORD FoaToRva(LPVOID pFileBuffer, DWORD dwFoa)
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pFileBuffer + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
	PIMAGE_SECTION_HEADER pSectionHeader = \
		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);

	// RVA在文件头中或者文件对齐==内存对齐时,RVA==FOA  错!第一句是对的,第二句是错的
	if (dwFoa < pOptionHeader->SizeOfHeaders)
	{
		return dwFoa;
	}

	// 遍历节表,确定偏移属于哪一个节	
	for (int i = 0; i < pPEHeader->NumberOfSections; i++)
	{
		if (dwFoa >= pSectionHeader[i].PointerToRawData && \
			dwFoa < pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData)
		{
			int offset = dwFoa - pSectionHeader[i].PointerToRawData;
			return pSectionHeader[i].VirtualAddress + offset;
		}
	}
	printf("找不到FOA %x 对应的 RVA,转换失败\n", dwFoa);
	return 0;
}

// 移动NT头和节表到DOS STUB,该函数在新增节时节表空间不足的情况下调用;返回地址减小值
DWORD MoveNTHeaderAndSectionHeadersToDosStub(LPVOID pFileBuffer)
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
	PIMAGE_SECTION_HEADER pSectionHeader = \
		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);

	LPVOID pDst = (LPVOID)((DWORD)pDosHeader + sizeof(IMAGE_DOS_HEADER)); // NT头插入点
	DWORD dwRet = (DWORD)pNTHeader - (DWORD)pDst; // 返回地址减小的值
	DWORD dwSize = 4 + sizeof(IMAGE_FILE_HEADER) + pPEHeader->SizeOfOptionalHeader + \
		sizeof(IMAGE_SECTION_HEADER) * pPEHeader->NumberOfSections; // 移动的字节数
	LPVOID pSrc = malloc(dwSize);
	if (pSrc == NULL)
	{
		printf("分配内存失败\n");
		return 0;
	}
	memcpy(pSrc, (LPVOID)pNTHeader, dwSize); // 保存要复制的数据
	memset((LPVOID)pNTHeader, 0, dwSize); // 清空原数据
	memcpy(pDst, pSrc, dwSize); // 移动数据
	free(pSrc);
	pDosHeader->e_lfanew = sizeof(IMAGE_DOS_HEADER); // 更新 e_lfanew

	return dwRet;
}

// 修改 ImageBase 并修复重定位表
// 文件版本
VOID SetNewImageBase(LPVOID pFileBuffer, DWORD dwNewImageBase)
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pDosHeader + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
	PIMAGE_SECTION_HEADER pSectionHeader = \
		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);

	PIMAGE_BASE_RELOCATION pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pFileBuffer + \
		RvaToFoa(pFileBuffer, pOptionHeader->DataDirectory[5].VirtualAddress));
	DWORD dwImageBaseDelta = dwNewImageBase - pOptionHeader->ImageBase; // 新旧ImageBase 的差值	

	// 重定位表的 VirtualAddress + 低12位偏移 = RVA
	// RVA + ImageBase 这个内存里存储了一个“指针”
	// 要修改的是这个“指针”的值,要让这个“指针”加上两个ImageBase的差值
	while (pRelocationTable->VirtualAddress || pRelocationTable->SizeOfBlock)
	{
		size_t n = (pRelocationTable->SizeOfBlock - 8) / 2; // 可能需要修改的地址数量(高4位==0011才要修改)
		PWORD pOffset = (PWORD)((DWORD)pRelocationTable + 8); // 2字节偏移的数组
		for (size_t i = 0; i < n; i++)
		{
			// 高4位等于0011才需要重定位
			if ((pOffset[i] & 0xF000) == 0x3000)
			{
				// 计算需要重定位的数据的RVA地址
				DWORD dwRva = pRelocationTable->VirtualAddress + (pOffset[i] & 0x0FFF);
				// 计算在文件中的偏移
				DWORD dwFoa = RvaToFoa(pFileBuffer, dwRva);
				// 计算在文件中的地址
				PDWORD pData = (PDWORD)((DWORD)pFileBuffer + dwFoa);
				// 重定位,即修正写死的地址				
				*pData += dwImageBaseDelta;
			}
		}

		pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTable + pRelocationTable->SizeOfBlock);
	}
	// 修改 ImageBase
	pOptionHeader->ImageBase = dwNewImageBase;
}

// 修改 ImageBase 并修复重定位表
// 内存镜像版本
VOID SetNewImageBase2(LPVOID pImageBuffer, DWORD dwNewImageBase)
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pDosHeader + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
	PIMAGE_SECTION_HEADER pSectionHeader = \
		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
	
	PIMAGE_BASE_RELOCATION pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pImageBuffer + \
		pOptionHeader->DataDirectory[5].VirtualAddress);
	DWORD dwImageBaseDelta = dwNewImageBase - pOptionHeader->ImageBase; // 新旧ImageBase 的差值	
	
	// 重定位表的 VirtualAddress + 低12位偏移 = RVA
	// RVA + ImageBase 这个内存里存储了一个“指针”
	// 要修改的是这个“指针”的值,要让这个“指针”加上两个ImageBase的差值
	while (pRelocationTable->VirtualAddress || pRelocationTable->SizeOfBlock)
	{
		size_t n = (pRelocationTable->SizeOfBlock - 8) / 2; // 可能需要修改的地址数量(高4位==0011才要修改)
		PWORD pOffset = (PWORD)((DWORD)pRelocationTable + 8); // 2字节偏移的数组
		for (size_t i = 0; i < n; i++)
		{
			// 高4位等于0011才需要重定位
			if ((pOffset[i] & 0xF000) == 0x3000)
			{
				// 计算需要重定位的数据的RVA地址
				DWORD dwRva = pRelocationTable->VirtualAddress + (pOffset[i] & 0x0FFF);				
				// 计算在镜像中的地址
				PDWORD pData = (PDWORD)((DWORD)pImageBuffer + dwRva);
				// 重定位,即修正写死的地址				
				*pData += dwImageBaseDelta;
			}
		}		
		pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTable + pRelocationTable->SizeOfBlock);
	}
	// 修改 ImageBase
	pOptionHeader->ImageBase = dwNewImageBase;
}


// 传入一个imagebuffer,修复它的IAT表
void RepairIAT(LPVOID pImageBuffer)
{
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pDosHeader + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
	PIMAGE_SECTION_HEADER pSectionHeader = \
		(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
	PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pImageBuffer + \
		pOptionHeader->DataDirectory[1].VirtualAddress);	
	// 严格来说应该是 sizeof(IMAGE_IMPORT_DESCRIPTOR) 个字节为0表示结束
	while (pImportTable->OriginalFirstThunk || pImportTable->FirstThunk)
	{
		// 打印模块名
		//printf("%s\n", (LPCSTR)(pImportTable->Name + (DWORD)pImageBuffer));
		// 获取模块句柄
		HMODULE hModule = LoadLibraryA((LPCSTR)(pImportTable->Name + (DWORD)pImageBuffer));
		if (NULL == hModule)
		{
			printf("获取模块句柄失败,模块名: %s\n",(LPCSTR)(pImportTable->Name + (DWORD)pImageBuffer));
		}
		// 修复IAT表
		//printf("--------------FirstThunkRVA:%x--------------\n", pImportTable->FirstThunk);		
		PIMAGE_THUNK_DATA32 pThunkData = (PIMAGE_THUNK_DATA32)((DWORD)pImageBuffer + \
			pImportTable->FirstThunk);
		while (*((PDWORD)pThunkData) != 0)
		{
			// IMAGE_THUNK_DATA32 是一个4字节数据
			// 如果最高位是1,那么除去最高位就是导出序号
			// 如果最高位是0,那么这个值是RVA 指向 IMAGE_IMPORT_BY_NAME
			if ((*((PDWORD)pThunkData) & 0x80000000) == 0x80000000)
			{
				//printf("按序号导入 Ordinal:%04x\n", (*((PDWORD)pThunkData) & 0x7FFFFFFF));
				DWORD dwProcAddress = (DWORD)GetProcAddress(hModule,MAKEINTRESOURCE((*((PDWORD)pThunkData) & 0x7FFFFFFF)));				
				*((PDWORD)pThunkData) = dwProcAddress;
			}
			else
			{
				PIMAGE_IMPORT_BY_NAME pIBN = (PIMAGE_IMPORT_BY_NAME)(*((PDWORD)pThunkData) + \
					(DWORD)pImageBuffer);
				
				//printf("按名字导入 Hint:%04x Name:%s\n", pIBN->Hint, pIBN->Name);
				DWORD dwProcAddress = (DWORD)GetProcAddress(hModule,(LPCSTR)pIBN->Name);
				*((PDWORD)pThunkData) = dwProcAddress;
			}
			pThunkData++;
		}
		pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pImportTable + sizeof(IMAGE_IMPORT_DESCRIPTOR));		
	}	
}


#endif

main.cpp

// MemoryInject.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "PE.h"
#include <MALLOC.H>

void GameInjectMe();
void MeInjectGame();
BOOL EnableDebugPrivilege();
DWORD WINAPI InjectEntry(LPVOID param);

int main(int argc, char* argv[])
{
	EnableDebugPrivilege(); 	
	
	//GameInjectMe();
	MeInjectGame();
	return 0;
}


// 将游戏模块注入到我的进程
// 修改ImageBase=0x20000000,使本程序在高地址运行
// 这种方式非常不推荐,因为游戏程序只要稍微大一些,就很可能无法在0x400000处申请内存了
void GameInjectMe()
{	
	if ((DWORD)GetModuleHandle(NULL) != (DWORD)0x20000000)
	{
		printf("当前进程句柄: 0x%X\n", (DWORD)GetModuleHandle(NULL));
		printf("请修改链接设置,将ImageBase设置为0x20000000\n");
		return;
	}
	// 读取游戏PE,拉伸,修复IAT,写入到其ImageBase处
	LPVOID pFileBuffer = NULL;
	FileToMemory(TEXT("c:\\program32\\dbgview.exe"), &pFileBuffer);
	LPVOID pImageBuffer = NULL;
	DWORD dwImageSize = FileBufferToImageBuffer(pFileBuffer, &pImageBuffer);
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));		
	LPVOID pImageBase = VirtualAlloc((LPVOID)pOptionHeader->ImageBase, dwImageSize,MEM_COMMIT, PAGE_READWRITE);	
	if (pImageBase != (LPVOID)pOptionHeader->ImageBase)
	{
		printf("错误码:0x%X 在ImageBase(0x%X)处VirtualAlloc失败\n",GetLastError(),pOptionHeader->ImageBase);
		return;
	}
	// 修复IAT表
	RepairIAT(pImageBuffer);	
	memcpy(pImageBase, pImageBuffer, dwImageSize);
	
	// 跳转到游戏的入口点
	DWORD dwOEP = pOptionHeader->AddressOfEntryPoint + pOptionHeader->ImageBase;
	__asm{
		jmp dwOEP
	}	
}

// 将我的模块注入到游戏进程
void MeInjectGame()
{
	// 获取自己的ImageBase和SizeOfImage
	HMODULE hModule = GetModuleHandle(NULL);
	LPVOID pImageBuffer = (LPVOID)hModule;	
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
	PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
	PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));	
	// 拷贝到新的缓冲区
	DWORD dwSizeOfImage = pOptionHeader->SizeOfImage;
	pImageBuffer = malloc(dwSizeOfImage);
	memcpy(pImageBuffer, hModule, dwSizeOfImage);
	// 在游戏进程申请内存
	HWND hWnd = FindWindowA(NULL, "LittleGame");
    DWORD dwPID = 0;
    GetWindowThreadProcessId(hWnd, &dwPID);
	printf("进程id: %d\n", dwPID);	
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPID);
	LPVOID pGameImageBase = VirtualAllocEx(hProcess,NULL,dwSizeOfImage,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
	if (NULL == pGameImageBase)
	{
		printf("在游戏进程申请内存失败,错误码: %d\n", GetLastError());
		return;
	}
	// 写入到游戏进程前,先修复重定位表
	SetNewImageBase2(pImageBuffer, (DWORD)pGameImageBase);
	WriteProcessMemory(hProcess,pGameImageBase,pImageBuffer,dwSizeOfImage,NULL);
	// 获取当前进程中,入口函数的地址(希望在注入后运行的那个函数),然后和基址相减得到偏移
	DWORD dwProcOffset = (DWORD)InjectEntry - (DWORD)hModule + (DWORD)pGameImageBase;
	// 创建远程线程,执行入口代码
	CreateRemoteThread(hProcess,NULL,NULL,(LPTHREAD_START_ROUTINE)dwProcOffset,NULL,NULL,NULL);
}

DWORD WINAPI InjectEntry(LPVOID param)
{
	while (TRUE)
	{
		MessageBox(0,0,0,0);
		Sleep(5000);
	}
	return 0;
}

// 提权函数:提升为DEBUG权限
BOOL EnableDebugPrivilege()
{
	HANDLE hToken;
	BOOL fOk=FALSE;
	if(OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken))
	{
		TOKEN_PRIVILEGES tp;
		tp.PrivilegeCount=1;
		LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tp.Privileges[0].Luid);
		
		tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
		AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(tp),NULL,NULL);
		
		fOk=(GetLastError()==ERROR_SUCCESS);
		CloseHandle(hToken);
	}
    return fOk;
}


---------------------
作者:hambaga
来源:CSDN
原文:https://blog.csdn.net/kwansy/article/details/107958662
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值