Load environment variables from registry and other fixes (#243)

This PR aims to:
- [Fix
TimeZoneInformation](496fbd3a40)
by moving `wDayOfWeek` to the end of the `SYSTEMTIME` structure. This
goes against the public definitions of the `SYSTEMTIME` struct but I can
confirm that this yields correct values when `GetTimeZoneInformation` is
called.
- [Load environment variables from
registry](9d5338b168).
- [Miscellaneous
fixes](a629f77e31),
to be more specific:
  - Add `NtOpenProcess` syscall stub.
  - Add logging to `handle_file_enumeration`.
- Fix `FILE_ATTRIBUTE_NORMAL` being together with
`FILE_ATTRIBUTE_DIRECTORY` (`FILE_ATTRIBUTE_NORMAL` means "no attributes
are set").
  - Properly handle relative filenames in `NtQueryFullAttributesFile`.
  - Properly handle "trap flag".
  - Handle "DbgPrint" interrupt.
This commit is contained in:
Maurice Heumann
2025-04-29 09:35:58 +02:00
committed by GitHub
11 changed files with 314 additions and 35 deletions

View File

@@ -863,28 +863,26 @@ typedef struct _SYSTEM_TIMEOFDAY_INFORMATION64
ULONGLONG SleepTimeBias;
} SYSTEM_TIMEOFDAY_INFORMATION64, *PSYSTEM_TIMEOFDAY_INFORMATION64;
#ifndef OS_WINDOWS
typedef struct _SYSTEMTIME
typedef struct _SYSTEMTIME64
{
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
#endif
WORD wDayOfWeek;
} SYSTEMTIME64, *PSYSTEMTIME64, *LPSYSTEMTIME64;
typedef struct _SYSTEM_TIMEZONE_INFORMATION
{
LONG Bias;
ARRAY_CONTAINER<char16_t, 32> StandardName;
SYSTEMTIME StandardDate;
SYSTEMTIME64 StandardDate;
LONG StandardBias;
ARRAY_CONTAINER<char16_t, 32> DaylightName;
SYSTEMTIME DaylightDate;
SYSTEMTIME64 DaylightDate;
LONG DaylightBias;
} SYSTEM_TIMEZONE_INFORMATION, *PSYSTEM_TIMEZONE_INFORMATION;
@@ -892,10 +890,10 @@ typedef struct _SYSTEM_DYNAMIC_TIMEZONE_INFORMATION
{
LONG Bias;
ARRAY_CONTAINER<char16_t, 32> StandardName;
SYSTEMTIME StandardDate;
SYSTEMTIME64 StandardDate;
LONG StandardBias;
ARRAY_CONTAINER<char16_t, 32> DaylightName;
SYSTEMTIME DaylightDate;
SYSTEMTIME64 DaylightDate;
LONG DaylightBias;
ARRAY_CONTAINER<char16_t, 128> TimeZoneKeyName;
BOOLEAN DynamicDaylightTimeDisabled;

View File

@@ -8,26 +8,28 @@
namespace utils
{
struct string_hash
template <typename Elem, typename Traits>
struct basic_string_hash
{
using is_transparent = void;
size_t operator()(const std::string_view str) const
size_t operator()(const std::basic_string_view<Elem, Traits> str) const
{
constexpr std::hash<std::string_view> hasher{};
constexpr std::hash<std::basic_string_view<Elem, Traits>> hasher{};
return hasher(str);
}
};
struct insensitive_string_hash
template <typename Elem, typename Traits>
struct basic_insensitive_string_hash
{
using is_transparent = void;
size_t operator()(const std::string_view str) const
size_t operator()(const std::basic_string_view<Elem, Traits> str) const
{
size_t hash = 0;
constexpr std::hash<int> hasher{};
for (const char c : str)
for (const auto c : str)
{
hash ^= hasher(string::char_to_lower(c)) + 0x9e3779b9 + (hash << 6) + (hash >> 2);
}
@@ -35,22 +37,39 @@ namespace utils
}
};
struct insensitive_string_equal
template <typename Elem, typename Traits>
struct basic_insensitive_string_equal
{
using is_transparent = void;
bool operator()(const std::string_view lhs, const std::string_view rhs) const
bool operator()(const std::basic_string_view<Elem, Traits> lhs,
const std::basic_string_view<Elem, Traits> rhs) const
{
return string::equals_ignore_case(lhs, rhs);
}
};
using string_hash = basic_string_hash<char, std::char_traits<char>>;
using u16string_hash = basic_string_hash<char16_t, std::char_traits<char16_t>>;
using insensitive_string_hash = basic_insensitive_string_hash<char, std::char_traits<char>>;
using insensitive_u16string_hash = basic_insensitive_string_hash<char16_t, std::char_traits<char16_t>>;
using insensitive_string_equal = basic_insensitive_string_equal<char, std::char_traits<char>>;
using insensitive_u16string_equal = basic_insensitive_string_equal<char16_t, std::char_traits<char16_t>>;
template <typename T>
using unordered_string_map = std::unordered_map<std::string, T, string_hash, std::equal_to<>>;
template <typename T>
using unordered_u16string_map = std::unordered_map<std::u16string, T, u16string_hash, std::equal_to<>>;
template <typename T>
using unordered_insensitive_string_map =
std::unordered_map<std::string, T, insensitive_string_hash, insensitive_string_equal>;
template <typename T>
using unordered_insensitive_u16string_map =
std::unordered_map<std::u16string, T, insensitive_u16string_hash, insensitive_u16string_equal>;
using unordered_string_set = std::unordered_set<std::string, string_hash, std::equal_to<>>;
using unordered_u16string_set = std::unordered_set<std::u16string, u16string_hash, std::equal_to<>>;
}

View File

@@ -11,6 +11,7 @@
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <intrin.h>
#include <Windows.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
@@ -458,6 +459,48 @@ namespace
return true;
}
bool test_time_zone()
{
DYNAMIC_TIME_ZONE_INFORMATION current_dtzi = {};
DWORD result = GetDynamicTimeZoneInformation(&current_dtzi);
if (result == TIME_ZONE_ID_INVALID)
{
return false;
}
if (current_dtzi.Bias != -60 || current_dtzi.StandardBias != 0 || current_dtzi.DaylightBias != -60 ||
current_dtzi.DynamicDaylightTimeDisabled != FALSE)
{
return false;
}
if (wcscmp(current_dtzi.StandardName, L"W. Europe Standard Time") != 0 ||
wcscmp(current_dtzi.DaylightName, L"W. Europe Daylight Time") != 0 ||
wcscmp(current_dtzi.TimeZoneKeyName, L"W. Europe Standard Time") != 0)
{
return false;
}
if (current_dtzi.StandardDate.wYear != 0 || current_dtzi.StandardDate.wMonth != 10 ||
current_dtzi.StandardDate.wDayOfWeek != 0 || current_dtzi.StandardDate.wDay != 5 ||
current_dtzi.StandardDate.wHour != 3 || current_dtzi.StandardDate.wMinute != 0 ||
current_dtzi.StandardDate.wSecond != 0 || current_dtzi.StandardDate.wMilliseconds != 0)
{
return false;
}
if (current_dtzi.DaylightDate.wYear != 0 || current_dtzi.DaylightDate.wMonth != 3 ||
current_dtzi.DaylightDate.wDayOfWeek != 0 || current_dtzi.DaylightDate.wDay != 5 ||
current_dtzi.DaylightDate.wHour != 2 || current_dtzi.DaylightDate.wMinute != 0 ||
current_dtzi.DaylightDate.wSecond != 0 || current_dtzi.DaylightDate.wMilliseconds != 0)
{
return false;
}
return true;
}
void throw_exception()
{
if (do_the_task)
@@ -599,6 +642,36 @@ namespace
return test_access_violation_exception() && test_illegal_instruction_exception();
}
bool trap_flag_cleared = false;
constexpr DWORD TRAP_FLAG_MASK = 0x100;
LONG NTAPI single_step_handler(PEXCEPTION_POINTERS exception_info)
{
if (exception_info->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP)
{
PCONTEXT context = exception_info->ContextRecord;
trap_flag_cleared = (context->EFlags & TRAP_FLAG_MASK) == 0;
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
bool test_interrupts()
{
PVOID veh_handle = AddVectoredExceptionHandler(1, single_step_handler);
if (!veh_handle)
return false;
__writeeflags(__readeflags() | TRAP_FLAG_MASK);
__nop();
RemoveVectoredExceptionHandler(veh_handle);
return trap_flag_cleared;
}
void print_time()
{
const auto epoch_time = std::chrono::system_clock::now().time_since_epoch();
@@ -651,10 +724,15 @@ int main(const int argc, const char* argv[])
RUN_TEST(test_working_directory, "Working Directory")
RUN_TEST(test_registry, "Registry")
RUN_TEST(test_system_info, "System Info")
RUN_TEST(test_time_zone, "Time Zone")
RUN_TEST(test_threads, "Threads")
RUN_TEST(test_env, "Environment")
RUN_TEST(test_exceptions, "Exceptions")
RUN_TEST(test_native_exceptions, "Native Exceptions")
if (!getenv("EMULATOR_ICICLE"))
{
RUN_TEST(test_interrupts, "Interrupts")
}
RUN_TEST(test_tls, "TLS")
RUN_TEST(test_socket, "Socket")
RUN_TEST(test_apc, "APC")

View File

@@ -25,11 +25,121 @@ namespace
emu.write_memory<uint64_t>(GDT_ADDR + 5 * (sizeof(uint64_t)), 0xEFF6000000FFFF);
emu.reg<uint16_t>(x86_register::ss, 0x2B);
}
std::u16string expand_environment_string(const std::u16string& input,
const utils::unordered_insensitive_u16string_map<std::u16string>& env_map)
{
std::u16string result;
result.reserve(input.length());
size_t pos = 0;
while (pos < input.length())
{
size_t start = input.find(u'%', pos);
if (start == std::u16string::npos)
{
result.append(input.substr(pos));
break;
}
result.append(input.substr(pos, start - pos));
size_t end = input.find(u'%', start + 1);
if (end == std::u16string::npos)
{
result.append(input.substr(start));
break;
}
std::u16string var_name = input.substr(start + 1, end - start - 1);
if (var_name.empty())
{
result.append(u"%%");
}
else
{
auto it = env_map.find(var_name);
result.append(it != env_map.end() ? it->second : input.substr(start, end - start + 1));
}
pos = end + 1;
}
return result;
}
utils::unordered_insensitive_u16string_map<std::u16string> get_environment_variables(registry_manager& registry)
{
utils::unordered_insensitive_u16string_map<std::u16string> env_map;
std::unordered_set<std::u16string_view> keys_to_expand;
const auto env_key =
registry.get_key({R"(\Registry\Machine\System\CurrentControlSet\Control\Session Manager\Environment)"});
if (env_key)
{
for (size_t i = 0; const auto value_opt = registry.get_value(*env_key, i); i++)
{
const auto& value = *value_opt;
if (value.type != REG_SZ && value.type != REG_EXPAND_SZ)
{
continue;
}
if (value.data.empty() || value.data.size() % 2 != 0)
{
continue;
}
const auto char_count = value.data.size() / sizeof(char16_t);
const auto* data_ptr = reinterpret_cast<const char16_t*>(value.data.data());
if (data_ptr[char_count - 1] != u'\0')
{
continue;
}
const auto [it, inserted] =
env_map.emplace(u8_to_u16(value.name), std::u16string(data_ptr, char_count - 1));
if (inserted && value.type == REG_EXPAND_SZ)
{
keys_to_expand.insert(it->first);
}
}
}
env_map[u"EMULATOR"] = u"1";
const auto* env = getenv("EMULATOR_ICICLE");
if (env && (env == "1"sv || env == "true"sv))
{
env_map[u"EMULATOR_ICICLE"] = u"1";
}
env_map[u"COMPUTERNAME"] = u"momo";
env_map[u"USERNAME"] = u"momo";
env_map[u"SystemDrive"] = u"C:";
env_map[u"SystemRoot"] = u"C:\\WINDOWS";
for (const auto& key : keys_to_expand)
{
auto it = env_map.find(key);
if (it != env_map.end())
{
std::u16string expanded = expand_environment_string(it->second, env_map);
if (expanded != it->second)
{
it->second = expanded;
}
}
}
return env_map;
}
}
void process_context::setup(x86_64_emulator& emu, memory_manager& memory, const application_settings& app_settings,
const mapped_module& executable, const mapped_module& ntdll,
const apiset::container& apiset_container)
void process_context::setup(x86_64_emulator& emu, memory_manager& memory, registry_manager& registry,
const application_settings& app_settings, const mapped_module& executable,
const mapped_module& ntdll, const apiset::container& apiset_container)
{
setup_gdt(emu, memory);
@@ -66,9 +176,17 @@ void process_context::setup(x86_64_emulator& emu, memory_manager& memory, const
proc_params.StandardError = proc_params.StandardOutput;
proc_params.Environment = allocator.copy_string(u"=::=::\\");
allocator.copy_string(u"EMULATOR=1");
allocator.copy_string(u"COMPUTERNAME=momo");
allocator.copy_string(u"SystemRoot=C:\\WINDOWS");
const auto env_map = get_environment_variables(registry);
for (const auto& [name, value] : env_map)
{
std::u16string entry;
entry += name;
entry += u"=";
entry += value;
allocator.copy_string(entry);
}
allocator.copy_string(u"");
const auto application_str = app_settings.application.u16string();

View File

@@ -59,8 +59,9 @@ struct process_context
{
}
void setup(x86_64_emulator& emu, memory_manager& memory, const application_settings& app_settings,
const mapped_module& executable, const mapped_module& ntdll, const apiset::container& apiset_container);
void setup(x86_64_emulator& emu, memory_manager& memory, registry_manager& registry,
const application_settings& app_settings, const mapped_module& executable, const mapped_module& ntdll,
const apiset::container& apiset_container);
handle create_thread(memory_manager& memory, uint64_t start_address, uint64_t argument, uint64_t stack_size,
bool suspended);

View File

@@ -5,6 +5,25 @@
#include "serialization_helper.hpp"
#include "../handles.hpp"
#ifndef OS_WINDOWS
#define REG_NONE (0ul) // No value type
#define REG_SZ (1ul) // Unicode nul terminated string
#define REG_EXPAND_SZ \
(2ul) // Unicode nul terminated string
// (with environment variable references)
#define REG_BINARY (3ul) // Free form binary
#define REG_DWORD (4ul) // 32-bit number
#define REG_DWORD_LITTLE_ENDIAN (4ul) // 32-bit number (same as REG_DWORD)
#define REG_DWORD_BIG_ENDIAN (5ul) // 32-bit number
#define REG_LINK (6ul) // Symbolic Link (unicode)
#define REG_MULTI_SZ (7ul) // Multiple Unicode strings
#define REG_RESOURCE_LIST (8ul) // Resource list in the resource map
#define REG_FULL_RESOURCE_DESCRIPTOR (9ul) // Resource list in the hardware description
#define REG_RESOURCE_REQUIREMENTS_LIST (10ul)
#define REG_QWORD (11ul) // 64-bit number
#define REG_QWORD_LITTLE_ENDIAN (11ul) // 64-bit number (same as REG_QWORD)
#endif
struct registry_key : ref_counted_object
{
utils::path_key hive{};

View File

@@ -215,6 +215,7 @@ namespace syscalls
emulator_object<uint32_t> return_length);
NTSTATUS handle_NtSetInformationProcess(const syscall_context& c, handle process_handle, uint32_t info_class,
uint64_t process_information, uint32_t process_information_length);
NTSTATUS handle_NtOpenProcess();
NTSTATUS handle_NtOpenProcessToken(const syscall_context&, handle process_handle, ACCESS_MASK /*desired_access*/,
emulator_object<handle> token_handle);
NTSTATUS handle_NtOpenProcessTokenEx(const syscall_context& c, handle process_handle, ACCESS_MASK desired_access,
@@ -769,6 +770,7 @@ void syscall_dispatcher::add_handlers(std::map<std::string, syscall_handler>& ha
add_handler(NtCreateFile);
add_handler(NtDeviceIoControlFile);
add_handler(NtQueryWnfStateData);
add_handler(NtOpenProcess);
add_handler(NtOpenProcessToken);
add_handler(NtOpenProcessTokenEx);
add_handler(NtQuerySecurityAttributesToken);

View File

@@ -140,9 +140,20 @@ namespace syscalls
{
if (!f->enumeration_state || query_flags & SL_RESTART_SCAN)
{
const auto mask = file_mask ? read_unicode_string(c.emu, file_mask) : u"";
if (!mask.empty())
{
c.win_emu.log.print(color::dark_gray, "--> Enumerating directory: %s (Mask: \"%s\")\n",
u16_to_u8(f->name).c_str(), u16_to_u8(mask).c_str());
}
else
{
c.win_emu.log.print(color::dark_gray, "--> Enumerating directory: %s\n", u16_to_u8(f->name).c_str());
}
f->enumeration_state.emplace(file_enumeration_state{});
f->enumeration_state->files = scan_directory(c.win_emu.file_sys.translate(f->name),
file_mask ? read_unicode_string(c.emu, file_mask) : u"");
f->enumeration_state->files = scan_directory(c.win_emu.file_sys.translate(f->name), mask);
}
auto& enum_state = *f->enumeration_state;
@@ -154,6 +165,10 @@ namespace syscalls
if (current_index >= enum_state.files.size())
{
IO_STATUS_BLOCK<EmulatorTraits<Emu64>> block{};
block.Information = 0;
io_status_block.write(block);
return STATUS_NO_MORE_FILES;
}
@@ -191,11 +206,7 @@ namespace syscalls
T info{};
info.NextEntryOffset = 0;
info.FileIndex = static_cast<ULONG>(current_index);
info.FileAttributes = FILE_ATTRIBUTE_NORMAL;
if (current_file.is_directory)
{
info.FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
}
info.FileAttributes = current_file.is_directory ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL;
info.FileNameLength = static_cast<ULONG>(file_name.size() * 2);
info.EndOfFile.QuadPart = current_file.file_size;
@@ -722,9 +733,21 @@ namespace syscalls
return STATUS_INVALID_PARAMETER;
}
const auto filename = read_unicode_string(
auto filename = read_unicode_string(
c.emu, emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>>{c.emu, attributes.ObjectName});
if (attributes.RootDirectory)
{
const auto* root = c.proc.files.get(attributes.RootDirectory);
if (!root)
{
return STATUS_INVALID_HANDLE;
}
const auto has_separator = root->name.ends_with(u"\\") || root->name.ends_with(u"/");
filename = root->name + (has_separator ? u"" : u"\\") + filename;
}
c.win_emu.log.print(color::dark_gray, "--> Querying file attributes: %s\n", u16_to_u8(filename).c_str());
const auto local_filename = c.win_emu.file_sys.translate(filename).string();

View File

@@ -348,6 +348,11 @@ namespace syscalls
return STATUS_NOT_SUPPORTED;
}
NTSTATUS handle_NtOpenProcess()
{
return STATUS_NOT_SUPPORTED;
}
NTSTATUS handle_NtOpenProcessToken(const syscall_context&, const handle process_handle,
const ACCESS_MASK /*desired_access*/, const emulator_object<handle> token_handle)
{

View File

@@ -171,6 +171,9 @@ namespace syscalls
c.emu.write_memory(obj_address + 0x9C8, 0xFFFFFFFF); // TIME_ZONE_ID_INVALID
// Windows 2019 offset!
c.emu.write_memory(obj_address + 0xA70, 0xFFFFFFFF); // TIME_ZONE_ID_INVALID
if (view_size)
{
view_size.write(shared_section_size);

View File

@@ -345,7 +345,7 @@ void windows_emulator::setup_process(const application_settings& app_settings)
const auto apiset_data = apiset::obtain(this->emulation_root);
this->process.setup(this->emu(), this->memory, app_settings, *executable, *ntdll, apiset_data);
this->process.setup(this->emu(), this->memory, this->registry, app_settings, *executable, *ntdll, apiset_data);
const auto ntdll_data = emu.read_memory(ntdll->image_base, static_cast<size_t>(ntdll->size_of_image));
const auto win32u_data = emu.read_memory(win32u->image_base, static_cast<size_t>(win32u->size_of_image));
@@ -515,6 +515,7 @@ void windows_emulator::setup_hooks()
this->emu().hook_interrupt([&](const int interrupt) {
const auto rip = this->emu().read_instruction_pointer();
const auto eflags = this->emu().reg<uint32_t>(x86_register::eflags);
switch (interrupt)
{
@@ -522,7 +523,15 @@ void windows_emulator::setup_hooks()
dispatch_integer_division_by_zero(this->emu(), this->process);
return;
case 1:
this->log.print(color::pink, "Singlestep: 0x%" PRIx64 "\n", rip);
if ((eflags & 0x100) != 0)
{
this->log.print(color::pink, "Singlestep (Trap Flag): 0x%" PRIx64 "\n", rip);
this->emu().reg(x86_register::eflags, eflags & ~0x100);
}
else
{
this->log.print(color::pink, "Singlestep: 0x%" PRIx64 "\n", rip);
}
dispatch_single_step(this->emu(), this->process);
return;
case 3:
@@ -532,6 +541,10 @@ void windows_emulator::setup_hooks()
case 6:
dispatch_illegal_instruction_violation(this->emu(), this->process);
return;
case 45:
this->log.print(color::pink, "DbgPrint: 0x%" PRIx64 "\n", rip);
dispatch_breakpoint(this->emu(), this->process);
return;
default:
break;
}