模块隐藏那节课要求完成两个作业,都是隐藏模块,本文介绍两种方法分别如何实现。
方法一:往自己的进程注入游戏主模块
这个题目的意思是将程序的基址设置成高地址,将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博客文章一键转载插件