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

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

这个题目的意思是将程序的基址设置成高地址,将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_NAMEif ((*((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);}// 修改 ImageBasepOptionHeader->ImageBase = dwNewImageBase;
}

注入代码

// 将我的模块注入到游戏进程
void MeInjectGame()
{// 获取自己的ImageBase和SizeOfImageHMODULE 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;}// 如果能除尽,直接返回offsetreturn 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_lfanewreturn 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);}// 修改 ImageBasepOptionHeader->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);}// 修改 ImageBasepOptionHeader->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_NAMEif ((*((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和SizeOfImageHMODULE 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;
}

隐藏模块(无模块注入)相关推荐

  1. 驱动无模块注入dll

    文章目录 实现效果 三环无模块注入的方案 反射型dll注入方式的改进 零环无模块注入方案 petoshellcode 无模块注入流程 实现代码 Xenos注入方案研究 IT_MMap注入 IT_Thr ...

  2. python处理回显_Python中getpass模块无回显输入源码解析

    本文主要讨论了python中getpass模块的相关内容,具体如下. getpass模块 昨天跟学弟吹牛b安利Python标准库官方文档的时候偶然发现了这个模块.仔细一看内容挺少的,只有两个主要api ...

  3. python 路径往上一层_Python常用模块之模块、包介绍和相关语法

    在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护. 为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很 ...

  4. python 网络编程--socket模块/struct模块

    socket模块: 客户端:CS架构,  client -> server 浏览器:BS架构,  browser -> server 网络通信本质:传输字节 doc命令查看ip地址:ipc ...

  5. java 模块化_Java模块化方法–模块,模块,模块

    java 模块化 我想每个人都会同意,编写模块化应用程序和模块化通常是一件好事. 但是,从Java和Scala语言以及各种Java / Scala框架来看,对模块化的支持看起来如何? 有很多不同的方法 ...

  6. Java模块化方法–模块,模块,模块

    我认为每个人都会同意,编写模块化应用程序和模块化通常是一件好事. 但是,从Java和Scala语言以及各种Java / Scala框架来看,对模块化的支持是怎样的呢? 有很多不同的方法! 让我们看看其 ...

  7. 计算机应用基础形考作业3Excel,国家开放大学《计算机应用基础》考试与答案形考任务模块3 模块3 Excel 电子表格系统—客观题答案(2020年整理).pptx...

    国家开放大学<计算机应用基础>考试与答案形考任务模块3 模块3 Excel 电子表格系统-客观题答案(2020年整理).pptx 学 海 无 涯 Excel 2010 上机操作题(共 5 ...

  8. python-time模块--pickle模块

    目录 time 模块 为什么要有time模块,time模块有什么用? time模块的三种格式 时间戳(timestamp) 格式化时间(需要自己定义格式) 结构化时间(struct-time) 结构化 ...

  9. python中常用的模块(cleandar模块,time模块,datetime模块,OS模块,shutil()模块,random模块)

    常用模块-cleandar模块,time模块,datetime模块,OS模块,shutil()模块,random模块 claendar 跟日历相关的模块 语法 import calendar 常用方法 ...

  10. STM32F103+DHT11模块+DS18B20模块 显示实时温湿度并高温高湿报警

    目录 前言 代码下载: 功能介绍: 接线 DS18B20 DHT11 效果图 普通环境 普通版main.c 升级版 哈热气 普通版 升级版 搓手捂热 核心代码 普通版main.c 升级版main.c ...

最新文章

  1. php手机定位导航,js实现网页定位导航功能
  2. 解决idea控制台打印乱码问题
  3. 取消Exchange数据库的自动分配功能
  4. 内存映射文件(File Mapping)API
  5. 拾取物品怎么实现_这样的物品品质设定,一眼便知是“欧”还是“非”
  6. c++异常处理的基本思想
  7. 基于 Unity 中的 NGUI 插件,通用的 UI 如何设计
  8. linux上安装mysql5.7
  9. 国产浏览器 linux,360安全浏览器推出国产操作系统版,在deepin上架获得高度点赞...
  10. SAP S/4HANA Layer Repository(LREP)的读取逻辑调试
  11. php怎样完成批量审核,CMS_DedeCMS 批量取消审核文档的实现方法,今天修改了几个地方的代码 实 - phpStudy...
  12. 深拷贝与浅拷贝深入理解
  13. java springmvc https_【Java Web开发学习】Spring MVC 使用HTTP信息转换器
  14. .相亲过程:你有房子么?你有钱么?你有能力么? 【结婚吧】【先买房子在结婚】【先赚钱再买房子再结婚】都没有【拜拜~~】 利用if嵌套做相亲过程...
  15. C#关于AutoResetEvent的使用介绍[转载]
  16. 合同法律风险管理 被骗者刑事风险
  17. 笔记本使用的几大盲区
  18. PD协议诱骗取电XSP01支持Type-C 5V9V12V15V20V原理图
  19. CPU飙高原因及解决方案
  20. conda create创建虚拟环境失败

热门文章

  1. centos组件显示乱码,centos组件显示乱码_解决CentOS下中文显示乱码
  2. 成功解决利用pandas输出DataFrame格式数据表时没有最左边的索引编号(我去,这个问题折腾了我半个多小时)
  3. Interview:算法岗位面试—10.16下午—上海某公司算法岗位(偏图像算法,国内顶端医疗行业)技术面试之一点技术都没问
  4. 理解 java 中常用数据结构
  5. SSM框架整合(一)
  6. 腾讯云 视频 点播 视频上传接口
  7. ios开发-系统架构
  8. Timus 1204 Idempotents
  9. hdu 4739 状压DP
  10. Shell Basic