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