Search code examples
delphix8664-bitreadprocessmemory

32/64-bit ReadProcessMemory issues


I always used this code on x86 machines without problems:

PIDHandle:= OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_OPERATION or
                                          PROCESS_VM_READ, false, Struct.th32ProcessID);
if (PIDHandle <> 0)
 ScanMemory(PIDHandle, Struct.szExeFile);

procedure TForm1.ScanMemory(PIDHandle: THandle; const ProcessName: string);
var
  MemStart, ReceivedBytes: SIZE_T;
  MemInfo: MEMORY_BASIC_INFORMATION;
begin
  MemStart:= 0;
  while (VirtualQueryEx(PIDHandle, Pointer(MemStart), MemInfo, SizeOf(MemInfo)) <> 0) do
  begin
    if ((MemInfo.State = MEM_COMMIT) and (not (MemInfo.Protect = PAGE_GUARD)
    or (MemInfo.Protect = PAGE_NOACCESS)) and (MemInfo.Protect = PAGE_READWRITE)) then
    begin
      SetLength(Buff, MemInfo.RegionSize);
      if (ReadProcessMemory(PIDHandle, MemInfo.BaseAddress, Buff,
          MemInfo.RegionSize, ReceivedBytes)) then
        begin
          //do particular stuff with memory
        end; //if readprocessmemory
    end; //if mempages
    MemStart:= MemStart + MemInfo.RegionSize;
  end; 
end; 

Now I compiled a x64 binary and it stops responding in some random processes, which I'm not sure if they are x86/x64...

Are there any known issue on running ReadProcessMemory from a x64 bin, into x86 processes? Is really necessary to keep a x86 bin to read memory of other x86 processes and a x64 bin to read x64 processes? Or there are any workaround?


Solution

  • The problem is not with ReadProcessMemory(), it is with VirtualQueryEx().

    The MEMORY_BASIC_INFORMATION documentation says:

    To enable a debugger to debug a target that is running on a different architecture (32-bit versus 64-bit), use one of the explicit forms of this structure.

    typedef struct _MEMORY_BASIC_INFORMATION32 {
        DWORD BaseAddress;
        DWORD AllocationBase;
        DWORD AllocationProtect;
        DWORD RegionSize;
        DWORD State;
        DWORD Protect;
        DWORD Type;
    } MEMORY_BASIC_INFORMATION32, *PMEMORY_BASIC_INFORMATION32;
    
    typedef struct DECLSPEC_ALIGN(16) _MEMORY_BASIC_INFORMATION64 {
        ULONGLONG BaseAddress;
        ULONGLONG AllocationBase;
        DWORD     AllocationProtect;
        DWORD     __alignment1;
        ULONGLONG RegionSize;
        DWORD     State;
        DWORD     Protect;
        DWORD     Type;
        DWORD     __alignment2;
    } MEMORY_BASIC_INFORMATION64, *PMEMORY_BASIC_INFORMATION64;
    

    So, use MEMORY_BASIC_INFORMATION32 when querying a 32bit process, and use MEMORY_BASIC_INFORMATION64 when querying a 64bit process. However, Delphi does not declare these record types, so you will have to do so manually in your code.

    Delphi's declaration of MEMORY_BASIC_INFORMATION in the Windows unit is modeled after the old 32-bit version:

    typedef struct _MEMORY_BASIC_INFORMATION {
      PVOID  BaseAddress;
      PVOID  AllocationBase;
      DWORD  AllocationProtect;
      SIZE_T RegionSize;
      DWORD  State;
      DWORD  Protect;
      DWORD  Type;
    } MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
    
    PMemoryBasicInformation = ^TMemoryBasicInformation;
    _MEMORY_BASIC_INFORMATION = record
      BaseAddress : Pointer;
      AllocationBase : Pointer;
      AllocationProtect : DWORD;
      RegionSize : SIZE_T;
      State : DWORD;
      Protect : DWORD;
      Type_9 : DWORD;
    end;
    {$EXTERNALSYM _MEMORY_BASIC_INFORMATION}
    TMemoryBasicInformation = _MEMORY_BASIC_INFORMATION;
    MEMORY_BASIC_INFORMATION = _MEMORY_BASIC_INFORMATION;
    {$EXTERNALSYM MEMORY_BASIC_INFORMATION}
    

    This only works when used in a 32-bit calling process, not when used in a 64-bit calling process.

    Use IsWow64Process() to check whether PIDHandle is a handle to a 32-bit or 64-bit process, and then scan its memory using the appropriate record type, eg:

    // 32-bit and 64-bit processes can scan the full address space of a 32-bit process,
    // but a 32-bit process cannot scan the full address space of a 64-bit process
    
    type
      PMEMORY_BASIC_INFORMATION32 = ^MEMORY_BASIC_INFORMATION32;
      MEMORY_BASIC_INFORMATION32 = record
        BaseAddress: DWORD;
        AllocationBase: DWORD;
        AllocationProtect: DWORD;
        RegionSize: DWORD;
        State: DWORD;
        Protect: DWORD;
        _Type: DWORD;
      end;
    
      {$IFDEF WIN64}
      PMEMORY_BASIC_INFORMATION64 = ^MEMORY_BASIC_INFORMATION64;
      {$ALIGN 16}
      MEMORY_BASIC_INFORMATION64 = record
        BaseAddress: ULONGLONG;
        AllocationBase: ULONGLONG;
        AllocationProtect: DWORD;
        _alignment1: DWORD;
        RegionSize: ULONGLONG;
        State: DWORD;
        Protect: DWORD;
        _Type: DWORD;
        _alignment2: DWORD;
      end;
      {$ENDIF}
    
    function VirtualQueryEx32(hProcess: THandle; lpAddress: Pointer; var lpBuffer: MEMORY_BASIC_INFORMATION32; dwLength: SIZE_T): SIZE_T; stdcall; external 'kernel32' name 'VirtualQueryEx';
    
    {$IFDEF WIN64}
    function VirtualQueryEx64(hProcess: THandle; lpAddress: Pointer; var lpBuffer: MEMORY_BASIC_INFORMATION64; dwLength: SIZE_T): SIZE_T; stdcall; external 'kernel32' name 'VirtualQueryEx';
    {$ENDIF}
    
    procedure TForm1.DoSomethingWithBuff(const ProcessName: string);
    begin
      // do particular stuff with Buff
    end;
    
    procedure TForm1.ScanMemory32(PIDHandle: THandle; const ProcessName: string);
    var
      MemStart: DWORD;
      ReceivedBytes: SIZE_T;
      MemInfo: MEMORY_BASIC_INFORMATION32;
    begin
      MemStart := 0;
      while (VirtualQueryEx32(PIDHandle, Pointer(MemStart), MemInfo, SizeOf(MemInfo)) <> 0) do
      begin
        if ((MemInfo.State = MEM_COMMIT) and (not (MemInfo.Protect = PAGE_GUARD)
        or (MemInfo.Protect = PAGE_NOACCESS)) and (MemInfo.Protect = PAGE_READWRITE)) then
        begin
          SetLength(Buff, MemInfo.RegionSize);
          if (ReadProcessMemory(PIDHandle, Pointer(MemInfo.BaseAddress), Buff,
              MemInfo.RegionSize, ReceivedBytes)) then
          begin
            DoSomethingWithBuff(ProcessName);
          end;
        end;
        Inc(MemStart, MemInfo.RegionSize);
      end; 
    end;
    
    {$IFDEF WIN64}
    procedure TForm1.ScanMemory64(PIDHandle: THandle; const ProcessName: string);
    var
      MemStart: ULONGLONG;
      ReceivedBytes: SIZE_T;
      MemInfo: MEMORY_BASIC_INFORMATION64;
    begin
      MemStart := 0;
      while (VirtualQueryEx64(PIDHandle, Pointer(MemStart), MemInfo, SizeOf(MemInfo)) <> 0) do
      begin
        if ((MemInfo.State = MEM_COMMIT) and (not (MemInfo.Protect = PAGE_GUARD)
        or (MemInfo.Protect = PAGE_NOACCESS)) and (MemInfo.Protect = PAGE_READWRITE)) then
        begin
          SetLength(Buff, MemInfo.RegionSize);
          if (ReadProcessMemory(PIDHandle, Pointer(MemInfo.BaseAddress), Buff,
              MemInfo.RegionSize, ReceivedBytes)) then
          begin
            DoSomethingWithBuff(ProcessName);
          end;
        end;
        Inc(MemStart, MemInfo.RegionSize);
      end; 
    end;
    {$ENDIF}
    
    procedure TForm1.ScanMemory(PIDHandle: THandle; const ProcessName: string);
    var
      Is32Bit: BOOL;
    begin
      if not IsWow64Process(PIDHandle, @Is32Bit) then
        RaiseLastOSError;
    
      if Is32Bit then begin
        ScanMemory32(PIDHandle, ProcessName);
      end
      {$IFDEF WIN64}
      else begin
        ScanMemory64(PIDHandle, ProcessName);
      end
      {$ENDIF};
    end;