读取代码段实践小结
因为项目需要,需要读取PE文件的代码段信息和进程内的代码段信息,用于防破解的数据准备。从安全防护的角度上看,能防一点是一点。下面分别从读取PE文件和进程空间的代码段信息来小结。
读取PE文件的代码段信息
准备知识参见 windows PE文件结构及其加载机制,这篇文章讲解的很全面,很有参考价值。完成此项功能的思路是,将PE文件读取到内存中,定位到.text
段在PE文件中的偏移和代码段长度,将结果写入本地文件即可。
大致的代码流程如下:
// 获得PE文件代码段信息
// 思路:读取PE文件头部的代码段偏移和代码段大小
bool GetPECodeSegInfo(const char* pFilePath)
{
string strContent = GetFileContent(pFilePath);
if (0 == strContent.length())
return false;
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)strContent.c_str();
if (pDosHeader && pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
return false;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)pDosHeader + pDosHeader->e_lfanew);
if (pNtHeaders && pNtHeaders->Signature != IMAGE_NT_SIGNATURE)
return false;
PIMAGE_SECTION_HEADER pSecHeader = IMAGE_FIRST_SECTION(pNtHeaders);
const int nSectionCnts = pNtHeaders->FileHeader.NumberOfSections;
for (int i = 0; i < nSectionCnts; i++)
{
if (0 == strcmp((char*)pSecHeader->Name, ".text"))
{
const char* pCodeEntry = (const char*)((BYTE*)pDosHeader + pSecHeader->PointerToRawData);
SaveFile("CodeSegInfo.txt", pCodeEntry, pSecHeader->SizeOfRawData);
return true;
}
pSecHeader++;
}
return false;
}
在取偏移地址时,注意不要使用 IMAGE_NT_HEADERS32_OptionalHeader_BaseOfCode 代码段基偏移地址。参见MSDN中的解释IMAGE_OPTIONAL_HEADER32 structure
BaseOfCode
A pointer to the beginning of the code section, relative to the image base.
该字段表示代码段相对虚拟地址(RVA),它是相对于镜像载入地址的偏移。镜像载入地址是加载器加载PE文件时设置的地址。在上述实例代码中,使用的偏移地址是 PointerToRawData
,参考MSDN中的解释IMAGE_SECTION_HEADER structure
PointerToRawData
A file pointer to the first page within the COFF file. This value must be a multiple of the FileAlignment member of the IMAGE_OPTIONAL_HEADER structure. If a section contains only uninitialized data, set this member is zero.
SizeOfRawData
The size of the initialized data on disk, in bytes. This value must be a multiple of the FileAlignment member of the IMAGE_OPTIONAL_HEADER structure. If this value is less than the VirtualSize member, the remainder of the section is filled with zeroes. If the section contains only uninitialized data, the member is zero.
它是对应节信息原始数据相对于PE文件首部的偏移,SizeOfRawData
是原始数据的长度,Misc.VirtualSize
是实际加载到内存中的大小,当Misc.VirtualSize
大于SizeOfRawData
时,该节剩余填充0.
读取进程空间代码段信息
读取进程空间代码段信息,大致的思路如下:
- 根据进程名称,获得进程 PID 信息
- 根据 PID 信息,获得进程名称对应的模块加载基址和大小
- 从上述基址开始读取指定大小的内存数据
- 从内存数据中解析得到代码段偏移和大小
- 保存得到的代码段数据
bool GetProcessCodeSegInfoTesx(const char* pEXENAME)
{
bool bRet = false;
int nPid = GetProcessId(pEXENAME);
MODULEENTRY32 moduleInfo = { 0 };
if (!GetProcessModule(nPid, pEXENAME, &moduleInfo, sizeof(moduleInfo)))
{
printf("can't find %s dll in %s", pEXENAME, pEXENAME);
return false;
}
HANDLE hHandle = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE, false, nPid);
char* pBuffer = new char[moduleInfo.modBaseSize + 1];
memset(pBuffer, 0, moduleInfo.modBaseSize + 1);
DWORD dwByteOfRead = 0;
ReadProcessMemory(hHandle, (LPCVOID)moduleInfo.modBaseAddr, pBuffer, moduleInfo.modBaseSize, &dwByteOfRead);
if (moduleInfo.modBaseSize == dwByteOfRead)
{
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuffer;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)pDosHeader + pDosHeader->e_lfanew);
if (pDosHeader && pDosHeader->e_magic == IMAGE_DOS_SIGNATURE && pNtHeaders && pNtHeaders->Signature == IMAGE_NT_SIGNATURE)
{
DWORD dwBaseOfCode = pNtHeaders->OptionalHeader.BaseOfCode; // 代码段偏移地址
DWORD dwSizeOfCode = pNtHeaders->OptionalHeader.SizeOfCode; // 代码段大小
SaveFile("ProcessCodeInfo.txt", pBuffer + dwBaseOfCode, dwSizeOfCode);
bRet = true;
}
}
delete[]pBuffer;
pBuffer = NULL;
return bRet;
}
读取进程空间的代码段信息需要注意,如果是获取当前进程的模块信息,不需要提权,如果是获取其他进程的模块信息,则需要提权,否则会没有权限访问。