On my last post was show how get the environment variables of an external x86 process, now it’s time to show how access the same information for x64 (and x86) process from a 64 bits application.
In order to access the environment variables of a x64 process, you must use a 64 bits application, So if you compile the code posted in the last article in a 64 bits app, the code will still working but only for read the environment variables of x64 processes, this is because the offset of the fields of the records changes due to the Pointer and THandle types now have a 8 bytes size, and the structures which hold the data in a 32 bits process still using 4 bytes to represent the adresses (Pointers) and Handles (THandle). So the first step is create 2 new types to replace the Pointer and THandle types like so.
Pointer32 = ULONG; THANDLE32 = ULONG;
Now using these new types we can create a 32 bits version of the records to access the PEB.
type Pointer32 = ULONG; THANDLE32 = ULONG; _UNICODE_STRING32 = record Length: Word; MaximumLength: Word; Buffer: Pointer32; end; UNICODE_STRING32 = _UNICODE_STRING32; _RTL_DRIVE_LETTER_CURDIR32 = record Flags: Word; Length: Word; TimeStamp: ULONG; DosPath: UNICODE_STRING32; end; RTL_DRIVE_LETTER_CURDIR32 = _RTL_DRIVE_LETTER_CURDIR32; _CURDIR32 = record DosPath: UNICODE_STRING32; Handle: THANDLE32; end; CURDIR32 = _CURDIR32; _RTL_USER_PROCESS_PARAMETERS32 = record MaximumLength: ULONG; Length: ULONG; Flags: ULONG; DebugFlags: ULONG; ConsoleHandle: THANDLE32; ConsoleFlags: ULONG; StandardInput: THANDLE32; StandardOutput: THANDLE32; StandardError: THANDLE32; CurrentDirectory: CURDIR32; DllPath: UNICODE_STRING32; ImagePathName: UNICODE_STRING32; CommandLine: UNICODE_STRING32; Environment: Pointer32; StartingX: ULONG; StartingY: ULONG; CountX: ULONG; CountY: ULONG; CountCharsX: ULONG; CountCharsY: ULONG; FillAttribute: ULONG; WindowFlags: ULONG; ShowWindowFlags: ULONG; WindowTitle: UNICODE_STRING32; DesktopInfo: UNICODE_STRING32; ShellInfo: UNICODE_STRING32; RuntimeData: UNICODE_STRING32; CurrentDirectories: array[0..31] of RTL_DRIVE_LETTER_CURDIR32; end; RTL_USER_PROCESS_PARAMETERS32 = _RTL_USER_PROCESS_PARAMETERS32; PRTL_USER_PROCESS_PARAMETERS32 = ^RTL_USER_PROCESS_PARAMETERS32; _PEB32 = record Reserved1 : array [0..1] of Byte; BeingDebugged : Byte; Reserved2 : Byte; Reserved3 : array [0..1] of Pointer32; Ldr : Pointer32; ProcessParameters : Pointer32;//PRTL_USER_PROCESS_PARAMETERS; Reserved4 : array [0..102] of Byte; Reserved5 : array [0..51] of Pointer32; PostProcessInitRoutine : Pointer32; Reserved6 : array [0..127] of byte; Reserved7 : Pointer32; SessionId : ULONG; end; PEB32=_PEB32;
Reading the PEB Address of a X86 process from a 64 bits app
Additional to the above changes we must modify the code to get the PEB address of a 32 bits process, this can be done using the NtQueryInformationProcess function passing ProcessWow64Information value, this will return the PEB Adresss of a process running under WOW64.
NtQueryInformationProcess(ProcessHandle, ProcessWow64Information, @PEBBaseAddress32, SizeOf(PEBBaseAddress32), nil)
And now using the ReadProcessMemory function and the new defined types we can read the environment variables block.
//read the PEB structure if not ReadProcessMemory(ProcessHandle, PEBBaseAddress32, @Peb32, sizeof(Peb32), lpNumberOfBytesRead) then RaiseLastOSError else begin //read the RTL_USER_PROCESS_PARAMETERS structure if not ReadProcessMemory(ProcessHandle, Pointer(Peb32.ProcessParameters), @Rtl32, SizeOf(Rtl32), lpNumberOfBytesRead) then RaiseLastOSError else begin //get the size of the Env. variables block if VirtualQueryEx(ProcessHandle, Pointer(Rtl32.Environment), Mbi, SizeOf(Mbi))=0 then RaiseLastOSError else EnvStrLength :=(mbi.RegionSize -(ULONG_PTR(Pointer(Rtl32.Environment)) - ULONG_PTR(mbi.BaseAddress))); SetLength(EnvStrBlock, EnvStrLength); //read the content of the env. variables block if not ReadProcessMemory(ProcessHandle, Pointer(Rtl32.Environment), @EnvStrBlock[0], EnvStrLength, lpNumberOfBytesRead) then RaiseLastOSError else Result:=TEncoding.Unicode.GetString(EnvStrBlock); end; end;
The source code
Finally this is the full source code.
program NtQueryInformationProcess_EnvVarsX64; //Author Rodrigo Ruz (RRUZ) //2012-06-09 {$APPTYPE CONSOLE} {$IFNDEF UNICODE} this code only runs under unicode delphi versions{$ENDIF} {$R *.res} uses Classes, SysUtils, Windows; type Pointer32 = ULONG; THANDLE32 = ULONG; _UNICODE_STRING = record Length: Word; MaximumLength: Word; Buffer: LPWSTR; end; UNICODE_STRING = _UNICODE_STRING; //http://msdn.microsoft.com/en-us/library/windows/desktop/ms684280%28v=vs.85%29.aspx PROCESS_BASIC_INFORMATION = record Reserved1 : Pointer; PebBaseAddress: Pointer; Reserved2: array [0..1] of Pointer; UniqueProcessId: ULONG_PTR; Reserved3: Pointer; end; //http://undocumented.ntinternals.net/UserMode/Structures/RTL_DRIVE_LETTER_CURDIR.html _RTL_DRIVE_LETTER_CURDIR = record Flags: Word; Length: Word; TimeStamp: ULONG; DosPath: UNICODE_STRING; end; RTL_DRIVE_LETTER_CURDIR = _RTL_DRIVE_LETTER_CURDIR; _CURDIR = record DosPath: UNICODE_STRING; Handle: THANDLE; end; CURDIR = _CURDIR; //http://undocumented.ntinternals.net/UserMode/Structures/RTL_USER_PROCESS_PARAMETERS.html _RTL_USER_PROCESS_PARAMETERS = record MaximumLength: ULONG; Length: ULONG; Flags: ULONG; DebugFlags: ULONG; ConsoleHandle: THANDLE; ConsoleFlags: ULONG; StandardInput: THANDLE; StandardOutput: THANDLE; StandardError: THANDLE; CurrentDirectory: CURDIR; DllPath: UNICODE_STRING; ImagePathName: UNICODE_STRING; CommandLine: UNICODE_STRING; Environment: Pointer; StartingX: ULONG; StartingY: ULONG; CountX: ULONG; CountY: ULONG; CountCharsX: ULONG; CountCharsY: ULONG; FillAttribute: ULONG; WindowFlags: ULONG; ShowWindowFlags: ULONG; WindowTitle: UNICODE_STRING; DesktopInfo: UNICODE_STRING; ShellInfo: UNICODE_STRING; RuntimeData: UNICODE_STRING; CurrentDirectories: array[0..31] of RTL_DRIVE_LETTER_CURDIR; end; RTL_USER_PROCESS_PARAMETERS = _RTL_USER_PROCESS_PARAMETERS; PRTL_USER_PROCESS_PARAMETERS = ^RTL_USER_PROCESS_PARAMETERS; _PEB = record Reserved1 : array [0..1] of Byte; BeingDebugged : Byte; Reserved2 : Byte; Reserved3 : array [0..1] of Pointer; Ldr : Pointer; ProcessParameters : PRTL_USER_PROCESS_PARAMETERS; Reserved4 : array [0..102] of Byte; Reserved5 : array [0..51] of Pointer; PostProcessInitRoutine : Pointer; Reserved6 : array [0..127] of byte; Reserved7 : Pointer; SessionId : ULONG; end; PEB=_PEB; {$IFDEF CPUX64} _UNICODE_STRING32 = record Length: Word; MaximumLength: Word; Buffer: Pointer32; end; UNICODE_STRING32 = _UNICODE_STRING32; _RTL_DRIVE_LETTER_CURDIR32 = record Flags: Word; Length: Word; TimeStamp: ULONG; DosPath: UNICODE_STRING32; end; RTL_DRIVE_LETTER_CURDIR32 = _RTL_DRIVE_LETTER_CURDIR32; _CURDIR32 = record DosPath: UNICODE_STRING32; Handle: THANDLE32; end; CURDIR32 = _CURDIR32; _RTL_USER_PROCESS_PARAMETERS32 = record MaximumLength: ULONG; Length: ULONG; Flags: ULONG; DebugFlags: ULONG; ConsoleHandle: THANDLE32; ConsoleFlags: ULONG; StandardInput: THANDLE32; StandardOutput: THANDLE32; StandardError: THANDLE32; CurrentDirectory: CURDIR32; DllPath: UNICODE_STRING32; ImagePathName: UNICODE_STRING32; CommandLine: UNICODE_STRING32; Environment: Pointer32; StartingX: ULONG; StartingY: ULONG; CountX: ULONG; CountY: ULONG; CountCharsX: ULONG; CountCharsY: ULONG; FillAttribute: ULONG; WindowFlags: ULONG; ShowWindowFlags: ULONG; WindowTitle: UNICODE_STRING32; DesktopInfo: UNICODE_STRING32; ShellInfo: UNICODE_STRING32; RuntimeData: UNICODE_STRING32; CurrentDirectories: array[0..31] of RTL_DRIVE_LETTER_CURDIR32; end; RTL_USER_PROCESS_PARAMETERS32 = _RTL_USER_PROCESS_PARAMETERS32; PRTL_USER_PROCESS_PARAMETERS32 = ^RTL_USER_PROCESS_PARAMETERS32; _PEB32 = record Reserved1 : array [0..1] of Byte; BeingDebugged : Byte; Reserved2 : Byte; Reserved3 : array [0..1] of Pointer32; Ldr : Pointer32; ProcessParameters : Pointer32;//PRTL_USER_PROCESS_PARAMETERS; Reserved4 : array [0..102] of Byte; Reserved5 : array [0..51] of Pointer32; PostProcessInitRoutine : Pointer32; Reserved6 : array [0..127] of byte; Reserved7 : Pointer32; SessionId : ULONG; end; PEB32=_PEB32; {$ENDIF} function NtQueryInformationProcess(ProcessHandle : THandle; ProcessInformationClass : DWORD; ProcessInformation : Pointer; ProcessInformationLength : ULONG; ReturnLength : PULONG ): LongInt; stdcall; external 'ntdll.dll'; function NtQueryVirtualMemory(ProcessHandle : THandle; BaseAddress : Pointer; MemoryInformationClass : DWORD; MemoryInformation : Pointer; MemoryInformationLength : ULONG; ReturnLength : PULONG ): LongInt; stdcall; external 'ntdll.dll'; type TIsWow64Process = function(Handle:THandle; var IsWow64 : BOOL) : BOOL; stdcall; var _IsWow64Process : TIsWow64Process; procedure Init_IsWow64Process; var hKernel32 : Integer; begin hKernel32 := LoadLibrary(kernel32); if (hKernel32 = 0) then RaiseLastOSError; try _IsWow64Process := GetProcAddress(hkernel32, 'IsWow64Process'); finally FreeLibrary(hKernel32); end; end; function ProcessIsX64(hProcess: DWORD): Boolean; var IsWow64 : BOOL; begin Result := False; {$IFNDEF CPUX64} exit; {$ENDIF} if not Assigned(_IsWow64Process) then Init_IsWow64Process; if Assigned(_IsWow64Process) then begin if (_IsWow64Process(hProcess, IsWow64)) then Result := not IsWow64 else RaiseLastOSError; end; end; function GetEnvVarsPid(dwProcessId : DWORD): string; const STATUS_SUCCESS = $00000000; SE_DEBUG_NAME = 'SeDebugPrivilege'; ProcessWow64Information = 26; var ProcessHandle : THandle; ProcessBasicInfo : PROCESS_BASIC_INFORMATION; ReturnLength : DWORD; lpNumberOfBytesRead : ULONG_PTR; TokenHandle : THandle; lpLuid : TOKEN_PRIVILEGES; OldlpLuid : TOKEN_PRIVILEGES; Rtl : RTL_USER_PROCESS_PARAMETERS; Mbi : TMemoryBasicInformation; Peb : _PEB; EnvStrBlock : TBytes; EnvStrLength : ULONG; IsProcessx64 : Boolean; {$IFDEF CPUX64} PEBBaseAddress32 : Pointer; Peb32 : _PEB32; Rtl32 : RTL_USER_PROCESS_PARAMETERS32; {$ENDIF} begin Result:=''; if OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, TokenHandle) then begin try if not LookupPrivilegeValue(nil, SE_DEBUG_NAME, lpLuid.Privileges[0].Luid) then RaiseLastOSError else begin lpLuid.PrivilegeCount := 1; lpLuid.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; ReturnLength := 0; OldlpLuid := lpLuid; //Set the SeDebugPrivilege privilege if not AdjustTokenPrivileges(TokenHandle, False, lpLuid, SizeOf(OldlpLuid), OldlpLuid, ReturnLength) then RaiseLastOSError; end; ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, dwProcessId); if ProcessHandle=0 then RaiseLastOSError else try IsProcessx64 :=ProcessIsX64(ProcessHandle); {$IFNDEF CPUX64} if IsProcessx64 then raise Exception.Create('Only 32 bits processes are supported'); {$ENDIF} {$IFDEF CPUX64} if IsProcessx64 then begin {$ENDIF} // get the PROCESS_BASIC_INFORMATION to access to the PEB Address if (NtQueryInformationProcess(ProcessHandle,0{=>ProcessBasicInformation},@ProcessBasicInfo, SizeOf(ProcessBasicInfo), @ReturnLength)=STATUS_SUCCESS) and (ReturnLength=SizeOf(ProcessBasicInfo)) then begin //read the PEB struture if not ReadProcessMemory(ProcessHandle, ProcessBasicInfo.PEBBaseAddress, @Peb, sizeof(Peb), lpNumberOfBytesRead) then RaiseLastOSError else begin //read the RTL_USER_PROCESS_PARAMETERS structure if not ReadProcessMemory(ProcessHandle, Peb.ProcessParameters, @Rtl, SizeOf(Rtl), lpNumberOfBytesRead) then RaiseLastOSError else begin //get the size of the Env. variables block if VirtualQueryEx(ProcessHandle, Rtl.Environment, Mbi, SizeOf(Mbi))=0 then RaiseLastOSError else EnvStrLength :=(mbi.RegionSize -(ULONG_PTR(Rtl.Environment) - ULONG_PTR(mbi.BaseAddress))); SetLength(EnvStrBlock, EnvStrLength); //read the content of the env. variables block if not ReadProcessMemory(ProcessHandle, Rtl.Environment, @EnvStrBlock[0], EnvStrLength, lpNumberOfBytesRead) then RaiseLastOSError else Result:=TEncoding.Unicode.GetString(EnvStrBlock); end; end; end else RaiseLastOSError; {$IFDEF CPUX64} end else begin //get the PEB address if NtQueryInformationProcess(ProcessHandle, ProcessWow64Information, @PEBBaseAddress32, SizeOf(PEBBaseAddress32), nil)=STATUS_SUCCESS then begin //read the PEB structure if not ReadProcessMemory(ProcessHandle, PEBBaseAddress32, @Peb32, sizeof(Peb32), lpNumberOfBytesRead) then RaiseLastOSError else begin //read the RTL_USER_PROCESS_PARAMETERS structure if not ReadProcessMemory(ProcessHandle, Pointer(Peb32.ProcessParameters), @Rtl32, SizeOf(Rtl32), lpNumberOfBytesRead) then RaiseLastOSError else begin //get the size of the Env. variables block if VirtualQueryEx(ProcessHandle, Pointer(Rtl32.Environment), Mbi, SizeOf(Mbi))=0 then RaiseLastOSError else EnvStrLength :=(mbi.RegionSize -(ULONG_PTR(Pointer(Rtl32.Environment)) - ULONG_PTR(mbi.BaseAddress))); SetLength(EnvStrBlock, EnvStrLength); //read the content of the env. variables block if not ReadProcessMemory(ProcessHandle, Pointer(Rtl32.Environment), @EnvStrBlock[0], EnvStrLength, lpNumberOfBytesRead) then RaiseLastOSError else Result:=TEncoding.Unicode.GetString(EnvStrBlock); end; end; end else RaiseLastOSError; end; {$ENDIF} finally CloseHandle(ProcessHandle); end; finally CloseHandle(TokenHandle); end; end else RaiseLastOSError; end; function GetEnvVarsPidList(dwProcessId : DWORD): TStringList; var PEnvVars: PChar; PEnvEntry: PChar; begin Result:=TStringList.Create; PEnvVars := PChar(GetEnvVarsPid(dwProcessId)); PEnvEntry := PEnvVars; while PEnvEntry^ <> #0 do begin Result.Add(PEnvEntry); Inc(PEnvEntry, StrLen(PEnvEntry) + 1); end; end; Var EnvVars : TStringList; begin ReportMemoryLeaksOnShutdown:=True; try //Pass a valid pid here EnvVars:=GetEnvVarsPidList(5732); try Writeln(EnvVars.Text); finally EnvVars.Free; end; except on E:Exception do Writeln(E.Classname, ':', E.Message); end; Readln; end.
June 10, 2012 at 3:17 am
As in the code before I get a System Error 87 (wrong parameter)
Line 347; Using a german Delphi XE2; Windows 7 Professional
June 10, 2012 at 10:35 am
Is your windows version 32 or 64 bits? are you passing a valid (existing) PID to the GetEnvVarsPidList function?
June 10, 2012 at 3:55 am
Are you sure you can’t read the memory of a 64 bit process from a WOW64 process? I see no reason why you cannot. I think you would need the base address to be below the 4GB boundary but that’s all that should be needed.
June 10, 2012 at 10:18 am
David, AFAIK there is no easy way to read or write memory regions of x64 processes from x86 process that is running under WOW64, if you mean which is technically possible, yes it is, but the only way it can be done is to using a hack (including the use of undocumented functions like NtReadVirtualMemory and NtWriteVirtualMemory), All the apps which I know which access information of x64 process are 64 bits apps or uses a x64 helper process, Now if you comment refears to the memory location of the PEB which is always under the 4gb boundary and due that you can access from a x86 process that can be true, but that is not always is true for the location of the env. variable block, in fact I found cases in compressed exes where this memory address is outside of the 4b boundary..