While attempting to enumerate modules of a remote process I came across the need to acquire the process environment block or PEB
in short. While it may seem quite simple - that is a single native API call and you are done the problem is that there actually are 2 evironment blocks.
The problem at hand
As it turns out, the most documented method NtQueryInformationProcess(ProcessBasicInformation)
can only acquire the address of PEB whose architecture is same as of your own process. As you can guess if the architectures do not match, the address you get is quite useless when trying to enumerate loaded modules. To solve the problem one has to use a bunch of different combinations of flags and APIs to get the correct structure.
Implementation
To cut the chase here is a table of exactly what needs to be used to correctly acquire the PEB
according to architecture of your own and target process.
Target | Local | Function |
---|---|---|
same | same | NtQueryInformationProcess(ProcessBasicInformation) |
32 bit | 64 bit | NtQueryInformationProcess(ProcessWow64Information) |
64 bit | 32 bit | NtWow64QueryInformationProcess64(ProcessBasicInformation) |
First step is to figure out whether our process is of the same architecture. Coincidentally that can be done by reusing the return value of same native API that actually returns the address of PEB
.
// acquire the PROCESS_EXTENDED_BASIC_INFORMATION of target process PROCESS_EXTENDED_BASIC_INFORMATION info; info.Size = sizeof(info); NtQueryInformationProcess( handle, ProcessBasicInformation, &info, sizeof(info), nullptr); // function to check whether the arch is the same bool is_process_same_arch(const PROCESS_EXTENDED_BASIC_INFORMATION& info) noexcept { return info.IsWow64Process == !!NtCurrentTeb()->WowTebOffset; }
And in the case where the architecture doesn’t match we can then choose to use one of the other two aforementioned APIs.
NTSTATUS remote_peb_diff_arch(void* handle, uint64_t& peb_addr) noexcept { #ifdef _WIN64 // we are x64 and target is x32 return NtQueryInformationProcess( handle, ProcessWow64Information, &peb_addr, sizeof(peb_addr), nullptr); #else // we are x32 and the target is x64 PROCESS_BASIC_INFORMATION64 pbi; const auto status = NtWow64QueryInformationProcess64( handle, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr); if(NT_SUCCESS(status)) peb_addr = pbi.PebBaseAddress; return status; #endif }
Lastly here is the full, finished code listing.
bool is_process_same_arch(const PROCESS_EXTENDED_BASIC_INFORMATION& info) noexcept { return info.IsWow64Process == !!NtCurrentTeb()->WowTebOffset; } NTSTATUS peb_address(void* handle, std::uint64_t& address) noexcept PROCESS_EXTENDED_BASIC_INFORMATION info; info.Size = sizeof(info); auto status = NtQueryInformationProcess( handle, ProcessBasicInformation, &info, sizeof(info), nullptr); if(NT_SUCCESS(status)){ if(is_process_same_arch(info)) peb_address = reinterpret_cast<std::uintptr_t>(info.BasicInfo.PebBaseAddress); else status = remote_peb_address(handle, false, peb_address)); } return status; }
The next article will explain how to enumerate the loaded modules list.
BACK TO HOME