讀取代碼段實踐小結

由於項目須要,須要讀取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

相關文章
相關標籤/搜索