Compare commits

...

33 Commits

Author SHA1 Message Date
Maurice Heumann
d70ab2607e Add support for user callbacks (#691)
This PR is my attempt to add support for user callbacks in the emulator.
User callbacks allow the emulator to call guest callbacks from syscalls,
and when the callback finishes running, control returns to the syscall
through the completion method. I've also added a test and implemented
the NtUserEnumDisplayMonitors syscall.

One thing to note is that this implementation isn't faithful to how the
Windows kernel does it, since the kernel uses the KernelCallbackTable
and the `ntdll!KiUserCallbackDispatch` method, and this implementation
currently just calls the callback directly.
2026-01-10 13:59:24 +01:00
Maurice Heumann
f7eb8a2b2d Fix memory_manager serialization (#699)
This PR fixes `memory_manager` serialization. In particular, it fixes
running `WOW64` programs from snapshots.
2026-01-10 13:58:39 +01:00
ssvine
c49226d7c1 Fix memory_manager serialization 2026-01-10 14:13:09 +03:00
Maurice Heumann
83a6b65add Fix user_handle_table memory handling (#696)
This PR fixes `user_handle_table` for WOW64. Otherwise we get AV during
32-bit `user32.dll` initialization.
2026-01-09 14:45:25 +01:00
ssvine
dd31b6344f Fix user_handle_table memory handling 2026-01-09 10:51:09 +03:00
Igor Pissolati
d33ef2d93f Avoid memory hook being triggered twice for the same callback 2026-01-08 16:59:09 -03:00
Maurice Heumann
9090e29e21 Revert "module manager: safer ldr init block setup" (#695)
The change from fixed size 0xF0 to sizeof(PS_SYSTEM_DLL_INIT_BLOCK)
(which is 0x128 for V3 struct) causes memory corruption when using
Windows 10 system files.

**Before (working):**
```
constexpr uint64_t symtem_dll_init_block_fix_size = 0xF0; // Wine or WIN10
init_block.Size = symtem_dll_init_block_fix_size;
// ...
this->memory_->write_memory(ldr_init_block_addr, &init_block, symtem_dll_init_block_fix_size);
```

**After (broken):**
```
constexpr uint64_t system_dll_init_block_size = sizeof(PS_SYSTEM_DLL_INIT_BLOCK);  // = 0x128
init_block.Size = system_dll_init_block_size;
// ...
this->memory_->write_memory(ldr_init_block_addr, &init_block, write_size);
```

**Symptom:**
```
Executing syscall: NtQueryVirtualMemory (0x23) at 0x18009d442 via 0x1800d4920 (ntdll.dll)
Interrupt 41
Suspicious: Breakpoint at 0x1800ac7d8 (via 0x1800ac7d5)
Executing syscall: NtQueryVirtualMemory (0x23) at 0x18009d442 via 0x180033579 (ntdll.dll)
Executing syscall: NtQueryVirtualMemory (0x23) at 0x18009d442 via 0x180033579 (ntdll.dll)
Executing syscall: NtQueryVirtualMemory (0x23) at 0x18009d442 via 0x180033579 (ntdll.dll)
Executing syscall: NtQueryVirtualMemory (0x23) at 0x18009d442 via 0x180033579 (ntdll.dll)
Bad address for memory image request: 0x5f0000
Executing syscall: NtRaiseException (0x168) at 0x18009fcd2 via 0x1800a0ee3 (ntdll.dll)
!!! NtRaiseException: Code=0x80000003, Flags=0x0, Address=0x1800ac7d7, NumParams=0, HandleException=0
Emulation terminated without status!
```

**Root cause:** PS_SYSTEM_DLL_INIT_BLOCK has different sizes across
Windows versions. It needs to detect the Windows version from ntdll and
use the appropriate size. I will submit a PR to fix this issue soon.
2026-01-08 11:42:34 +01:00
Brian Wynn
7ba5a7b2e4 Revert "module manager: safer ldr init block setup" 2026-01-08 17:37:01 +08:00
Igor Pissolati
d5d73f42c9 Improve clarity in dispatch_user_callback 2026-01-07 15:01:02 -03:00
Maurice Heumann
01851ad571 Update description for Security Research section 2026-01-07 08:02:34 +01:00
Maurice Heumann
e7abe50f00 module manager: safer ldr init block setup (#693) 2026-01-07 07:50:23 +01:00
redthing1
4e5ba450c1 module manager: safer ldr init block setup 2026-01-06 17:57:34 -08:00
momo5502
ff99a1cb56 Log writes to the import table 2026-01-06 14:41:34 +01:00
Igor Pissolati
9fdc2a4ce6 Add support for user callbacks 2026-01-06 10:14:02 -03:00
Maurice Heumann
7c912146fb Cleanup common header includes (#690) 2026-01-06 11:46:07 +01:00
momo5502
45ac1fc32f Cleanup headers 2026-01-06 11:29:39 +01:00
Maurice Heumann
5d81d8bda9 Implement NtQueryMultipleValueKey (#684)
This PR implements `NtQueryMultipleValueKey` that is used in calls like
`RegQueryMultipleValues`.
2026-01-06 10:38:10 +01:00
Maurice Heumann
e46e8dcf7a more object syscalls (#687) 2026-01-06 10:30:47 +01:00
Maurice Heumann
1a613be667 ports: add noop port stub for WER (#689) 2026-01-06 10:22:49 +01:00
Maurice Heumann
d3af3c781c Add support for user_object/user_handle_table (#677)
Fixes #641

This PR is my attempt to add support for user32 objects and the user32
handle table. I also added a test, but as expected, it fails on Windows
2022. I’ll try to fix that another day, but feel free to review the code
😄
2026-01-06 10:21:58 +01:00
redthing1
17d1e23b60 syscalls.cpp: clang format 2026-01-05 23:15:02 -08:00
redthing1
1be74c4cde ports: add noop port stub for WER 2026-01-05 23:12:53 -08:00
redthing1
51034297bf object syscalls: wire up the new syscalls 2026-01-05 23:08:21 -08:00
redthing1
a2a119aec2 object syscalls: implement NtCompareObjects 2026-01-05 23:08:18 -08:00
redthing1
6f8dca6614 object syscalls: implement NtWaitForMultipleObjects32 2026-01-05 23:08:14 -08:00
redthing1
cafa0ebd0b NTSTATUS: add STATUS_NOT_SAME_OBJECT 2026-01-05 23:08:05 -08:00
Igor Pissolati
90b38b3bff Fix user_handle_table initialization 2026-01-05 09:01:05 -03:00
Igor Pissolati
5d59700ec7 Remove parse_number 2026-01-05 09:00:45 -03:00
ssvine
37c2184bfc Implement NtQueryMultipleValueKey 2026-01-05 13:11:35 +03:00
Igor Pissolati
d51f890197 Use vector instead of large array 2026-01-04 22:51:19 -03:00
Igor Pissolati
7112d619b4 Fix clang tidy 2026-01-04 16:28:30 -03:00
Igor Pissolati
5302900a9d Fix for older Windows builds 2026-01-04 16:01:13 -03:00
Igor Pissolati
de491ade0e Add support for user_object/user_handle_table 2026-01-02 19:46:04 -03:00
35 changed files with 1372 additions and 25 deletions

View File

@@ -91,7 +91,7 @@ export function LandingPage() {
icon: <Shield className="h-6 w-6" />,
title: "Security Research",
description:
"Analyze malware and security vulnerabilities in a controlled environment",
"Analyze security vulnerabilities in a controlled environment",
},
{
icon: <Lock className="h-6 w-6" />,

View File

@@ -167,8 +167,15 @@ namespace
const auto var_ptr = get_function_argument(win_emu.emu(), index);
if (var_ptr && !is_int_resource(var_ptr))
{
const auto str = read_string<CharType>(win_emu.memory, var_ptr);
print_string(win_emu.log, str);
try
{
const auto str = read_string<CharType>(win_emu.memory, var_ptr);
print_string(win_emu.log, str);
}
catch (...)
{
print_string(win_emu.log, "[failed to read]");
}
}
}
@@ -532,6 +539,29 @@ namespace
max = std::max(import_thunk, max);
}
c.win_emu->emu().hook_memory_write(min, max - min, [&c](const uint64_t address, const void*, size_t) {
const auto& watched_module = *c.win_emu->mod_manager.executable;
const auto& accessor_module = *c.win_emu->mod_manager.executable;
const auto rip = c.win_emu->emu().read_instruction_pointer();
if (!accessor_module.contains(rip))
{
return;
}
const auto sym = watched_module.imports.find(address);
if (sym == watched_module.imports.end())
{
return;
}
const auto import_module = watched_module.imported_modules.at(sym->second.module_index);
c.win_emu->log.print(color::blue, "Import write access: %s (%s) at 0x%" PRIx64 " (%s)\n", sym->second.name.c_str(),
import_module.c_str(), rip, accessor_module.name.c_str());
});
c.win_emu->emu().hook_memory_read(min, max - min, [&c](const uint64_t address, const void*, size_t) {
const auto& watched_module = *c.win_emu->mod_manager.executable;
const auto& accessor_module = *c.win_emu->mod_manager.executable;

View File

@@ -1,5 +1,7 @@
#pragma once
#include "kernel_mapped.hpp"
// NOLINTBEGIN(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
#define ACCESS_MASK DWORD

View File

@@ -2,6 +2,12 @@
#include <cstdint>
#include "primitives.hpp"
#include "traits.hpp"
#include "unicode.hpp"
#include "status.hpp"
#include "process.hpp"
// NOLINTBEGIN(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
#ifndef NT_SUCCESS

View File

@@ -1,5 +1,8 @@
#pragma once
#include "traits.hpp"
#include "primitives.hpp"
template <typename Traits>
struct EMU_WSABUF
{

View File

@@ -17,6 +17,7 @@
#include "unicode.hpp"
#include "status.hpp"
#include "process.hpp"
#include "user.hpp"
#include "kernel_mapped.hpp"
#include "memory.hpp"
#include "file_management.hpp"

View File

@@ -1,13 +1,14 @@
#pragma once
#include <cstdint>
#include "compiler.hpp"
// NOLINTBEGIN(modernize-use-using)
#ifdef OS_WINDOWS
#include "../utils/win.hpp"
#include "winnt.h"
#include <winnt.h>
#else
@@ -50,6 +51,14 @@ typedef union _LARGE_INTEGER
using BYTE = std::uint8_t;
#define CHAR BYTE
typedef struct _RECT
{
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT;
#endif
using WORD = std::uint16_t;

View File

@@ -110,4 +110,12 @@ struct KEY_VALUE_FULL_INFORMATION
char16_t Name[1];
};
struct KEY_VALUE_ENTRY
{
EmulatorTraits<Emu64>::PVOID ValueName;
ULONG DataLength;
ULONG DataOffset;
ULONG Type;
};
// NOLINTEND(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)

View File

@@ -50,6 +50,7 @@ using NTSTATUS = std::uint32_t;
#define STATUS_PROCEDURE_NOT_FOUND ((NTSTATUS)0xC000007AL)
#define STATUS_NO_TOKEN ((NTSTATUS)0xC000007CL)
#define STATUS_FILE_INVALID ((NTSTATUS)0xC0000098L)
#define STATUS_INSUFFICIENT_RESOURCES ((NTSTATUS)0xC000009AL)
#define STATUS_FREE_VM_NOT_AT_BASE ((NTSTATUS)0xC000009FL)
#define STATUS_MEMORY_NOT_ALLOCATED ((NTSTATUS)0xC00000A0L)
#define STATUS_PIPE_BUSY ((NTSTATUS)0xC00000AAL)
@@ -78,6 +79,7 @@ using NTSTATUS = std::uint32_t;
#define STATUS_PIPE_BROKEN ((NTSTATUS)0xC000014BL)
#define STATUS_CONNECTION_RESET ((NTSTATUS)0xC000020DL)
#define STATUS_NOT_FOUND ((NTSTATUS)0xC0000225L)
#define STATUS_NOT_SAME_OBJECT ((NTSTATUS)0xC00001ACL)
#define STATUS_CONNECTION_REFUSED ((NTSTATUS)0xC0000236L)
#define STATUS_TIMER_RESOLUTION_NOT_SET ((NTSTATUS)0xC0000245L)
#define STATUS_ADDRESS_ALREADY_ASSOCIATED ((NTSTATUS)0xC0000328L)

View File

@@ -1,5 +1,7 @@
#pragma once
#include "kernel_mapped.hpp"
// NOLINTBEGIN(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
typedef enum _THREADINFOCLASS

View File

@@ -0,0 +1,117 @@
#pragma once
#include <cstdint>
// NOLINTBEGIN(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
struct USER_SERVERINFO
{
DWORD dwSRVIFlags;
uint64_t cHandleEntries;
uint8_t unknown[0x1000];
};
struct USER_DISPINFO
{
DWORD dwMonitorCount;
EMULATOR_CAST(uint64_t, USER_MONITOR*) pPrimaryMonitor;
uint8_t unknown[0xFF];
};
struct USER_HANDLEENTRY
{
uint64_t pHead;
uint64_t pOwner;
uint64_t unknown;
uint8_t bType;
uint8_t bFlags;
uint16_t wUniq;
};
static_assert(sizeof(USER_HANDLEENTRY) == 0x20);
struct USER_SHAREDINFO
{
uint64_t psi;
uint64_t aheList;
uint32_t HeEntrySize;
uint64_t pDispInfo;
uint8_t unknown[0xFF];
};
struct USER_THROBJHEAD
{
struct
{
uint64_t h;
uint32_t cLockObj;
} h;
uint64_t pti;
};
struct USER_THRDESKHEAD
{
USER_THROBJHEAD h;
uint64_t rpdesk;
uint64_t pSelf;
};
enum USER_HANDLETYPE : uint8_t
{
TYPE_FREE = 0,
TYPE_WINDOW = 1,
TYPE_MENU = 2,
TYPE_CURSOR = 3,
TYPE_SETWINDOWPOS = 4,
TYPE_HOOK = 5,
TYPE_CLIPDATA = 6,
TYPE_CALLPROC = 7,
TYPE_ACCELTABLE = 8,
TYPE_DDEACCESS = 9,
TYPE_DDECONV = 10,
TYPE_DDEXACT = 11,
TYPE_MONITOR = 12,
TYPE_KBDLAYOUT = 13,
TYPE_KBDFILE = 14,
TYPE_WINEVENTHOOK = 15,
TYPE_TIMER = 16,
TYPE_INPUTCONTEXT = 17,
TYPE_HIDDATA = 18,
TYPE_DEVICEINFO = 19,
TYPE_TOUCHINPUTINFO = 20,
TYPE_GESTUREINFOOBJ = 21,
TYPE_CTYPES = 22,
TYPE_GENERIC = 255
};
struct USER_MONITOR
{
EMULATOR_CAST(uint64_t, HMONITOR) hmon;
uint8_t unknown1[0x14];
RECT rcMonitor;
RECT rcWork;
union
{
struct
{
uint16_t monitorDpi;
uint16_t nativeDpi;
} b26;
struct
{
uint32_t unknown1;
uint16_t monitorDpi;
uint16_t nativeDpi;
uint16_t cachedDpi;
uint16_t unknown2;
RECT rcMonitorDpiAware;
} b20;
};
uint8_t unknown4[0xFF];
};
struct USER_WINDOW
{
uint8_t unknown[0xFF];
};
// NOLINTEND(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)

View File

@@ -7,6 +7,8 @@
#include <system_error>
#include <variant>
#include "primitives.hpp"
// NOLINTBEGIN(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory

View File

@@ -1,5 +1,7 @@
#pragma once
#include "kernel_mapped.hpp"
// NOLINTBEGIN(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
using pointer = uint64_t;

View File

@@ -160,6 +160,36 @@ namespace utils
this->write_span(std::span(vec));
}
void write_vector(const std::vector<bool>& vec)
{
this->write(static_cast<uint64_t>(vec.size()));
uint8_t byte = 0;
uint8_t bit_index = 0;
for (const bool b : vec)
{
if (b)
{
byte |= (1u << bit_index);
}
++bit_index;
if (bit_index == 8)
{
this->write<uint8_t>(byte);
byte = 0;
bit_index = 0;
}
}
if (bit_index != 0)
{
this->write<uint8_t>(byte);
}
}
template <typename T>
void write_list(const std::list<T>& vec)
{
@@ -385,6 +415,25 @@ namespace utils
}
}
void read_vector(std::vector<bool>& result)
{
const auto bit_count = this->read<uint64_t>();
result.clear();
result.reserve(static_cast<size_t>(bit_count));
const auto size = (bit_count + 7) / 8;
for (uint64_t i = 0; i < size; ++i)
{
const auto byte = this->read<uint8_t>();
for (uint8_t bit = 0; bit < 8 && result.size() < bit_count; ++bit)
{
result.push_back((byte >> bit) & 1u);
}
}
}
template <typename T>
std::vector<T> read_vector()
{

View File

@@ -59,6 +59,12 @@ class typed_emulator : public emulator
return result;
}
void write_stack(const size_t index, const pointer_type& value)
{
const auto sp = this->read_stack_pointer();
this->write_memory(sp + (index * pointer_size), &value, sizeof(value));
}
void push_stack(const pointer_type& value)
{
const auto sp = this->read_stack_pointer() - pointer_size;

View File

@@ -509,6 +509,74 @@ namespace
return true;
}
bool validate_primary_monitor(MONITORINFOEXA& mi)
{
if (std::string_view(mi.szDevice) != R"(\\.\DISPLAY1)")
{
return false;
}
if (mi.rcMonitor.left != 0 || mi.rcMonitor.top != 0 || mi.rcMonitor.right != 1920 || mi.rcMonitor.bottom != 1080)
{
return false;
}
if (!(mi.dwFlags & MONITORINFOF_PRIMARY))
{
return false;
}
return true;
}
bool test_monitor_info()
{
const POINT pt = {0, 0};
const auto hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTOPRIMARY);
if (!hMonitor)
{
return false;
}
MONITORINFOEXA mi;
mi.cbSize = sizeof(mi);
if (!GetMonitorInfoA(hMonitor, &mi))
{
return false;
}
return validate_primary_monitor(mi);
}
BOOL CALLBACK monitor_enum_proc(HMONITOR hMonitor, HDC, LPRECT, LPARAM dwData)
{
auto* valid = reinterpret_cast<bool*>(dwData);
MONITORINFOEXA mi;
mi.cbSize = sizeof(mi);
if (!GetMonitorInfoA(hMonitor, &mi))
{
return FALSE;
}
*valid = validate_primary_monitor(mi);
return *valid ? TRUE : FALSE;
}
bool test_user_callback()
{
bool valid = false;
if (!EnumDisplayMonitors(nullptr, nullptr, monitor_enum_proc, reinterpret_cast<LPARAM>(&valid)))
{
return false;
}
return valid;
}
bool test_time_zone()
{
DYNAMIC_TIME_ZONE_INFORMATION current_dtzi = {};
@@ -908,6 +976,7 @@ 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_monitor_info, "Monitor Info")
RUN_TEST(test_time_zone, "Time Zone")
RUN_TEST(test_threads, "Threads")
RUN_TEST(test_threads_winapi, "Threads WinAPI")
@@ -921,9 +990,9 @@ int main(const int argc, const char* argv[])
RUN_TEST(test_interrupts, "Interrupts")
}
RUN_TEST(test_tls, "TLS")
RUN_TEST(test_socket, "Socket")
RUN_TEST(test_apc, "APC")
RUN_TEST(test_user_callback, "User Callback")
return valid ? 0 : 1;
}

View File

@@ -35,6 +35,48 @@ struct pending_apc
}
};
enum class callback_id : uint32_t
{
Invalid = 0,
NtUserEnumDisplayMonitors,
};
struct callback_frame
{
callback_id handler_id;
uint64_t rip;
uint64_t rsp;
uint64_t r10;
uint64_t rcx;
uint64_t rdx;
uint64_t r8;
uint64_t r9;
void serialize(utils::buffer_serializer& buffer) const
{
buffer.write(this->handler_id);
buffer.write(this->rip);
buffer.write(this->rsp);
buffer.write(this->r10);
buffer.write(this->rcx);
buffer.write(this->rdx);
buffer.write(this->r8);
buffer.write(this->r9);
}
void deserialize(utils::buffer_deserializer& buffer)
{
buffer.read(this->handler_id);
buffer.read(this->rip);
buffer.read(this->rsp);
buffer.read(this->r10);
buffer.read(this->rcx);
buffer.read(this->rdx);
buffer.read(this->r8);
buffer.read(this->r9);
}
};
class emulator_thread : public ref_counted_object
{
public:
@@ -105,6 +147,8 @@ class emulator_thread : public ref_counted_object
bool debugger_hide{false};
std::vector<callback_frame> callback_stack;
void mark_as_ready(NTSTATUS status);
bool is_await_time_over(utils::clock& clock) const
@@ -188,6 +232,8 @@ class emulator_thread : public ref_counted_object
buffer.write_vector(this->last_registers);
buffer.write(this->debugger_hide);
buffer.write_vector(this->callback_stack);
}
void deserialize_object(utils::buffer_deserializer& buffer) override
@@ -236,6 +282,8 @@ class emulator_thread : public ref_counted_object
buffer.read_vector(this->last_registers);
buffer.read(this->debugger_hide);
buffer.read_vector(this->callback_stack);
}
void leak_memory()

View File

@@ -408,6 +408,8 @@ 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});
}
/// Retrieves function arguments from registers or stack memory. This function assumes the stack pointer currently points to the
/// return address.
inline uint64_t get_function_argument(x86_64_emulator& emu, const size_t index, const bool is_syscall = false)
{
bool use_32bit_stack = false;
@@ -440,3 +442,50 @@ inline uint64_t get_function_argument(x86_64_emulator& emu, const size_t index,
return emu.read_stack(index + 1);
}
}
/// Sets function arguments in registers or stack memory. This function does not modify RSP and assumes the caller has already allocated
/// stack space and that RSP currently points to the return address.
inline void set_function_argument(x86_64_emulator& emu, const size_t index, const uint64_t value, const bool is_syscall = false)
{
bool use_32bit_stack = false;
if (!is_syscall)
{
const auto cs_selector = emu.reg<uint16_t>(x86_register::cs);
const auto bitness = segment_utils::get_segment_bitness(emu, cs_selector);
use_32bit_stack = bitness && *bitness == segment_utils::segment_bitness::bit32;
}
if (use_32bit_stack)
{
const auto esp = emu.reg<uint32_t>(x86_register::esp);
const auto address = static_cast<uint64_t>(esp) + static_cast<uint64_t>((index + 1) * sizeof(uint32_t));
emu.write_memory<uint32_t>(address, static_cast<uint32_t>(value));
return;
}
switch (index)
{
case 0:
emu.reg(is_syscall ? x86_register::r10 : x86_register::rcx, value);
break;
case 1:
emu.reg(x86_register::rdx, value);
break;
case 2:
emu.reg(x86_register::r8, value);
break;
case 3:
emu.reg(x86_register::r9, value);
break;
default:
emu.write_stack(index + 1, value);
break;
}
}
constexpr size_t aligned_stack_space(const size_t arg_count)
{
const size_t slots = (arg_count < 4) ? 4 : arg_count;
const size_t bytes = slots * sizeof(uint64_t);
return (bytes + 15) & ~15;
}

View File

@@ -21,6 +21,7 @@ struct handle_types
token,
window,
timer,
monitor,
};
};

View File

@@ -122,6 +122,7 @@ memory_stats memory_manager::compute_memory_stats() const
void memory_manager::serialize_memory_state(utils::buffer_serializer& buffer, const bool is_snapshot) const
{
buffer.write_atomic(this->layout_version_);
buffer.write(this->default_allocation_address_);
buffer.write_map(this->reserved_regions_);
if (is_snapshot)
@@ -157,6 +158,7 @@ void memory_manager::deserialize_memory_state(utils::buffer_deserializer& buffer
}
buffer.read_atomic(this->layout_version_);
buffer.read(this->default_allocation_address_);
buffer.read_map(this->reserved_regions_);
if (is_snapshot)

View File

@@ -1,12 +1,13 @@
#include "std_include.hpp"
#include "minidump_loader.hpp"
#include "windows_emulator.hpp"
#include "windows_objects.hpp"
#include "emulator_thread.hpp"
#include "common/platform/unicode.hpp"
#include "common/platform/kernel_mapped.hpp"
#include "memory_utils.hpp"
#include <platform/platform.hpp>
#include <minidump/minidump.hpp>
namespace minidump_loader

View File

@@ -15,6 +15,15 @@ namespace
return STATUS_NOT_SUPPORTED;
}
};
struct noop_port : port
{
NTSTATUS handle_request(windows_emulator& /*win_emu*/, const lpc_request_context& c) override
{
c.recv_buffer_length = 0;
return STATUS_SUCCESS;
}
};
}
std::unique_ptr<port> create_port(const std::u16string_view port)
@@ -29,6 +38,11 @@ std::unique_ptr<port> create_port(const std::u16string_view port)
return create_dns_resolver();
}
if (port == u"\\WindowsErrorReportingServicePort")
{
return std::make_unique<noop_port>();
}
return std::make_unique<dummy_port>();
}

View File

@@ -9,7 +9,13 @@ namespace
{
NTSTATUS handle_request(windows_emulator& win_emu, const lpc_request_context& c) override
{
// TODO: Fix this. This is broken and wrong.
uint32_t server_dll_index{};
win_emu.memory.read_memory(c.recv_buffer + 0x18, &server_dll_index, sizeof(server_dll_index));
if (server_dll_index != 3)
{
return STATUS_NOT_SUPPORTED;
}
try
{
@@ -17,8 +23,13 @@ namespace
const auto dest = data.read();
const auto base = dest.Base;
const auto value = base + 0x10;
win_emu.emu().write_memory(base + 8, &value, sizeof(value));
const emulator_object<USER_SHAREDINFO> shared_obj{win_emu.emu(), base + 8};
shared_obj.access([&](USER_SHAREDINFO& shared) {
shared.psi = win_emu.process.user_handles.get_server_info().value();
shared.aheList = win_emu.process.user_handles.get_handle_table().value();
shared.HeEntrySize = sizeof(USER_HANDLEENTRY);
shared.pDispInfo = win_emu.process.user_handles.get_display_info().value();
});
}
catch (...)
{

View File

@@ -176,12 +176,40 @@ namespace
return env_map;
}
uint32_t read_windows_build(registry_manager& registry)
{
const auto key = registry.get_key({R"(\Registry\Machine\Software\Microsoft\Windows NT\CurrentVersion)"});
if (!key)
{
return 0;
}
for (size_t i = 0; const auto value = registry.get_value(*key, i); ++i)
{
if (value->type != REG_SZ)
{
continue;
}
if (value->name == "CurrentBuildNumber" || value->name == "CurrentBuild")
{
const auto* s = reinterpret_cast<const char16_t*>(value->data.data());
return static_cast<uint32_t>(std::strtoul(u16_to_u8(s).c_str(), nullptr, 10));
}
}
return 0;
}
}
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, const mapped_module* ntdll32)
{
this->windows_build_number = read_windows_build(registry);
setup_gdt(emu, memory);
this->kusd.setup();
@@ -391,6 +419,94 @@ void process_context::setup(x86_64_emulator& emu, memory_manager& memory, regist
this->instrumentation_callback = 0;
this->default_register_set = emu.save_registers();
this->user_handles.setup(is_wow64_process);
auto [h, monitor_obj] = this->user_handles.allocate_object<USER_MONITOR>(handle_types::monitor);
this->default_monitor_handle = h;
monitor_obj.access([&](USER_MONITOR& monitor) {
monitor.hmon = h.bits;
monitor.rcMonitor = {.left = 0, .top = 0, .right = 1920, .bottom = 1080};
monitor.rcWork = monitor.rcMonitor;
if (this->is_older_windows_build())
{
monitor.b20.monitorDpi = 96;
monitor.b20.nativeDpi = monitor.b20.monitorDpi;
monitor.b20.cachedDpi = monitor.b20.monitorDpi;
monitor.b20.rcMonitorDpiAware = monitor.rcMonitor;
}
else
{
monitor.b26.monitorDpi = 96;
monitor.b26.nativeDpi = monitor.b26.monitorDpi;
}
});
const auto user_display_info = this->user_handles.get_display_info();
user_display_info.access([&](USER_DISPINFO& display_info) {
display_info.dwMonitorCount = 1;
display_info.pPrimaryMonitor = monitor_obj.value();
});
}
void process_context::setup_callback_hook(windows_emulator& win_emu, memory_manager& memory)
{
uint64_t sentinel_addr = this->callback_sentinel_addr;
if (!sentinel_addr)
{
using sentinel_type = std::array<uint8_t, 2>;
constexpr sentinel_type sentinel_opcodes{0x90, 0xC3}; // NOP, RET
auto sentinel_obj = this->base_allocator.reserve_page_aligned<sentinel_type>();
sentinel_addr = sentinel_obj.value();
this->callback_sentinel_addr = sentinel_addr;
win_emu.emu().write_memory(sentinel_addr, sentinel_opcodes.data(), sentinel_opcodes.size());
const auto sentinel_aligned_length = page_align_up(sentinel_addr + sentinel_opcodes.size()) - sentinel_addr;
memory.protect_memory(sentinel_addr, static_cast<size_t>(sentinel_aligned_length), memory_permission::all);
}
auto& emu = win_emu.emu();
emu.hook_memory_execution(sentinel_addr, [&](uint64_t) {
auto* t = this->active_thread;
if (!t || t->callback_stack.empty())
{
return;
}
const auto frame = t->callback_stack.back();
t->callback_stack.pop_back();
const auto callbacks_before = t->callback_stack.size();
const uint64_t guest_result = emu.reg(x86_register::rax);
emu.reg(x86_register::rip, frame.rip);
emu.reg(x86_register::rsp, frame.rsp);
emu.reg(x86_register::r10, frame.r10);
emu.reg(x86_register::rcx, frame.rcx);
emu.reg(x86_register::rdx, frame.rdx);
emu.reg(x86_register::r8, frame.r8);
emu.reg(x86_register::r9, frame.r9);
win_emu.dispatcher.dispatch_completion(win_emu, frame.handler_id, guest_result);
uint64_t target_rip = emu.reg(x86_register::rip);
emu.reg(x86_register::rip, this->callback_sentinel_addr + 1);
const bool new_callback_dispatched = t->callback_stack.size() > callbacks_before;
if (!new_callback_dispatched)
{
// Move past the syscall instruction
target_rip += 2;
}
const uint64_t ret_stack_ptr = frame.rsp - sizeof(emulator_pointer);
emu.write_memory(ret_stack_ptr, &target_rip, sizeof(target_rip));
emu.reg(x86_register::rsp, ret_stack_ptr);
});
}
void process_context::serialize(utils::buffer_serializer& buffer) const
@@ -408,6 +524,7 @@ void process_context::serialize(utils::buffer_serializer& buffer) const
buffer.write(this->kusd);
buffer.write(this->is_wow64_process);
buffer.write(this->windows_build_number);
buffer.write(this->ntdll_image_base);
buffer.write(this->ldr_initialize_thunk);
buffer.write(this->rtl_user_thread_start);
@@ -416,6 +533,8 @@ void process_context::serialize(utils::buffer_serializer& buffer) const
buffer.write(this->ki_user_exception_dispatcher);
buffer.write(this->instrumentation_callback);
buffer.write(this->user_handles);
buffer.write(this->default_monitor_handle);
buffer.write(this->events);
buffer.write(this->files);
buffer.write(this->sections);
@@ -437,6 +556,8 @@ void process_context::serialize(utils::buffer_serializer& buffer) const
buffer.write(this->threads);
buffer.write(this->threads.find_handle(this->active_thread).bits);
buffer.write(this->callback_sentinel_addr);
}
void process_context::deserialize(utils::buffer_deserializer& buffer)
@@ -454,6 +575,7 @@ void process_context::deserialize(utils::buffer_deserializer& buffer)
buffer.read(this->kusd);
buffer.read(this->is_wow64_process);
buffer.read(this->windows_build_number);
buffer.read(this->ntdll_image_base);
buffer.read(this->ldr_initialize_thunk);
buffer.read(this->rtl_user_thread_start);
@@ -462,6 +584,8 @@ void process_context::deserialize(utils::buffer_deserializer& buffer)
buffer.read(this->ki_user_exception_dispatcher);
buffer.read(this->instrumentation_callback);
buffer.read(this->user_handles);
buffer.read(this->default_monitor_handle);
buffer.read(this->events);
buffer.read(this->files);
buffer.read(this->sections);
@@ -489,6 +613,8 @@ void process_context::deserialize(utils::buffer_deserializer& buffer)
buffer.read(this->threads);
this->active_thread = this->threads.get(buffer.read<uint64_t>());
buffer.read(this->callback_sentinel_addr);
}
generic_handle_store* process_context::get_handle_store(const handle handle)

View File

@@ -14,6 +14,7 @@
#include "windows_objects.hpp"
#include "emulator_thread.hpp"
#include "port.hpp"
#include "user_handle_table.hpp"
#include "apiset/apiset.hpp"
@@ -66,7 +67,8 @@ struct process_context
base_allocator(emu),
peb64(emu),
process_params64(emu),
kusd(memory, clock)
kusd(memory, clock),
user_handles(memory)
{
}
@@ -74,6 +76,8 @@ struct process_context
const mapped_module& executable, const mapped_module& ntdll, const apiset::container& apiset_container,
const mapped_module* ntdll32 = nullptr);
void setup_callback_hook(windows_emulator& win_emu, memory_manager& memory);
handle create_thread(memory_manager& memory, uint64_t start_address, uint64_t argument, uint64_t stack_size, uint32_t create_flags,
bool initial_thread = false);
@@ -86,10 +90,17 @@ struct process_context
void serialize(utils::buffer_serializer& buffer) const;
void deserialize(utils::buffer_deserializer& buffer);
generic_handle_store* get_handle_store(handle handle);
// WOW64 support flag - set during process setup based on executable architecture
bool is_wow64_process{false};
generic_handle_store* get_handle_store(handle handle);
uint32_t windows_build_number{0};
bool is_older_windows_build() const
{
return windows_build_number < 26040;
}
callbacks* callbacks_{};
@@ -118,6 +129,8 @@ struct process_context
std::optional<emulator_object<RTL_USER_PROCESS_PARAMETERS32>> process_params32;
std::optional<uint64_t> rtl_user_thread_start32{};
user_handle_table user_handles;
handle default_monitor_handle{};
handle_store<handle_types::event, event> events{};
handle_store<handle_types::file, file> files{};
handle_store<handle_types::section, section> sections{};
@@ -125,7 +138,7 @@ struct process_context
handle_store<handle_types::semaphore, semaphore> semaphores{};
handle_store<handle_types::port, port_container> ports{};
handle_store<handle_types::mutant, mutant> mutants{};
handle_store<handle_types::window, window> windows{};
user_handle_store<handle_types::window, window> windows{user_handles};
handle_store<handle_types::timer, timer> timers{};
handle_store<handle_types::registry, registry_key, 2> registry_keys{};
std::map<uint16_t, atom_entry> atoms{};
@@ -136,6 +149,8 @@ struct process_context
handle_store<handle_types::thread, emulator_thread> threads{};
emulator_thread* active_thread{nullptr};
emulator_pointer callback_sentinel_addr{0};
// Extended parameters from last NtMapViewOfSectionEx call
// These can be used by other syscalls like NtAllocateVirtualMemoryEx
uint64_t last_extended_params_numa_node{0};

View File

@@ -22,6 +22,7 @@ void syscall_dispatcher::deserialize(utils::buffer_deserializer& buffer)
{
buffer.read_map(this->handlers_);
this->add_handlers();
this->add_callbacks();
}
void syscall_dispatcher::setup(const exported_symbols& ntdll_exports, const std::span<const std::byte> ntdll_data,
@@ -36,6 +37,7 @@ void syscall_dispatcher::setup(const exported_symbols& ntdll_exports, const std:
map_syscalls(this->handlers_, win32u_syscalls);
this->add_handlers();
this->add_callbacks();
}
void syscall_dispatcher::add_handlers()
@@ -140,6 +142,41 @@ void syscall_dispatcher::dispatch_callback(windows_emulator& win_emu, std::strin
}
}
void syscall_dispatcher::dispatch_completion(windows_emulator& win_emu, callback_id callback_id, uint64_t guest_result)
{
auto& emu = win_emu.emu();
const syscall_context c{
.win_emu = win_emu,
.emu = emu,
.proc = win_emu.process,
.write_status = true,
};
const auto entry = this->callbacks_.find(callback_id);
if (entry == this->callbacks_.end())
{
win_emu.log.error("Unknown callback: 0x%X\n", static_cast<uint32_t>(callback_id));
c.emu.stop();
return;
}
try
{
entry->second(c, guest_result);
}
catch (std::exception& e)
{
win_emu.log.error("Callback 0x%X threw an exception - %s\n", static_cast<int>(callback_id), e.what());
emu.stop();
}
catch (...)
{
win_emu.log.error("Callback 0x%X threw an unknown exception\n", static_cast<int>(callback_id));
emu.stop();
}
}
syscall_dispatcher::syscall_dispatcher(const exported_symbols& ntdll_exports, const std::span<const std::byte> ntdll_data,
const exported_symbols& win32u_exports, const std::span<const std::byte> win32u_data)
{

View File

@@ -4,6 +4,7 @@
struct syscall_context;
using syscall_handler = void (*)(const syscall_context& c);
using callback_completion_handler = void (*)(const syscall_context& c, uint64_t guest_result);
struct syscall_handler_entry
{
@@ -22,6 +23,7 @@ class syscall_dispatcher
void dispatch(windows_emulator& win_emu);
static void dispatch_callback(windows_emulator& win_emu, std::string& syscall_name);
void dispatch_completion(windows_emulator& win_emu, callback_id callback_id, uint64_t guest_result);
void serialize(utils::buffer_serializer& buffer) const;
void deserialize(utils::buffer_deserializer& buffer);
@@ -36,7 +38,9 @@ class syscall_dispatcher
private:
std::map<uint64_t, syscall_handler_entry> handlers_{};
std::map<callback_id, callback_completion_handler> callbacks_{};
static void add_handlers(std::map<std::string, syscall_handler>& handler_mapping);
void add_handlers();
void add_callbacks();
};

View File

@@ -12,6 +12,7 @@ struct syscall_context
process_context& proc;
mutable bool write_status{true};
mutable bool retrigger_syscall{false};
mutable bool run_callback{false};
};
inline uint64_t get_syscall_argument(x86_64_emulator& emu, const size_t index)
@@ -132,15 +133,16 @@ T resolve_indexed_argument(x86_64_emulator& emu, size_t& index)
return resolve_argument<T>(emu, index++);
}
inline void write_syscall_result(const syscall_context& c, const uint64_t result, const uint64_t initial_ip)
inline void write_syscall_result(const syscall_context& c, const uint64_t result, const uint64_t initial_ip,
const bool is_callback_completion = false)
{
if (c.write_status && !c.retrigger_syscall)
if (c.write_status && !c.retrigger_syscall && !c.run_callback)
{
c.emu.reg<uint64_t>(x86_register::rax, result);
}
const auto new_ip = c.emu.read_instruction_pointer();
if (initial_ip != new_ip || c.retrigger_syscall)
if ((initial_ip != new_ip || c.retrigger_syscall || c.run_callback) && !is_callback_completion)
{
c.emu.reg(x86_register::rip, new_ip - 2);
}
@@ -176,6 +178,36 @@ syscall_handler make_syscall_handler()
return +[](const syscall_context& c) { forward_syscall(c, Handler); };
}
template <typename Result>
void forward_callback_completion(const syscall_context& c, const uint64_t /*guest_result*/, Result (*handler)())
{
const auto ip = c.emu.read_instruction_pointer();
const auto ret = handler();
write_syscall_result(c, static_cast<uint64_t>(ret), ip, true);
}
template <typename Result, typename GuestResult, typename... Args>
void forward_callback_completion(const syscall_context& c, const uint64_t guest_result,
Result (*handler)(const syscall_context&, GuestResult, Args...))
{
const auto ip = c.emu.read_instruction_pointer();
size_t index = 0;
std::tuple<const syscall_context&, GuestResult, Args...> func_args{
c, static_cast<GuestResult>(guest_result),
resolve_indexed_argument<std::remove_cv_t<std::remove_reference_t<Args>>>(c.emu, index)...};
const auto ret = std::apply(handler, std::move(func_args));
write_syscall_result(c, ret, ip, true);
}
template <auto Handler>
callback_completion_handler make_callback_completion_handler()
{
return +[](const syscall_context& c, const uint64_t guest_result) { forward_callback_completion(c, guest_result, Handler); };
}
template <typename T, typename Traits>
void write_attribute(emulator& emu, const PS_ATTRIBUTE<Traits>& attribute, const T& value)
{

View File

@@ -3,6 +3,7 @@
#include "cpu_context.hpp"
#include "emulator_utils.hpp"
#include "syscall_utils.hpp"
#include "user_callback_dispatch.hpp"
#include <numeric>
#include <cwctype>
@@ -155,8 +156,11 @@ namespace syscalls
NTSTATUS handle_NtQueryObject(const syscall_context& c, handle handle, OBJECT_INFORMATION_CLASS object_information_class,
emulator_pointer object_information, ULONG object_information_length,
emulator_object<ULONG> return_length);
NTSTATUS handle_NtCompareObjects(const syscall_context& c, handle first, handle second);
NTSTATUS handle_NtWaitForMultipleObjects(const syscall_context& c, ULONG count, emulator_object<handle> handles, WAIT_TYPE wait_type,
BOOLEAN alertable, emulator_object<LARGE_INTEGER> timeout);
NTSTATUS handle_NtWaitForMultipleObjects32(const syscall_context& c, ULONG count, emulator_object<uint32_t> handles,
WAIT_TYPE wait_type, BOOLEAN alertable, emulator_object<LARGE_INTEGER> timeout);
NTSTATUS handle_NtWaitForSingleObject(const syscall_context& c, handle h, BOOLEAN alertable, emulator_object<LARGE_INTEGER> timeout);
NTSTATUS handle_NtSetInformationObject();
NTSTATUS handle_NtQuerySecurityObject(const syscall_context& c, handle /*h*/, SECURITY_INFORMATION /*security_information*/,
@@ -230,6 +234,9 @@ namespace syscalls
emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> value_name,
KEY_VALUE_INFORMATION_CLASS key_value_information_class, uint64_t key_value_information, ULONG length,
emulator_object<ULONG> result_length);
NTSTATUS handle_NtQueryMultipleValueKey(const syscall_context& c, handle key_handle, emulator_object<KEY_VALUE_ENTRY> value_entries,
ULONG entry_count, uint64_t value_buffer, emulator_object<ULONG> buffer_length,
emulator_object<ULONG> required_buffer_length);
NTSTATUS handle_NtCreateKey(const syscall_context& c, emulator_object<handle> key_handle, ACCESS_MASK desired_access,
emulator_object<OBJECT_ATTRIBUTES<EmulatorTraits<Emu64>>> object_attributes, ULONG /*title_index*/,
emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> /*class*/, ULONG /*create_options*/,
@@ -466,7 +473,7 @@ namespace syscalls
return STATUS_INVALID_HANDLE;
}
if (auto* e = c.win_emu.process.events.get(event))
if (auto* e = c.proc.events.get(event))
{
e->signaled = false;
}
@@ -834,7 +841,7 @@ namespace syscalls
const hwnd /*parent*/, const hmenu /*menu*/, const hinstance /*instance*/, const pointer /*l_param*/,
const DWORD /*flags*/, const pointer /*acbi_buffer*/)
{
window win{};
auto [handle, win] = c.proc.windows.create(c.win_emu.memory);
win.x = x;
win.y = y;
win.width = width;
@@ -843,7 +850,7 @@ namespace syscalls
win.class_name = read_large_string(class_name);
win.name = read_large_string(window_name);
return c.proc.windows.store(std::move(win)).bits;
return handle.bits;
}
BOOL handle_NtUserDestroyWindow(const syscall_context& c, const hwnd window)
@@ -1001,6 +1008,47 @@ namespace syscalls
return STATUS_UNSUCCESSFUL;
}
BOOL handle_NtUserEnumDisplayMonitors(const syscall_context& c, const hdc hdc_in, const uint64_t clip_rect_ptr, const uint64_t callback,
const uint64_t param)
{
if (!callback)
{
return FALSE;
}
const auto hmon = c.win_emu.process.default_monitor_handle.bits;
const auto display_info = c.proc.user_handles.get_display_info().read();
if (clip_rect_ptr)
{
RECT clip{};
c.emu.read_memory(clip_rect_ptr, &clip, sizeof(clip));
const emulator_object<USER_MONITOR> monitor_obj(c.emu, display_info.pPrimaryMonitor);
const auto monitor = monitor_obj.read();
auto effective_rc{monitor.rcMonitor};
effective_rc.left = std::max(effective_rc.left, clip.left);
effective_rc.top = std::max(effective_rc.top, clip.top);
effective_rc.right = std::min(effective_rc.right, clip.right);
effective_rc.bottom = std::min(effective_rc.bottom, clip.bottom);
if (effective_rc.right <= effective_rc.left || effective_rc.bottom <= effective_rc.top)
{
return TRUE;
}
}
const uint64_t rect_ptr = display_info.pPrimaryMonitor + offsetof(USER_MONITOR, rcMonitor);
dispatch_user_callback(c, callback_id::NtUserEnumDisplayMonitors, callback, hmon, hdc_in, rect_ptr, param);
return {};
}
BOOL completion_NtUserEnumDisplayMonitors(const syscall_context&, BOOL guest_result, const hdc /*hdc_in*/,
const uint64_t /*clip_rect_ptr*/, const uint64_t /*callback*/, const uint64_t /*param*/)
{
return guest_result;
}
NTSTATUS handle_NtAssociateWaitCompletionPacket()
{
return STATUS_SUCCESS;
@@ -1030,6 +1078,32 @@ namespace syscalls
{
return STATUS_NOT_SUPPORTED;
}
BOOL handle_NtUserGetHDevName(const syscall_context& c, handle hdev, emulator_pointer device_name)
{
if (hdev != c.proc.default_monitor_handle)
{
return FALSE;
}
const std::u16string name = u"\\\\.\\DISPLAY1";
c.emu.write_memory(device_name, name.c_str(), (name.size() + 1) * sizeof(char16_t));
return TRUE;
}
emulator_pointer handle_NtUserMapDesktopObject(const syscall_context& c, handle handle)
{
const auto index = handle.value.id;
if (index == 0 || index >= user_handle_table::MAX_HANDLES)
{
return 0;
}
const auto handle_entry = c.proc.user_handles.get_handle_table().read(static_cast<size_t>(index));
return handle_entry.pHead;
}
}
void syscall_dispatcher::add_handlers(std::map<std::string, syscall_handler>& handler_mapping)
@@ -1143,6 +1217,7 @@ void syscall_dispatcher::add_handlers(std::map<std::string, syscall_handler>& ha
add_handler(NtSetInformationFile);
add_handler(NtUserRegisterWindowMessage);
add_handler(NtQueryValueKey);
add_handler(NtQueryMultipleValueKey);
add_handler(NtQueryKey);
add_handler(NtGetNlsSectionPtr);
add_handler(NtAccessCheck);
@@ -1151,8 +1226,10 @@ void syscall_dispatcher::add_handlers(std::map<std::string, syscall_handler>& ha
add_handler(NtGetCurrentProcessorNumberEx);
add_handler(NtGetCurrentProcessorNumber);
add_handler(NtQueryObject);
add_handler(NtCompareObjects);
add_handler(NtQueryAttributesFile);
add_handler(NtWaitForMultipleObjects);
add_handler(NtWaitForMultipleObjects32);
add_handler(NtCreateMutant);
add_handler(NtReleaseMutant);
add_handler(NtDuplicateToken);
@@ -1216,6 +1293,7 @@ void syscall_dispatcher::add_handlers(std::map<std::string, syscall_handler>& ha
add_handler(NtUserGetKeyboardType);
add_handler(NtUserEnumDisplayDevices);
add_handler(NtUserEnumDisplaySettings);
add_handler(NtUserEnumDisplayMonitors);
add_handler(NtUserSetProp);
add_handler(NtUserSetProp2);
add_handler(NtUserChangeWindowMessageFilterEx);
@@ -1243,6 +1321,21 @@ void syscall_dispatcher::add_handlers(std::map<std::string, syscall_handler>& ha
add_handler(NtSetInformationDebugObject);
add_handler(NtRemoveProcessDebug);
add_handler(NtNotifyChangeDirectoryFileEx);
add_handler(NtUserGetHDevName);
add_handler(NtUserMapDesktopObject);
#undef add_handler
}
void syscall_dispatcher::add_callbacks()
{
#define add_callback(syscall) \
do \
{ \
this->callbacks_[callback_id::syscall] = make_callback_completion_handler<syscalls::completion_##syscall>(); \
} while (0)
add_callback(NtUserEnumDisplayMonitors);
#undef add_callback
}

View File

@@ -267,6 +267,11 @@ namespace syscalls
|| h.value.type == handle_types::event;
}
NTSTATUS handle_NtCompareObjects(const syscall_context&, const handle first, const handle second)
{
return (first == second) ? STATUS_SUCCESS : STATUS_NOT_SAME_OBJECT;
}
NTSTATUS handle_NtWaitForMultipleObjects(const syscall_context& c, const ULONG count, const emulator_object<handle> handles,
const WAIT_TYPE wait_type, const BOOLEAN alertable,
const emulator_object<LARGE_INTEGER> timeout)
@@ -304,6 +309,44 @@ namespace syscalls
return STATUS_SUCCESS;
}
NTSTATUS handle_NtWaitForMultipleObjects32(const syscall_context& c, const ULONG count, const emulator_object<uint32_t> handles,
const WAIT_TYPE wait_type, const BOOLEAN alertable,
const emulator_object<LARGE_INTEGER> timeout)
{
if (wait_type != WaitAny && wait_type != WaitAll)
{
c.win_emu.log.error("Wait type not supported!\n");
c.emu.stop();
return STATUS_NOT_SUPPORTED;
}
auto& t = c.win_emu.current_thread();
t.await_objects.clear();
t.await_any = wait_type == WaitAny;
for (ULONG i = 0; i < count; ++i)
{
const auto raw_handle = handles.read(i);
const auto h = make_handle(static_cast<uint64_t>(raw_handle));
if (!is_awaitable_object_type(h))
{
c.win_emu.log.warn("Unsupported handle type for NtWaitForMultipleObjects32: %d!\n", h.value.type);
return STATUS_INVALID_HANDLE;
}
t.await_objects.push_back(h);
}
if (timeout.value() && !t.await_time.has_value())
{
t.await_time = utils::convert_delay_interval_to_time_point(c.win_emu.clock(), timeout.read());
}
c.win_emu.yield_thread(alertable);
return STATUS_SUCCESS;
}
NTSTATUS handle_NtWaitForSingleObject(const syscall_context& c, const handle h, const BOOLEAN alertable,
const emulator_object<LARGE_INTEGER> timeout)
{

View File

@@ -261,6 +261,86 @@ namespace syscalls
return STATUS_NOT_SUPPORTED;
}
NTSTATUS handle_NtQueryMultipleValueKey(const syscall_context& c, const handle key_handle,
const emulator_object<KEY_VALUE_ENTRY> value_entries, const ULONG entry_count,
const uint64_t value_buffer, const emulator_object<ULONG> buffer_length,
const emulator_object<ULONG> required_buffer_length)
{
if (entry_count > 0x10000)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
const auto* key = c.proc.registry_keys.get(key_handle);
if (!key)
{
return STATUS_INVALID_HANDLE;
}
NTSTATUS status = STATUS_SUCCESS;
auto remaining_length = buffer_length.read();
ULONG required_length = 0;
ULONG written_bytes = 0;
for (ULONG i = 0; i < entry_count; i++)
{
auto entry = value_entries.read(i);
if (!entry.ValueName)
{
status = STATUS_INVALID_PARAMETER;
break;
}
const auto query_name = read_unicode_string(c.emu, entry.ValueName);
if (c.win_emu.callbacks.on_generic_access)
{
// TODO: Find a better way to log this
c.win_emu.callbacks.on_generic_access("Querying multiple value key ", query_name + u" (" + key->to_string() + u")");
}
const auto value = c.win_emu.registry.get_value(*key, u16_to_u8(query_name));
if (!value)
{
status = STATUS_OBJECT_NAME_NOT_FOUND;
break;
}
const auto data_length = static_cast<ULONG>(value->data.size());
if (status == STATUS_SUCCESS)
{
if (remaining_length >= data_length)
{
entry.DataOffset = written_bytes;
entry.DataLength = data_length;
entry.Type = value->type;
c.emu.write_memory(value_buffer + entry.DataOffset, value->data.data(), entry.DataLength);
value_entries.write(entry, i);
remaining_length -= data_length;
written_bytes += data_length;
}
else
{
status = STATUS_BUFFER_OVERFLOW;
}
}
required_length += data_length;
}
buffer_length.write(written_bytes);
if (required_buffer_length.value())
{
required_buffer_length.write(required_length);
}
return status;
}
NTSTATUS handle_NtCreateKey(const syscall_context& c, const emulator_object<handle> key_handle, const ACCESS_MASK desired_access,
const emulator_object<OBJECT_ATTRIBUTES<EmulatorTraits<Emu64>>> object_attributes,
const ULONG /*title_index*/, const emulator_object<UNICODE_STRING<EmulatorTraits<Emu64>>> /*class*/,

View File

@@ -0,0 +1,48 @@
#pragma once
#include "syscall_utils.hpp"
// TODO: Here we are calling guest functions directly, but this is not how it works in the real Windows kernel.
// In the real implementation, the kernel invokes ntdll!KiUserCallbackDispatcher and passes a callback
// index that refers to an entry in PEB->KernelCallbackTable. The dispatcher then looks up the function
// pointer in that table and invokes the corresponding user-mode callback.
template <typename... Args>
void prepare_call_stack(x86_64_emulator& emu, uint64_t return_address, Args... args)
{
constexpr size_t arg_count = sizeof...(Args);
const size_t stack_args_size = aligned_stack_space(arg_count);
const uint64_t current_rsp = emu.read_stack_pointer();
const uint64_t aligned_rsp = align_down(current_rsp, 16);
// We subtract the args size (including the shadow space) AND the size of the return address
const uint64_t new_rsp = aligned_rsp - stack_args_size - sizeof(emulator_pointer);
emu.reg(x86_register::rsp, new_rsp);
emu.write_memory(new_rsp, &return_address, sizeof(return_address));
size_t index = 0;
(set_function_argument(emu, index++, static_cast<uint64_t>(args)), ...);
}
template <typename... Args>
void dispatch_user_callback(const syscall_context& c, callback_id completion_id, uint64_t func_address, Args... args)
{
const callback_frame frame{
.handler_id = completion_id,
.rip = c.emu.read_instruction_pointer(),
.rsp = c.emu.read_stack_pointer(),
.r10 = c.emu.reg(x86_register::r10),
.rcx = c.emu.reg(x86_register::rcx),
.rdx = c.emu.reg(x86_register::rdx),
.r8 = c.emu.reg(x86_register::r8),
.r9 = c.emu.reg(x86_register::r9),
};
c.proc.active_thread->callback_stack.push_back(frame);
prepare_call_stack(c.emu, c.proc.callback_sentinel_addr, args...);
c.emu.reg(x86_register::rip, func_address);
c.run_callback = true;
}

View File

@@ -0,0 +1,406 @@
#pragma once
#include "emulator_utils.hpp"
#include "handles.hpp"
class user_handle_table
{
public:
static constexpr uint32_t MAX_HANDLES = 0xFFFF;
user_handle_table(memory_manager& memory)
: memory_(&memory)
{
}
void setup(const bool is_wow64_process)
{
this->is_wow64_process_ = is_wow64_process;
used_indices_.resize(MAX_HANDLES, false);
const auto server_info_size = static_cast<size_t>(page_align_up(sizeof(USER_SERVERINFO)));
server_info_addr_ = this->allocate_memory(server_info_size, memory_permission::read);
const auto display_info_size = static_cast<size_t>(page_align_up(sizeof(USER_DISPINFO)));
display_info_addr_ = this->allocate_memory(display_info_size, memory_permission::read);
const emulator_object<USER_SERVERINFO> srv_obj(*memory_, server_info_addr_);
srv_obj.access([&](USER_SERVERINFO& srv) {
srv.cHandleEntries = MAX_HANDLES - 1; //
});
const auto handle_table_size = static_cast<size_t>(page_align_up(sizeof(USER_HANDLEENTRY) * MAX_HANDLES));
handle_table_addr_ = this->allocate_memory(handle_table_size, memory_permission::read);
}
emulator_object<USER_SHAREDINFO> get_server_info() const
{
return {*memory_, server_info_addr_};
}
emulator_object<USER_HANDLEENTRY> get_handle_table() const
{
return {*memory_, handle_table_addr_};
}
emulator_object<USER_DISPINFO> get_display_info() const
{
return {*memory_, display_info_addr_};
}
template <typename T>
std::pair<handle, emulator_object<T>> allocate_object(handle_types::type type)
{
const auto index = find_free_index();
const auto alloc_size = static_cast<size_t>(page_align_up(sizeof(T)));
const auto alloc_ptr = this->allocate_memory(alloc_size, memory_permission::read);
const emulator_object<T> alloc_obj(*memory_, alloc_ptr);
const emulator_object<USER_HANDLEENTRY> handle_table_obj(*memory_, handle_table_addr_);
handle_table_obj.access(
[&](USER_HANDLEENTRY& entry) {
entry.pHead = alloc_ptr;
entry.bType = get_native_type(type);
entry.wUniq = static_cast<uint16_t>(type << 7);
},
index);
used_indices_[index] = true;
return {make_handle(index, type, false), alloc_obj};
}
void free_index(uint32_t index)
{
if (index >= used_indices_.size() || !used_indices_[index])
{
return;
}
used_indices_[index] = false;
const emulator_object<USER_HANDLEENTRY> handle_table_obj(*memory_, handle_table_addr_);
handle_table_obj.access(
[&](USER_HANDLEENTRY& entry) {
memory_->release_memory(entry.pHead, 0);
entry = {};
},
index);
}
void serialize(utils::buffer_serializer& buffer) const
{
buffer.write(server_info_addr_);
buffer.write(handle_table_addr_);
buffer.write(display_info_addr_);
buffer.write_vector(used_indices_);
buffer.write(is_wow64_process_);
}
void deserialize(utils::buffer_deserializer& buffer)
{
buffer.read(server_info_addr_);
buffer.read(handle_table_addr_);
buffer.read(display_info_addr_);
buffer.read_vector(used_indices_);
buffer.read(is_wow64_process_);
}
private:
uint32_t find_free_index() const
{
for (uint32_t i = 1; i < used_indices_.size(); ++i)
{
if (!used_indices_[i])
{
return i;
}
}
throw std::runtime_error("No more user handles available");
}
static uint8_t get_native_type(handle_types::type type)
{
switch (type)
{
case handle_types::type::window:
return TYPE_WINDOW;
case handle_types::type::monitor:
return TYPE_MONITOR;
default:
throw std::runtime_error("Unhandled handle type!");
}
}
uint64_t allocate_memory(const size_t size, const nt_memory_permission permissions)
{
const auto allocation_base = this->is_wow64_process_ ? DEFAULT_ALLOCATION_ADDRESS_32BIT : DEFAULT_ALLOCATION_ADDRESS_64BIT;
const auto base = memory_->find_free_allocation_base(size, allocation_base);
return memory_->allocate_memory(size, permissions, false, base);
}
uint64_t server_info_addr_{};
uint64_t handle_table_addr_{};
uint64_t display_info_addr_{};
std::vector<bool> used_indices_{};
memory_manager* memory_{};
bool is_wow64_process_{};
};
template <handle_types::type Type, typename T>
requires(utils::Serializable<T> && std::is_base_of_v<ref_counted_object, T>)
class user_handle_store : public generic_handle_store
{
public:
using index_type = uint32_t;
using value_map = std::map<index_type, T>;
explicit user_handle_store(user_handle_table& table)
: table_(&table)
{
}
std::pair<handle, T&> create(memory_interface& memory)
{
if (this->block_mutation_)
{
throw std::runtime_error("Mutation of user object store is blocked!");
}
auto [h, guest_obj] = table_->allocate_object<typename T::guest_type>(Type);
T new_obj(memory);
new_obj.guest = std::move(guest_obj);
const auto index = static_cast<uint32_t>(h.value.id);
const auto it = this->store_.emplace(index, std::move(new_obj)).first;
return {h, it->second};
}
bool block_mutation(bool blocked)
{
std::swap(this->block_mutation_, blocked);
return blocked;
}
handle make_handle(const index_type index) const
{
handle h{};
h.bits = 0;
h.value.is_pseudo = false;
h.value.type = Type;
h.value.id = index;
return h;
}
T* get_by_index(const uint32_t index)
{
const auto it = this->store_.find(index);
if (it == this->store_.end())
{
return nullptr;
}
return &it->second;
}
T* get(const handle_value h)
{
if (h.type != Type || h.is_pseudo)
{
return nullptr;
}
return this->get_by_index(static_cast<uint32_t>(h.id));
}
T* get(const handle h)
{
return this->get(h.value);
}
T* get(const uint64_t h)
{
handle hh{};
hh.bits = h;
return this->get(hh);
}
size_t size() const
{
return this->store_.size();
}
std::optional<handle> duplicate(const handle h) override
{
auto* entry = this->get(h);
if (!entry)
{
return std::nullopt;
}
++entry->ref_count;
return h;
}
std::pair<typename value_map::iterator, bool> erase(const typename value_map::iterator& entry)
{
if (this->block_mutation_)
{
throw std::runtime_error("Mutation of handle store is blocked!");
}
if (entry == this->store_.end())
{
return {entry, false};
}
if constexpr (handle_detail::has_deleter_function<T>())
{
if (!T::deleter(entry->second))
{
return {entry, true};
}
}
auto new_iter = this->store_.erase(entry);
return {new_iter, true};
}
bool erase(const handle_value h)
{
if (this->block_mutation_)
{
throw std::runtime_error("Mutation of user object store is blocked!");
}
if (h.type != Type || h.is_pseudo)
{
return false;
}
const auto index = static_cast<uint32_t>(h.id);
const auto entry = this->store_.find(index);
if (entry == this->store_.end())
{
return false;
}
if constexpr (handle_detail::has_deleter_function<T>())
{
if (!T::deleter(entry->second))
{
return false;
}
}
table_->free_index(index);
this->store_.erase(entry);
return true;
}
bool erase(const handle h) override
{
return this->erase(h.value);
}
bool erase(const uint64_t h)
{
handle hh{};
hh.bits = h;
return this->erase(hh);
}
bool erase(const T& value)
{
const auto entry = this->find(value);
if (entry == this->store_.end())
{
return false;
}
return this->erase(make_handle(entry->first));
}
typename value_map::iterator find(const T& value)
{
auto i = this->store_.begin();
for (; i != this->store_.end(); ++i)
{
if (&i->second == &value)
{
break;
}
}
return i;
}
typename value_map::const_iterator find(const T& value) const
{
auto i = this->store_.begin();
for (; i != this->store_.end(); ++i)
{
if (&i->second == &value)
{
break;
}
}
return i;
}
handle find_handle(const T& value) const
{
const auto entry = this->find(value);
if (entry == this->end())
{
return {};
}
return this->make_handle(entry->first);
}
handle find_handle(const T* value) const
{
if (!value)
{
return {};
}
return this->find_handle(*value);
}
typename value_map::iterator begin()
{
return this->store_.begin();
}
typename value_map::const_iterator begin() const
{
return this->store_.begin();
}
typename value_map::iterator end()
{
return this->store_.end();
}
typename value_map::const_iterator end() const
{
return this->store_.end();
}
void serialize(utils::buffer_serializer& buffer) const
{
buffer.write(this->block_mutation_);
buffer.write_map(this->store_);
}
void deserialize(utils::buffer_deserializer& buffer)
{
buffer.read(this->block_mutation_);
buffer.read_map(this->store_);
}
private:
user_handle_table* table_;
bool block_mutation_{false};
value_map store_{};
};

View File

@@ -554,6 +554,7 @@ void windows_emulator::start(size_t count)
{
this->should_stop = false;
this->setup_process_if_necessary();
this->process.setup_callback_hook(*this, this->memory);
const auto use_count = count > 0;
const auto start_instructions = this->executed_instructions_;

View File

@@ -54,19 +54,47 @@ struct event : ref_counted_object
}
};
struct window : ref_counted_object
template <typename GuestType>
struct user_object : ref_counted_object
{
using guest_type = GuestType;
emulator_object<GuestType> guest;
user_object(memory_interface& memory)
: guest(memory)
{
}
void serialize_object(utils::buffer_serializer& buffer) const override
{
buffer.write(this->guest);
}
void deserialize_object(utils::buffer_deserializer& buffer) override
{
buffer.read(this->guest);
}
};
struct window : user_object<USER_WINDOW>
{
uint32_t thread_id{};
std::u16string name{};
std::u16string class_name{};
int32_t width;
int32_t height;
int32_t x;
int32_t y;
int32_t width{};
int32_t height{};
int32_t x{};
int32_t y{};
std::unordered_map<std::u16string, uint64_t> props;
window(memory_interface& memory)
: user_object(memory)
{
}
void serialize_object(utils::buffer_serializer& buffer) const override
{
user_object::serialize_object(buffer);
buffer.write(this->thread_id);
buffer.write(this->name);
buffer.write(this->class_name);
@@ -79,6 +107,7 @@ struct window : ref_counted_object
void deserialize_object(utils::buffer_deserializer& buffer) override
{
user_object::deserialize_object(buffer);
buffer.read(this->thread_id);
buffer.read(this->name);
buffer.read(this->class_name);
@@ -186,7 +215,6 @@ struct file : ref_counted_object
utils::file_handle handle{};
std::u16string name{};
std::optional<file_enumeration_state> enumeration_state{};
std::optional<std::u16string> deferred_rename;
bool is_file() const
{