Fix PEB32 (#639)

When emulating `WOW64` samples sometimes you can see a garbige like
this:

```
Executing syscall: NtQueryAttributesFile (0x3D) at 0x18009dd42 via 0x100037f1c (wow64.dll)
--> Querying file attributes: \??\C:\SH╠мхЯЦэР╜р░АC:\Windows\SYSTEM32\VCRUNTIME140.dll
```

This is because of incorrect `PEB32` creation that leads to damaging
several `UNICODE_STRING` fields in `ProcessParameters`.
This PR fixes that.
This commit is contained in:
Maurice Heumann
2025-12-25 12:29:07 +01:00
committed by GitHub
3 changed files with 65 additions and 126 deletions

View File

@@ -245,7 +245,8 @@ class emulator_allocator
return uc_str.Buffer; return uc_str.Buffer;
} }
void make_unicode_string(UNICODE_STRING<EmulatorTraits<Emu64>>& result, const std::u16string_view str, template <typename EMU = Emu64>
void make_unicode_string(UNICODE_STRING<EmulatorTraits<EMU>>& result, const std::u16string_view str,
const std::optional<size_t> maximum_length = std::nullopt) const std::optional<size_t> maximum_length = std::nullopt)
{ {
constexpr auto element_size = sizeof(str[0]); constexpr auto element_size = sizeof(str[0]);
@@ -262,13 +263,14 @@ class emulator_allocator
constexpr std::array<char, element_size> nullbyte{}; constexpr std::array<char, element_size> nullbyte{};
this->memory_->write_memory(string_buffer + total_length, nullbyte.data(), nullbyte.size()); this->memory_->write_memory(string_buffer + total_length, nullbyte.data(), nullbyte.size());
result.Buffer = string_buffer; result.Buffer = static_cast<EmulatorTraits<EMU>::PVOID>(string_buffer);
result.Length = static_cast<USHORT>(total_length); result.Length = static_cast<USHORT>(total_length);
result.MaximumLength = static_cast<USHORT>(max_length); result.MaximumLength = static_cast<USHORT>(max_length);
} }
emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> make_unicode_string(const std::u16string_view str, template <typename EMU = Emu64>
const std::optional<size_t> maximum_length = std::nullopt) emulator_object<UNICODE_STRING<EmulatorTraits<EMU>>> make_unicode_string(const std::u16string_view str,
const std::optional<size_t> maximum_length = std::nullopt)
{ {
const auto unicode_string = this->reserve<UNICODE_STRING<EmulatorTraits<Emu64>>>(); const auto unicode_string = this->reserve<UNICODE_STRING<EmulatorTraits<Emu64>>>();
@@ -393,36 +395,6 @@ inline std::u16string read_unicode_string(emulator& emu, const uint64_t uc_strin
return read_unicode_string(emu, emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>>{emu, uc_string}); return read_unicode_string(emu, emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>>{emu, uc_string});
} }
inline void copy_unicode_string_64_to_32(memory_interface& memory, UNICODE_STRING<EmulatorTraits<Emu32>>& dest32,
const UNICODE_STRING<EmulatorTraits<Emu64>>& src64, const uint64_t dest_base_address,
uint32_t& offset, const uint32_t max_size)
{
dest32.Length = static_cast<uint16_t>(src64.Length);
dest32.MaximumLength = static_cast<uint16_t>(src64.MaximumLength);
if (!src64.Buffer || src64.Length == 0)
{
dest32.Buffer = 0;
return;
}
offset = static_cast<uint32_t>(align_up(offset, 2));
if (offset + src64.Length > max_size)
{
dest32.Buffer = 0;
return;
}
dest32.Buffer = static_cast<uint32_t>(dest_base_address + offset);
std::vector<std::byte> string_data(src64.Length);
memory.read_memory(src64.Buffer, string_data.data(), src64.Length);
memory.write_memory(dest_base_address + offset, string_data.data(), src64.Length);
offset += src64.MaximumLength;
}
inline uint64_t get_function_argument(x86_64_emulator& emu, const size_t index, const bool is_syscall = false) inline uint64_t get_function_argument(x86_64_emulator& emu, const size_t index, const bool is_syscall = false)
{ {
bool use_32bit_stack = false; bool use_32bit_stack = false;

View File

@@ -191,12 +191,6 @@ void process_context::setup(x86_64_emulator& emu, memory_manager& memory, regist
this->peb64 = allocator.reserve_page_aligned<PEB64>(); this->peb64 = allocator.reserve_page_aligned<PEB64>();
// Create PEB32 for WOW64 processes
if (this->is_wow64_process)
{
this->peb32 = allocator.reserve_page_aligned<PEB32>();
}
/* Values of the following fields must be /* Values of the following fields must be
* allocated relative to the process_params themselves * allocated relative to the process_params themselves
* and included in the length: * and included in the length:
@@ -290,96 +284,72 @@ void process_context::setup(x86_64_emulator& emu, memory_manager& memory, regist
p.UnicodeCaseTableData = allocator.reserve<NLSTABLEINFO>().value(); p.UnicodeCaseTableData = allocator.reserve<NLSTABLEINFO>().value();
}); });
if (this->is_wow64_process && this->peb32.has_value()) if (this->is_wow64_process)
{ {
// peb32->ProcessParameters : 0x30000 this->peb32 = allocator.reserve_page_aligned<PEB32>();
if (!memory.allocate_memory(WOW64_PEB32_PROCESS_PARA_BASE, WOW64_PEB32_PROCESS_PARA_SIZE, memory_permission::read_write))
{
throw std::runtime_error("Failed to allocate 32-bit process parameters at 0x30000");
}
// Initialize RTL_USER_PROCESS_PARAMETERS32 structure // Initialize RTL_USER_PROCESS_PARAMETERS32 structure
RTL_USER_PROCESS_PARAMETERS32 params32{}; this->process_params32 = allocator.reserve<RTL_USER_PROCESS_PARAMETERS32>();
params32.MaximumLength = sizeof(RTL_USER_PROCESS_PARAMETERS32);
params32.Length = sizeof(RTL_USER_PROCESS_PARAMETERS32);
params32.Flags = RTL_USER_PROCESS_PARAMETERS_IMAGE_KEY_MISSING | RTL_USER_PROCESS_PARAMETERS_APP_MANIFEST_PRESENT |
RTL_USER_PROCESS_PARAMETERS_NORMALIZED;
params32.ConsoleHandle = static_cast<uint32_t>(CONSOLE_HANDLE.h); this->process_params32->access([&](RTL_USER_PROCESS_PARAMETERS32& params32) {
params32.StandardOutput = static_cast<uint32_t>(STDOUT_HANDLE.h); params32.Flags = RTL_USER_PROCESS_PARAMETERS_IMAGE_KEY_MISSING | RTL_USER_PROCESS_PARAMETERS_APP_MANIFEST_PRESENT |
params32.StandardInput = static_cast<uint32_t>(STDIN_HANDLE.h); RTL_USER_PROCESS_PARAMETERS_NORMALIZED;
params32.StandardError = params32.StandardOutput;
this->process_params64.access([&](const RTL_USER_PROCESS_PARAMETERS64& params64) { params32.ConsoleHandle = static_cast<uint32_t>(CONSOLE_HANDLE.h);
uint32_t string_offset = sizeof(RTL_USER_PROCESS_PARAMETERS32); params32.StandardOutput = static_cast<uint32_t>(STDOUT_HANDLE.h);
params32.StandardInput = static_cast<uint32_t>(STDIN_HANDLE.h);
params32.StandardError = params32.StandardOutput;
copy_unicode_string_64_to_32(memory, params32.ImagePathName, params64.ImagePathName, WOW64_PEB32_PROCESS_PARA_BASE, this->process_params64.access([&](const RTL_USER_PROCESS_PARAMETERS64& params64) {
string_offset, WOW64_PEB32_PROCESS_PARA_SIZE); // Copy strings from params64
copy_unicode_string_64_to_32(memory, params32.CommandLine, params64.CommandLine, WOW64_PEB32_PROCESS_PARA_BASE, string_offset, allocator.make_unicode_string(params32.ImagePathName, read_unicode_string(emu, params64.ImagePathName));
WOW64_PEB32_PROCESS_PARA_SIZE); allocator.make_unicode_string(params32.CommandLine, read_unicode_string(emu, params64.CommandLine));
copy_unicode_string_64_to_32(memory, params32.DllPath, params64.DllPath, WOW64_PEB32_PROCESS_PARA_BASE, string_offset, allocator.make_unicode_string(params32.DllPath, read_unicode_string(emu, params64.DllPath));
WOW64_PEB32_PROCESS_PARA_SIZE); allocator.make_unicode_string(params32.CurrentDirectory.DosPath,
copy_unicode_string_64_to_32(memory, params32.CurrentDirectory.DosPath, params64.CurrentDirectory.DosPath, read_unicode_string(emu, params64.CurrentDirectory.DosPath));
WOW64_PEB32_PROCESS_PARA_BASE, string_offset, WOW64_PEB32_PROCESS_PARA_SIZE); allocator.make_unicode_string(params32.WindowTitle, read_unicode_string(emu, params64.WindowTitle));
copy_unicode_string_64_to_32(memory, params32.WindowTitle, params64.WindowTitle, WOW64_PEB32_PROCESS_PARA_BASE, string_offset, allocator.make_unicode_string(params32.DesktopInfo, read_unicode_string(emu, params64.DesktopInfo));
WOW64_PEB32_PROCESS_PARA_SIZE); allocator.make_unicode_string(params32.ShellInfo, read_unicode_string(emu, params64.ShellInfo));
copy_unicode_string_64_to_32(memory, params32.DesktopInfo, params64.DesktopInfo, WOW64_PEB32_PROCESS_PARA_BASE, string_offset, allocator.make_unicode_string(params32.RuntimeData, read_unicode_string(emu, params64.RuntimeData));
WOW64_PEB32_PROCESS_PARA_SIZE); allocator.make_unicode_string(params32.RedirectionDllName, read_unicode_string(emu, params64.RedirectionDllName));
copy_unicode_string_64_to_32(memory, params32.ShellInfo, params64.ShellInfo, WOW64_PEB32_PROCESS_PARA_BASE, string_offset,
WOW64_PEB32_PROCESS_PARA_SIZE);
copy_unicode_string_64_to_32(memory, params32.RuntimeData, params64.RuntimeData, WOW64_PEB32_PROCESS_PARA_BASE, string_offset,
WOW64_PEB32_PROCESS_PARA_SIZE);
// RedirectionDllName - Initialize to empty but valid string // Copy other fields
params32.RedirectionDllName.Length = 0; params32.CurrentDirectory.Handle = static_cast<uint32_t>(params64.CurrentDirectory.Handle);
params32.RedirectionDllName.MaximumLength = 2; params32.ShowWindowFlags = params64.ShowWindowFlags;
// Align offset params32.ConsoleHandle = static_cast<uint32_t>(params64.ConsoleHandle);
string_offset = (string_offset + 1) & ~1; params32.ConsoleFlags = params64.ConsoleFlags;
if (string_offset + 2 <= WOW64_PEB32_PROCESS_PARA_SIZE) params32.StandardInput = static_cast<uint32_t>(params64.StandardInput);
{ params32.StandardOutput = static_cast<uint32_t>(params64.StandardOutput);
params32.RedirectionDllName.Buffer = static_cast<uint32_t>(WOW64_PEB32_PROCESS_PARA_BASE + string_offset); params32.StandardError = static_cast<uint32_t>(params64.StandardError);
uint16_t null_terminator = 0; params32.StartingX = params64.StartingX;
memory.write_memory(WOW64_PEB32_PROCESS_PARA_BASE + string_offset, &null_terminator, sizeof(null_terminator)); params32.StartingY = params64.StartingY;
} params32.CountX = params64.CountX;
else params32.CountY = params64.CountY;
{ params32.CountCharsX = params64.CountCharsX;
params32.RedirectionDllName.Buffer = 0; params32.CountCharsY = params64.CountCharsY;
} params32.FillAttribute = params64.FillAttribute;
params32.WindowFlags = params64.WindowFlags;
params32.DebugFlags = params64.DebugFlags;
params32.ProcessGroupId = params64.ProcessGroupId;
params32.LoaderThreads = params64.LoaderThreads;
// Copy other fields // Environment - copy the pointer value (both processes share the same environment)
params32.CurrentDirectory.Handle = static_cast<uint32_t>(params64.CurrentDirectory.Handle); params32.Environment = static_cast<uint32_t>(params64.Environment);
params32.ShowWindowFlags = params64.ShowWindowFlags; params32.EnvironmentSize = static_cast<uint32_t>(params64.EnvironmentSize);
params32.ConsoleHandle = static_cast<uint32_t>(params64.ConsoleHandle); params32.EnvironmentVersion = static_cast<uint32_t>(params64.EnvironmentVersion);
params32.ConsoleFlags = params64.ConsoleFlags;
params32.StandardInput = static_cast<uint32_t>(params64.StandardInput);
params32.StandardOutput = static_cast<uint32_t>(params64.StandardOutput);
params32.StandardError = static_cast<uint32_t>(params64.StandardError);
params32.StartingX = params64.StartingX;
params32.StartingY = params64.StartingY;
params32.CountX = params64.CountX;
params32.CountY = params64.CountY;
params32.CountCharsX = params64.CountCharsX;
params32.CountCharsY = params64.CountCharsY;
params32.FillAttribute = params64.FillAttribute;
params32.WindowFlags = params64.WindowFlags;
params32.DebugFlags = params64.DebugFlags;
params32.ProcessGroupId = params64.ProcessGroupId;
params32.LoaderThreads = params64.LoaderThreads;
// Environment - copy the pointer value (both processes share the same environment) const auto total_length = allocator.get_next_address() - this->process_params32->value();
params32.Environment = static_cast<uint32_t>(params64.Environment);
params32.EnvironmentSize = static_cast<uint32_t>(params64.EnvironmentSize); params32.Length = static_cast<uint32_t>(std::max(static_cast<uint64_t>(sizeof(params32)), total_length));
params32.EnvironmentVersion = static_cast<uint32_t>(params64.EnvironmentVersion); params32.MaximumLength = params32.Length;
});
}); });
// Write the RTL_USER_PROCESS_PARAMETERS32 structure to the allocated memory
memory.write_memory(WOW64_PEB32_PROCESS_PARA_BASE, &params32, sizeof(params32));
// Update PEB32 to point to the ProcessParameters32 // Update PEB32 to point to the ProcessParameters32
this->peb32->access([&](PEB32& p32) { this->peb32->access([&](PEB32& p32) {
p32.BeingDebugged = 0; p32.BeingDebugged = 0;
p32.ImageBaseAddress = static_cast<uint32_t>(executable.image_base); p32.ImageBaseAddress = static_cast<uint32_t>(executable.image_base);
p32.ProcessParameters = WOW64_PEB32_PROCESS_PARA_BASE; // Fixed address on 0x30000 p32.ProcessParameters = static_cast<uint32_t>(this->process_params32->value());
// Use the dedicated 32-bit ApiSetMap for PEB32 // Use the dedicated 32-bit ApiSetMap for PEB32
p32.ApiSetMap = static_cast<uint32_t>(apiset_map_address_32); p32.ApiSetMap = static_cast<uint32_t>(apiset_map_address_32);

View File

@@ -17,21 +17,18 @@
#include "apiset/apiset.hpp" #include "apiset/apiset.hpp"
#define PEB_SEGMENT_SIZE (20 << 20) // 20 MB #define PEB_SEGMENT_SIZE (20 << 20) // 20 MB
#define GS_SEGMENT_SIZE (1 << 20) // 1 MB #define GS_SEGMENT_SIZE (1 << 20) // 1 MB
#define STACK_SIZE 0x40000ULL // 256KB #define STACK_SIZE 0x40000ULL // 256KB
#define GDT_ADDR 0x35000 #define GDT_ADDR 0x35000
#define GDT_LIMIT 0x1000 #define GDT_LIMIT 0x1000
#define GDT_ENTRY_SIZE 0x8 #define GDT_ENTRY_SIZE 0x8
// TODO: Get rid of that // TODO: Get rid of that
#define WOW64_PEB32_PROCESS_PARA_BASE 0x30000 #define WOW64_NATIVE_STACK_SIZE 0x8000
#define WOW64_PEB32_PROCESS_PARA_SIZE 0x5000 #define WOW64_32BIT_STACK_SIZE (1 << 20)
#define WOW64_NATIVE_STACK_BASE 0x98000
#define WOW64_NATIVE_STACK_SIZE 0x8000
#define WOW64_32BIT_STACK_SIZE (1 << 20)
struct emulator_settings; struct emulator_settings;
struct application_settings; struct application_settings;