读取代码段实践小结

由于项目须要,须要读取PE文件的代码段信息和进程内的代码段信息,用于防破解的数据准备。从安全防御的角度上看,能防一点是一点。下面分别从读取PE文件和进程空间的代码段信息来小结。windows

读取PE文件的代码段信息

准备知识参见 windows PE文件结构及其加载机制,这篇文章讲解的很全面,颇有参考价值。完成此项功能的思路是,将PE文件读取到内存中,定位到.text段在PE文件中的偏移和代码段长度,将结果写入本地文件便可。api

大体的代码流程以下:安全

// 得到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 structureless

BaseOfCode
A pointer to the beginning of the code section, relative to the image base.

该字段表示代码段相对虚拟地址(RVA),它是相对于镜像载入地址的偏移。镜像载入地址是加载器加载PE文件时设置的地址。在上述实例代码中,使用的偏移地址是 PointerToRawData,参考MSDN中的解释IMAGE_SECTION_HEADER structurethis

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..net

读取进程空间代码段信息

读取进程空间代码段信息,大体的思路以下:code

  1. 根据进程名称,得到进程 PID 信息
  2. 根据 PID 信息,得到进程名称对应的模块加载基址和大小
  3. 从上述基址开始读取指定大小的内存数据
  4. 从内存数据中解析获得代码段偏移和大小
  5. 保存获得的代码段数据
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;
}

读取进程空间的代码段信息须要注意,若是是获取当前进程的模块信息,不须要提权,若是是获取其余进程的模块信息,则须要提权,不然会没有权限访问。blog

相关文章
相关标签/搜索