Win32 ReadProcessMemory API 的问题

Posted

技术标签:

【中文标题】Win32 ReadProcessMemory API 的问题【英文标题】:Issues with Win32 ReadProcessMemory API 【发布时间】:2020-06-30 21:05:30 【问题描述】:

我正在编写一个简单的应用程序来执行进程空心化,它启动一个 64 位进程,然后应用程序使用 NtQueryInformationProcess 获取 PebBaseAddress,我尝试获取 Peb 的第 6 个成员,即 BaseAddressofImage 但我得到了返回的这个奇怪的地址不正确:

pebImageBaseOffset 为:0000000000000000

这是我正在使用的代码:

#include <iostream>
#include <Windows.h>
#include <winternl.h>
#include <tchar.h>
#pragma comment(lib, "ntdll")

int main()

    // create destination process - this is the process to be hollowed out
    LPSTARTUPINFOA si = new STARTUPINFOA();
    LPPROCESS_INFORMATION pi = new PROCESS_INFORMATION();
    PROCESS_BASIC_INFORMATION *pbi = new PROCESS_BASIC_INFORMATION();
    DWORD returnLenght = 0;
    CreateProcessA(NULL, (LPSTR)"c:\\windows\\system32\\calc.exe", NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, si, pi);
    HANDLE destProcess = pi->hProcess;

    // get destination imageBase offset address from the PEB
    NtQueryInformationProcess(destProcess, ProcessBasicInformation, pbi, sizeof(PROCESS_BASIC_INFORMATION), &returnLenght);
    DWORD pebImageBaseOffset = (DWORD)pbi->PebBaseAddress + 8;

    // get destination imageBaseAddress
    LPVOID destImageBase = 0;
    SIZE_T bytesRead = NULL;
    ReadProcessMemory(destProcess, (LPCVOID)pebImageBaseOffset, &destImageBase, 4, &bytesRead);
    std::cout << "pebImageBaseOffset is: " << destImageBase << std::endl;
    std::cin.get();

此代码被编译并链接为 64 位 PE 可执行文件。我在哪里错了?我知道 calc.exe 映像的实际基地址是 0x7ff7ab750000。

如果此代码被编译并链接为 32 位 PE 可执行文件,它可以工作,所以我认为我的问题与指针大小和指针运算有关,但我不是一个经验丰富的 C++ 程序员,我必须监督一些事情。

我尝试复制以下代码,但无法让它与 64 位可执行文件一起使用,这可能是因为指针截断相关问题:

#include <iostream>
#include <Windows.h>
#include <winternl.h>
#pragma comment(lib, "ntdll")

using NtUnmapViewOfSection = NTSTATUS(WINAPI*)(HANDLE, PVOID);

typedef struct BASE_RELOCATION_BLOCK 
    DWORD PageAddress;
    DWORD BlockSize;
 BASE_RELOCATION_BLOCK, *PBASE_RELOCATION_BLOCK;

typedef struct BASE_RELOCATION_ENTRY 
    USHORT Offset : 12;
    USHORT Type : 4;
 BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;

int main()

    // create destination process - this is the process to be hollowed out
    LPSTARTUPINFOA si = new STARTUPINFOA();
    LPPROCESS_INFORMATION pi = new PROCESS_INFORMATION();
    PROCESS_BASIC_INFORMATION *pbi = new PROCESS_BASIC_INFORMATION();
    DWORD returnLenght = 0;
    CreateProcessA(NULL, (LPSTR)"c:\\windows\\syswow64\\notepad.exe", NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, si, pi);
    HANDLE destProcess = pi->hProcess;

    // get destination imageBase offset address from the PEB
    NtQueryInformationProcess(destProcess, ProcessBasicInformation, pbi, sizeof(PROCESS_BASIC_INFORMATION), &returnLenght);
    DWORD pebImageBaseOffset = (DWORD)pbi->PebBaseAddress + 8; 
    
    // get destination imageBaseAddress
    LPVOID destImageBase = 0;
    SIZE_T bytesRead = NULL;
    ReadProcessMemory(destProcess, (LPCVOID)pebImageBaseOffset, &destImageBase, 4, &bytesRead);

    // read source file - this is the file that will be executed inside the hollowed process
    HANDLE sourceFile = CreateFileA("C:\\temp\\regshot.exe", GENERIC_READ,  NULL, NULL, OPEN_ALWAYS, NULL, NULL);
    DWORD sourceFileSize = GetFileSize(sourceFile, NULL);
    LPDWORD fileBytesRead = 0;
    LPVOID sourceFileBytesBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sourceFileSize);
    ReadFile(sourceFile, sourceFileBytesBuffer, sourceFileSize, NULL, NULL);
    
    // get source image size
    PIMAGE_DOS_HEADER sourceImageDosHeaders = (PIMAGE_DOS_HEADER)sourceFileBytesBuffer;
    PIMAGE_NT_HEADERS sourceImageNTHeaders = (PIMAGE_NT_HEADERS)((DWORD)sourceFileBytesBuffer + sourceImageDosHeaders->e_lfanew);
    SIZE_T sourceImageSize = sourceImageNTHeaders->OptionalHeader.SizeOfImage;

    // carve out the destination image
    NtUnmapViewOfSection myNtUnmapViewOfSection = (NtUnmapViewOfSection)(GetProcAddress(GetModuleHandleA("ntdll"), "NtUnmapViewOfSection"));
    myNtUnmapViewOfSection(destProcess, destImageBase);

    // allocate new memory in destination image for the source image
    LPVOID newDestImageBase = VirtualAllocEx(destProcess, destImageBase, sourceImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    destImageBase = newDestImageBase;

    // get delta between sourceImageBaseAddress and destinationImageBaseAddress
    DWORD deltaImageBase = (DWORD)destImageBase - sourceImageNTHeaders->OptionalHeader.ImageBase;

    // set sourceImageBase to destImageBase and copy the source Image headers to the destination image
    sourceImageNTHeaders->OptionalHeader.ImageBase = (DWORD)destImageBase;
    WriteProcessMemory(destProcess, newDestImageBase, sourceFileBytesBuffer, sourceImageNTHeaders->OptionalHeader.SizeOfHeaders, NULL);

    // get pointer to first source image section
    PIMAGE_SECTION_HEADER sourceImageSection = (PIMAGE_SECTION_HEADER)((DWORD)sourceFileBytesBuffer + sourceImageDosHeaders->e_lfanew + sizeof(IMAGE_NT_HEADERS32));
    PIMAGE_SECTION_HEADER sourceImageSectionOld = sourceImageSection;
    int err = GetLastError();

    // copy source image sections to destination
    for (int i = 0; i < sourceImageNTHeaders->FileHeader.NumberOfSections; i++)
    
        PVOID destinationSectionLocation = (PVOID)((DWORD)destImageBase + sourceImageSection->VirtualAddress);
        PVOID sourceSectionLocation = (PVOID)((DWORD)sourceFileBytesBuffer + sourceImageSection->PointerToRawData);
        WriteProcessMemory(destProcess, destinationSectionLocation, sourceSectionLocation, sourceImageSection->SizeOfRawData, NULL);
        sourceImageSection++;
    

    // get address of the relocation table
    IMAGE_DATA_DIRECTORY relocationTable = sourceImageNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
    
    // patch the binary with relocations
    sourceImageSection = sourceImageSectionOld;
    for (int i = 0; i < sourceImageNTHeaders->FileHeader.NumberOfSections; i++)
    
        BYTE* relocSectionName = (BYTE*)".reloc";
        if (memcmp(sourceImageSection->Name, relocSectionName, 5) != 0) 
        
            sourceImageSection++;
            continue;
        

        DWORD sourceRelocationTableRaw = sourceImageSection->PointerToRawData;
        DWORD relocationOffset = 0;

        while (relocationOffset < relocationTable.Size) 
            PBASE_RELOCATION_BLOCK relocationBlock = (PBASE_RELOCATION_BLOCK)((DWORD)sourceFileBytesBuffer + sourceRelocationTableRaw + relocationOffset);
            relocationOffset += sizeof(BASE_RELOCATION_BLOCK);
            DWORD relocationEntryCount = (relocationBlock->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
            PBASE_RELOCATION_ENTRY relocationEntries = (PBASE_RELOCATION_ENTRY)((DWORD)sourceFileBytesBuffer + sourceRelocationTableRaw + relocationOffset);

            for (DWORD y = 0; y < relocationEntryCount; y++)
            
                relocationOffset += sizeof(BASE_RELOCATION_ENTRY);

                if (relocationEntries[y].Type == 0)
                
                    continue;
                

                DWORD patchAddress = relocationBlock->PageAddress + relocationEntries[y].Offset;
                DWORD patchedBuffer = 0;
                ReadProcessMemory(destProcess,(LPCVOID)((DWORD)destImageBase + patchAddress), &patchedBuffer, sizeof(DWORD), &bytesRead);
                patchedBuffer += deltaImageBase;

                WriteProcessMemory(destProcess, (PVOID)((DWORD)destImageBase + patchAddress), &patchedBuffer, sizeof(DWORD), fileBytesRead);
                int a = GetLastError();
            
        
    

    // get context of the dest process thread
    LPCONTEXT context = new CONTEXT();
    context->ContextFlags = CONTEXT_INTEGER;
    GetThreadContext(pi->hThread, context);

    // update dest image entry point to the new entry point of the source image and resume dest image thread
    DWORD patchedEntryPoint = (DWORD)destImageBase + sourceImageNTHeaders->OptionalHeader.AddressOfEntryPoint;
    context->Eax = patchedEntryPoint;
    SetThreadContext(pi->hThread, context);
    ResumeThread(pi->hThread);

    return 0;

【问题讨论】:

绝对没有错误处理。这不是一个学习的好方法,为什么你的程序会失败。好吧,这并不完全正确。有 次调用GetLastError。但它们都返回不确定的值。 为什么要将 PebBaseAddress 转换为 64 位 exe 的 DWORD? @JonathanPotter,我尝试转换为 unsigned long long ,但这并没有真正帮助...... 您的代码中还有其他类型的转换,这在 64 位中没有任何意义。从某个地方复制代码是可以的,但您应该仔细阅读并尝试自己理解它。 第一步,添加错误检查 【参考方案1】:

第一个代码示例:在x64下运行时,请改用ULONG_PTR,只要指针不被截断即可。并且需要将偏移量更改为 16(ImageBaseAddress 在 x86 中为 PEB+8,在 x64 中为 PEB+16)。

ReadProcessMemory中,需要将4改为8,即x64下ULONG_PTR的大小。

x64 下,

#include <iostream>
#include <Windows.h>
#include <winternl.h>
#include <tchar.h>
#pragma comment(lib, "ntdll")

int main()

    // create destination process - this is the process to be hollowed out
    LPSTARTUPINFOA si = new STARTUPINFOA();
    LPPROCESS_INFORMATION pi = new PROCESS_INFORMATION();
    PROCESS_BASIC_INFORMATION* pbi = new PROCESS_BASIC_INFORMATION();
    ULONG returnLenght = 0;
    CreateProcessA(NULL, (LPSTR)"c:\\windows\\system32\\calc.exe", NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, si, pi);
    HANDLE destProcess = pi->hProcess;

    // get destination imageBase offset address from the PEB
    NtQueryInformationProcess(destProcess, ProcessBasicInformation, pbi, sizeof(PROCESS_BASIC_INFORMATION), &returnLenght);
    ULONG_PTR  pebImageBaseOffset = (ULONG_PTR)pbi->PebBaseAddress + 16;

    // get destination imageBaseAddress
    LPVOID destImageBase = 0;
    SIZE_T bytesRead = NULL;
    ReadProcessMemory(destProcess, (LPCVOID)pebImageBaseOffset, &destImageBase, 8, &bytesRead);
    std::cout << "pebImageBaseOffset is: " << destImageBase << std::endl;
    std::cin.get();

第二个代码示例。 x64下,除了上面提到的修改外,还需要将IMAGE_NT_HEADERS32改为_IMAGE_NT_HEADERS64

#include <iostream>
#include <Windows.h>
#include <winternl.h>
#pragma comment(lib, "ntdll")

using NtUnmapViewOfSection = NTSTATUS(WINAPI*)(HANDLE, PVOID);

typedef struct BASE_RELOCATION_BLOCK 
    DWORD PageAddress;
    DWORD BlockSize;
 BASE_RELOCATION_BLOCK, * PBASE_RELOCATION_BLOCK;

typedef struct BASE_RELOCATION_ENTRY 
    USHORT Offset : 12;
    USHORT Type : 4;
 BASE_RELOCATION_ENTRY, * PBASE_RELOCATION_ENTRY;


int main()

    // create destination process - this is the process to be hollowed out
    LPSTARTUPINFOA si = new STARTUPINFOA();
    LPPROCESS_INFORMATION pi = new PROCESS_INFORMATION();
    PROCESS_BASIC_INFORMATION* pbi = new PROCESS_BASIC_INFORMATION();
    ULONG returnLenght = 0;
    CreateProcessA(NULL, (LPSTR)"c:\\windows\\syswow64\\notepad.exe", NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, si, pi);
    HANDLE destProcess = pi->hProcess;
    
    // get destination imageBase offset address from the PEB
    NtQueryInformationProcess(destProcess, ProcessBasicInformation, pbi, sizeof(PROCESS_BASIC_INFORMATION), &returnLenght);
    ULONG_PTR pebImageBaseOffset = (ULONG_PTR)pbi->PebBaseAddress + 16;
  
    // get destination imageBaseAddress
    LPVOID destImageBase = 0;
    SIZE_T bytesRead = NULL;
    ReadProcessMemory(destProcess, (LPCVOID)pebImageBaseOffset, &destImageBase, sizeof(ULONG_PTR), &bytesRead);

    // read source file - this is the file that will be executed inside the hollowed process
    HANDLE sourceFile = CreateFileA("c:\\windows\\system32\\calc.exe", GENERIC_READ, NULL, NULL, OPEN_ALWAYS, NULL, NULL);
    ULONG_PTR sourceFileSize = GetFileSize(sourceFile, NULL);
    SIZE_T fileBytesRead = 0;
    LPVOID sourceFileBytesBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sourceFileSize);
    ReadFile(sourceFile, sourceFileBytesBuffer, sourceFileSize, NULL, NULL);

    // get source image size
    PIMAGE_DOS_HEADER sourceImageDosHeaders = (PIMAGE_DOS_HEADER)sourceFileBytesBuffer;
    PIMAGE_NT_HEADERS sourceImageNTHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)sourceFileBytesBuffer + sourceImageDosHeaders->e_lfanew);
    SIZE_T sourceImageSize = sourceImageNTHeaders->OptionalHeader.SizeOfImage;

    // carve out the destination image
    NtUnmapViewOfSection myNtUnmapViewOfSection = (NtUnmapViewOfSection)(GetProcAddress(GetModuleHandleA("ntdll"), "NtUnmapViewOfSection"));
    myNtUnmapViewOfSection(destProcess, destImageBase);

    // allocate new memory in destination image for the source image
    LPVOID newDestImageBase = VirtualAllocEx(destProcess, destImageBase, sourceImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    destImageBase = newDestImageBase;

    // get delta between sourceImageBaseAddress and destinationImageBaseAddress
    ULONG_PTR deltaImageBase = (ULONG_PTR)destImageBase - sourceImageNTHeaders->OptionalHeader.ImageBase;

    // set sourceImageBase to destImageBase and copy the source Image headers to the destination image
    sourceImageNTHeaders->OptionalHeader.ImageBase = (ULONG_PTR)destImageBase;
    WriteProcessMemory(destProcess, newDestImageBase, sourceFileBytesBuffer, sourceImageNTHeaders->OptionalHeader.SizeOfHeaders, NULL);
    // get pointer to first source image section
    PIMAGE_SECTION_HEADER sourceImageSection = (PIMAGE_SECTION_HEADER)((ULONG_PTR)sourceFileBytesBuffer + sourceImageDosHeaders->e_lfanew + sizeof(_IMAGE_NT_HEADERS64)); //IMAGE_NT_HEADERS32
    PIMAGE_SECTION_HEADER sourceImageSectionOld = sourceImageSection;

    // copy source image sections to destination
    for (int i = 0; i < sourceImageNTHeaders->FileHeader.NumberOfSections; i++)
    
        PVOID destinationSectionLocation = (PVOID)((ULONG_PTR)destImageBase + sourceImageSection->VirtualAddress);
        PVOID sourceSectionLocation = (PVOID)((ULONG_PTR)sourceFileBytesBuffer + sourceImageSection->PointerToRawData);
        WriteProcessMemory(destProcess, destinationSectionLocation, sourceSectionLocation, sourceImageSection->SizeOfRawData, NULL);
        sourceImageSection++;
    

    // get address of the relocation table
    IMAGE_DATA_DIRECTORY relocationTable = sourceImageNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];

    // patch the binary with relocations
    sourceImageSection = sourceImageSectionOld;
    for (int i = 0; i < sourceImageNTHeaders->FileHeader.NumberOfSections; i++)
    
        BYTE* relocSectionName = (BYTE*)".reloc";
        if (memcmp(sourceImageSection->Name, relocSectionName, 5) != 0)
        
            sourceImageSection++;
            continue;
        

        ULONG_PTR sourceRelocationTableRaw = sourceImageSection->PointerToRawData;
        ULONG_PTR relocationOffset = 0;

        while (relocationOffset < relocationTable.Size) 
            PBASE_RELOCATION_BLOCK relocationBlock = (PBASE_RELOCATION_BLOCK)((ULONG_PTR)sourceFileBytesBuffer + sourceRelocationTableRaw + relocationOffset);
            relocationOffset += sizeof(BASE_RELOCATION_BLOCK);
            ULONG_PTR relocationEntryCount = (relocationBlock->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
            PBASE_RELOCATION_ENTRY relocationEntries = (PBASE_RELOCATION_ENTRY)((ULONG_PTR)sourceFileBytesBuffer + sourceRelocationTableRaw + relocationOffset);

            for (ULONG_PTR y = 0; y < relocationEntryCount; y++)
            
                relocationOffset += sizeof(BASE_RELOCATION_ENTRY);

                if (relocationEntries[y].Type == 0)
                
                    continue;
                

                ULONG_PTR patchAddress = relocationBlock->PageAddress + relocationEntries[y].Offset;
                ULONG_PTR patchedBuffer = 0;
                ReadProcessMemory(destProcess, (LPCVOID)((ULONG_PTR)destImageBase + patchAddress), &patchedBuffer, sizeof(ULONG_PTR), &bytesRead);
                patchedBuffer += deltaImageBase;

                WriteProcessMemory(destProcess, (PVOID)((ULONG_PTR)destImageBase + patchAddress), &patchedBuffer, sizeof(ULONG_PTR), &fileBytesRead);
            
        
    
    

    return 0;

【讨论】:

这是正确的,虽然你错过了代码的结尾...检查我的代码并编辑你的代码,因为它有更多的赞成票,谢谢你的帮助! 我不敢相信你为 64 位编写了另一个函数!使用 IMAGE_NT_HEADERS 而不是 IMAGE_NT_HEADERS32 或 IMAGE_NT_HEADERS64,您可以轻松编写一个适用于 32 位和 64 位的函数。如果 32 和 64 之间仍有不同之处,请使用 #ifdef WIN64 ... #else ... #endif。您可以使用 sizeof(ULONG_PTR) 而不是将硬编码的 4 更改为 8,它适用于两种位数。【参考方案2】:

我关注了@Strive Sun - 上面的 MSFT 回答,并得到了下面显示的代码。但是,我注意到该代码仅在使用某些可执行文件时才有效,例如,如果我将 calc.exe 替换为 C:\Windows\System32\ 中的 cmd.exe 它失败,当我对 3 个文件运行文件实用程序时,它似乎 cmd.exe 与其他文件略有不同,这可能解释了它失败的原因,如果有人阅读本文确切知道为什么这两个文件不兼容,请告诉我。

我怀疑它与this有关:

cmd.exe: PE32+ executable (console) x86-64, for MS Windows
calc.exe: PE32+ executable (GUI) x86-64, for MS Windows
cleanmgr.exe: PE32+ executable (GUI) x86-64, for MS Windows

"...为了最大化兼容性,源图像的子系统应该设置为windows。编译器应该使用静态版本的运行库来去除对Visual C++运行时DLL的依赖。这样可以实现通过使用 /MT 或 /MTd 编译器选项。源映像的首选基地址(假设它有一个)必须与目标映像的基地址匹配,或者源必须包含重定位表并且映像需要重新定位到目标地址。出于兼容性原因,首选变基路由。/DYNAMICBASE 或 /FIXED:NO 链接器选项可用于生成重定位表..."

这是我最终使用的代码,但@strive sun 的解决方案基本上也是我最终做的。

#include <iostream>
#include <Windows.h>
#include <winternl.h>
#include <tchar.h>
#include "ntstatus.h"
#pragma comment(lib, "ntdll")

using NtUnmapViewOfSection = NTSTATUS(WINAPI*)(HANDLE, PVOID);

typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)(
    IN  HANDLE ProcessHandle,
    IN  PROCESSINFOCLASS ProcessInformationClass,
    OUT PVOID ProcessInformation,
    IN  ULONG ProcessInformationLength,
    OUT PULONG ReturnLength    OPTIONAL
    );

typedef struct BASE_RELOCATION_BLOCK 
    DWORD PageAddress;
    DWORD BlockSize;
 BASE_RELOCATION_BLOCK, *PBASE_RELOCATION_BLOCK;

typedef struct BASE_RELOCATION_ENTRY 
    USHORT Offset : 12;
    USHORT Type : 4;
 BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;

UINT_PTR GetPEBLocation(HANDLE hProcess)

    ULONG RequiredLen = 0;
    UINT_PTR PebAddress = NULL;
    PROCESS_BASIC_INFORMATION myProcessBasicInformation[5] =  0 ;

    if (NtQueryInformationProcess(hProcess, ProcessBasicInformation, myProcessBasicInformation, sizeof(PROCESS_BASIC_INFORMATION), &RequiredLen) == STATUS_SUCCESS)
    
        PebAddress = (UINT_PTR)myProcessBasicInformation->PebBaseAddress + 16;
    
    else
    
        if (NtQueryInformationProcess(hProcess, ProcessBasicInformation, myProcessBasicInformation, RequiredLen, &RequiredLen) == STATUS_SUCCESS)
        
            PebAddress = (UINT_PTR)myProcessBasicInformation->PebBaseAddress + 16;
        
    

    return PebAddress;


int main()

    // create destination process - this is the process to be hollowed out
    LPSTARTUPINFOA si = new STARTUPINFOA();
    LPPROCESS_INFORMATION pi = new PROCESS_INFORMATION();
    PROCESS_BASIC_INFORMATION *pbi = new PROCESS_BASIC_INFORMATION();
    CreateProcessA(NULL, (LPSTR)"c:\\windows\\system32\\cleanmgr.exe", NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, si, pi);
    HANDLE destProcess = pi->hProcess;
    LPVOID destProcessImageBase = (LPVOID)GetPEBLocation(destProcess);
    std::cout << "PEB image base address is at: " << destProcessImageBase << std::endl;
    
    // get destination imageBaseAddress
    LPVOID destImageBase = NULL;
    SIZE_T bytesRead = NULL;
    ReadProcessMemory(destProcess, (LPVOID)destProcessImageBase, &destImageBase, 8, &bytesRead);
    std::cout << "Image base address is at: " << destImageBase << std::endl;


    // read source file - this is the file that will be executed inside the hollowed process
    HANDLE sourceFile = CreateFileA("c:\\windows\\system32\\calc.exe", GENERIC_READ, NULL, NULL, OPEN_ALWAYS, NULL, NULL);
    SIZE_T sourceFileSize = GetFileSize(sourceFile, NULL);
    LPVOID sourceFileBytesBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sourceFileSize);
    ReadFile(sourceFile, sourceFileBytesBuffer, sourceFileSize, NULL, NULL);

    // get source image size
    PIMAGE_DOS_HEADER sourceImageDosHeaders = (PIMAGE_DOS_HEADER)sourceFileBytesBuffer;
    PIMAGE_NT_HEADERS sourceImageNTHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)sourceFileBytesBuffer + sourceImageDosHeaders->e_lfanew);
    SIZE_T sourceImageSize = sourceImageNTHeaders->OptionalHeader.SizeOfImage;
    std::cout << "LPVOID is: " << sizeof(LPVOID) << " DWORD is " << sizeof(DWORD) << " ULONGLONG is " << sizeof(ULONGLONG) << std::endl;
    // carve out the destination image
    NtUnmapViewOfSection myNtUnmapViewOfSection = (NtUnmapViewOfSection)(GetProcAddress(GetModuleHandleA("ntdll"), "NtUnmapViewOfSection"));
    myNtUnmapViewOfSection(destProcess, destImageBase);

    // allocate new memory in destination image for the source image
    LPVOID newDestImageBase = VirtualAllocEx(destProcess, destImageBase, sourceImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    destImageBase = newDestImageBase;

    // get delta between sourceImageBaseAddress and destinationImageBaseAddress
    ULONGLONG deltaImageBase = (ULONGLONG)destImageBase - sourceImageNTHeaders->OptionalHeader.ImageBase;

    // set sourceImageBase to destImageBase and copy the source Image headers to the destination image
    sourceImageNTHeaders->OptionalHeader.ImageBase = (ULONGLONG)destImageBase;
    WriteProcessMemory(destProcess, newDestImageBase, sourceFileBytesBuffer, sourceImageNTHeaders->OptionalHeader.SizeOfHeaders, NULL);

    // get pointer to first source image section
    PIMAGE_SECTION_HEADER sourceImageSection = (PIMAGE_SECTION_HEADER)((ULONGLONG)sourceFileBytesBuffer + sourceImageDosHeaders->e_lfanew + sizeof(IMAGE_NT_HEADERS64));
    PIMAGE_SECTION_HEADER sourceImageSectionOld = sourceImageSection;
    std::cout << "LPDWORD is: " << sizeof(LPDWORD) << std::endl;
    std::cout << "SIZE_T is: " << sizeof(SIZE_T) << std::endl;
    // copy source image sections to destination
    for (int i = 0; i < sourceImageNTHeaders->FileHeader.NumberOfSections; i++)
    
        PVOID destinationSectionLocation = (PVOID)((ULONGLONG)destImageBase + sourceImageSection->VirtualAddress);
        PVOID sourceSectionLocation = (PVOID)((ULONGLONG)sourceFileBytesBuffer + sourceImageSection->PointerToRawData);
        WriteProcessMemory(destProcess, destinationSectionLocation, sourceSectionLocation, sourceImageSection->SizeOfRawData, NULL);
        sourceImageSection++;
    

    // get address of the relocation table
    IMAGE_DATA_DIRECTORY relocationTable = sourceImageNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];




    // patch the binary with relocations
    sourceImageSection = sourceImageSectionOld;
    for (int i = 0; i < sourceImageNTHeaders->FileHeader.NumberOfSections; i++)
    
        BYTE* relocSectionName = (BYTE*)".reloc";
        if (memcmp(sourceImageSection->Name, relocSectionName, 5) != 0)
        
            sourceImageSection++;
            continue;
        

        ULONGLONG sourceRelocationTableRaw = sourceImageSection->PointerToRawData;
        DWORD relocationOffset = 0;

        while (relocationOffset < relocationTable.Size) 
            PBASE_RELOCATION_BLOCK relocationBlock = (PBASE_RELOCATION_BLOCK)((ULONGLONG)sourceFileBytesBuffer + sourceRelocationTableRaw + relocationOffset);
            relocationOffset += sizeof(BASE_RELOCATION_BLOCK);
            DWORD relocationEntryCount = (relocationBlock->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
            PBASE_RELOCATION_ENTRY relocationEntries = (PBASE_RELOCATION_ENTRY)((ULONGLONG)sourceFileBytesBuffer + sourceRelocationTableRaw + relocationOffset);

            for (DWORD y = 0; y < relocationEntryCount; y++)
            
                relocationOffset += sizeof(BASE_RELOCATION_ENTRY);

                if (relocationEntries[y].Type == 0)
                
                    continue;
                

                ULONGLONG patchAddress = relocationBlock->PageAddress + relocationEntries[y].Offset;
                ULONGLONG patchedBuffer = 0;
                ReadProcessMemory(destProcess, (LPCVOID)((ULONGLONG)destImageBase + patchAddress), &patchedBuffer, sizeof(ULONGLONG), &bytesRead);
                patchedBuffer += deltaImageBase;

                WriteProcessMemory(destProcess, (PVOID)((ULONGLONG)destImageBase + patchAddress), &patchedBuffer, sizeof(ULONGLONG),NULL);
                int a = GetLastError();
            
        
    
    // get context of the dest process thread
    LPCONTEXT context = new CONTEXT();
    context->ContextFlags = CONTEXT_INTEGER;
    GetThreadContext(pi->hThread, context);

    // update dest image entry point to the new entry point of the source image and resume dest image thread
    ULONGLONG patchedEntryPoint = (ULONGLONG)destImageBase + sourceImageNTHeaders->OptionalHeader.AddressOfEntryPoint;
    context->Rcx = patchedEntryPoint;
    SetThreadContext(pi->hThread, context);
    ResumeThread(pi->hThread);
    
    std::cin.get();
    return 0;

【讨论】:

嗨,很抱歉恢复旧线程。在这种情况下SetThreadContext 不会失败吗?我也在尝试这样的事情,但我无法在 x64 上设置易失性寄存器。 这段代码在 x64/Windows 10 上确实为我工作,我能够空出一个进程并且没有得到你提到的问题......你有错误吗?如果没有,请尝试使用不同的二进制文件...我记得我遇到了我认为与目标子系统有关的问题。 我实际上正在尝试使用您正在使用的相同可执行文件,但很可能我可能做错了其他事情。我只是担心这个其他线程***.com/questions/25004311/…

以上是关于Win32 ReadProcessMemory API 的问题的主要内容,如果未能解决你的问题,请参考以下文章

A log about Reading the memroy of Other Process in C++/WIN API--ReadProcessMemory()

使用 ReadProcessMemory 获取字符串值的访问冲突

delphi XE Berlin ReadProcessMemory WriteProcessMemory

ReadProcessMemory 总是读取 0

确定外部进程的主线程 ID(ReadProcessMemory - 错误 299)

ReadProcessMemory函数的参数如何设置