diff --git a/README.md b/README.md
index 40460e6..49ac8ca 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,9 @@ To use this repository from another project:
7. Disable warning 4324 (structure was padded due to alignment) in the project properties, by adding 4324 to
Configuration Properties -> C/C++ -> Advanced -> Disable Specific Warnings.
8. Make sure you are not linking with any kernel libs, since they aren't usable by DLLs.
+9. Give usersim.lib preference over ntdll.lib as follows:
+ Under Configuration Properties -> Linker -> Input -> Ignore Specific Default Libraries, add ntdll.lib.
+ Under Configuration Properties -> Linker -> Input -> Additional Dependencies, add usersim.lib before ntdll.lib.
### Leak Detection
diff --git a/inc/usersim/io.h b/inc/usersim/io.h
index 3b8fa0e..7826f34 100644
--- a/inc/usersim/io.h
+++ b/inc/usersim/io.h
@@ -23,7 +23,7 @@ extern "C"
CustomPriorityWorkQueue = 32
} WORK_QUEUE_TYPE;
- typedef struct _DEVICE_OBJECT DEVICE_OBJECT;
+ typedef struct _DEVICE_OBJECT DEVICE_OBJECT, *PDEVICE_OBJECT;
typedef struct _DRIVER_OBJECT DRIVER_OBJECT, *PDRIVER_OBJECT;
diff --git a/inc/usersim/rtl.h b/inc/usersim/rtl.h
index b59e3b3..2c4886c 100644
--- a/inc/usersim/rtl.h
+++ b/inc/usersim/rtl.h
@@ -153,7 +153,15 @@ extern "C"
_Out_ PUNICODE_STRING destination_string,
_In_opt_z_ __drv_aliasesMem PCWSTR source_string);
- typedef struct _object_attributes OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
+ typedef struct _OBJECT_ATTRIBUTES
+ {
+ ULONG Length;
+ HANDLE RootDirectory;
+ PUNICODE_STRING ObjectName;
+ ULONG Attributes;
+ SECURITY_DESCRIPTOR* SecurityDescriptor;
+ SECURITY_QUALITY_OF_SERVICE* SecurityQualityOfService;
+ } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
// Include Rtl* implementations from ntdll.lib.
#pragma comment(lib, "ntdll.lib")
diff --git a/inc/usersim/wdf.h b/inc/usersim/wdf.h
index dd6744d..285cc5c 100644
--- a/inc/usersim/wdf.h
+++ b/inc/usersim/wdf.h
@@ -47,6 +47,7 @@ extern "C"
struct _DRIVER_OBJECT
{
WDF_DRIVER_CONFIG config;
+ PDEVICE_OBJECT device;
};
#define WDF_DRIVER_GLOBALS_NAME_LEN (32)
@@ -130,7 +131,6 @@ extern "C"
_In_ PWDF_FILEOBJECT_CONFIG file_object_config,
_In_opt_ PWDF_OBJECT_ATTRIBUTES file_object_attributes);
-
typedef NTSTATUS(FN_WDFDEVICE_WDM_IRP_PREPROCESS)(_In_ WDFDEVICE device, _Inout_ IRP* irp);
typedef FN_WDFDEVICE_WDM_IRP_PREPROCESS* PFN_WDFDEVICE_WDM_IRP_PREPROCESS;
@@ -163,6 +163,12 @@ extern "C"
_In_opt_ PWDF_OBJECT_ATTRIBUTES queue_attributes,
_Out_opt_ WDFQUEUE* queue);
+ typedef _IRQL_requires_max_(DISPATCH_LEVEL)
+ VOID(WdfControlFinishInitializing_t)(_In_ PWDF_DRIVER_GLOBALS driver_globals, _In_ WDFDEVICE device);
+
+ typedef _IRQL_requires_max_(DISPATCH_LEVEL)
+ PDEVICE_OBJECT(WdfDeviceWdmGetDeviceObject_t)(_In_ PWDF_DRIVER_GLOBALS driver_globals, _In_ WDFDEVICE device);
+
typedef HANDLE WDFOBJECT;
typedef _IRQL_requires_max_(DISPATCH_LEVEL)
@@ -171,6 +177,8 @@ extern "C"
typedef enum _WDFFUNCENUM
{
WdfControlDeviceInitAllocateTableIndex = 25,
+ WdfControlFinishInitializingTableIndex = 27,
+ WdfDeviceWdmGetDeviceObjectTableIndex = 31,
WdfDeviceInitFreeTableIndex = 54,
WdfDeviceInitSetDeviceTypeTableIndex = 66,
WdfDeviceInitAssignNameTableIndex = 67,
diff --git a/inc/usersim/zw.h b/inc/usersim/zw.h
new file mode 100644
index 0000000..129e672
--- /dev/null
+++ b/inc/usersim/zw.h
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation
+// SPDX-License-Identifier: MIT
+#pragma once
+#include "usersim/rtl.h" // For UNICODE_STRING
+
+#if defined(__cplusplus)
+extern "C"
+{
+#endif
+ USERSIM_API _IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS
+ ZwCreateKey(
+ _Out_ PHANDLE key_handle,
+ _In_ ACCESS_MASK desired_access,
+ _In_ POBJECT_ATTRIBUTES object_attributes,
+ _Reserved_ ULONG title_index,
+ _In_opt_ PUNICODE_STRING class_string,
+ _In_ ULONG create_options,
+ _Out_opt_ PULONG disposition);
+
+ // The following APIs are exported by ntdll.dll but the prototypes
+ // are not in system headers, so define them here.
+
+ NTSTATUS
+ ZwDeleteKey(_In_ HANDLE key_handle);
+
+ NTSTATUS
+ ZwClose(_In_ HANDLE handle);
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index aa619bd..2180b96 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -34,6 +34,7 @@ add_library(usersim SHARED
tracelog.h
utilities.h
wdf.cpp
+ zw.cpp
Source.def
)
diff --git a/src/usersim.vcxproj b/src/usersim.vcxproj
index 85f8a48..5c2707c 100644
--- a/src/usersim.vcxproj
+++ b/src/usersim.vcxproj
@@ -212,6 +212,7 @@
+
@@ -221,6 +222,7 @@
+
diff --git a/src/usersim.vcxproj.filters b/src/usersim.vcxproj.filters
index da1cd87..c903c83 100644
--- a/src/usersim.vcxproj.filters
+++ b/src/usersim.vcxproj.filters
@@ -75,6 +75,9 @@
Source Files
+
+ Source Files
+
@@ -146,6 +149,9 @@
Header Files
+
+ Header Files
+
diff --git a/src/wdf.cpp b/src/wdf.cpp
index 0d71b68..a5af561 100644
--- a/src/wdf.cpp
+++ b/src/wdf.cpp
@@ -12,6 +12,7 @@ WDF_DRIVER_GLOBALS g_UsersimWdfDriverGlobals = {0};
static WdfDriverCreate_t _WdfDriverCreate;
static WdfDeviceCreate_t _WdfDeviceCreate;
static WdfControlDeviceInitAllocate_t _WdfControlDeviceInitAllocate;
+static WdfControlFinishInitializing_t _WdfControlFinishInitializing;
static WdfDeviceInitFree_t _WdfDeviceInitFree;
static WdfDeviceInitSetDeviceType_t _WdfDeviceInitSetDeviceType;
static WdfDeviceInitSetCharacteristics_t _WdfDeviceInitSetCharacteristics;
@@ -20,6 +21,7 @@ static WdfDeviceInitSetFileObjectConfig_t _WdfDeviceInitSetFileObjectConfig;
static WdfDeviceInitAssignWdmIrpPreprocessCallback_t _WdfDeviceInitAssignWdmIrpPreprocessCallback;
static WdfDeviceCreateSymbolicLink_t _WdfDeviceCreateSymbolicLink;
static WdfIoQueueCreate_t _WdfIoQueueCreate;
+static WdfDeviceWdmGetDeviceObject_t _WdfDeviceWdmGetDeviceObject;
static WdfObjectDelete_t _WdfObjectDelete;
static NTSTATUS
@@ -42,6 +44,7 @@ _WdfDriverCreate(
}
g_UsersimWdfDriverGlobals.Driver = driver_object;
driver_object->config = *driver_config;
+ driver_object->device = nullptr;
if (driver != nullptr) {
*driver = driver_object;
}
@@ -64,6 +67,8 @@ _WdfDeviceCreate(
return STATUS_INSUFFICIENT_RESOURCES;
}
+ DRIVER_OBJECT* driver_object = (DRIVER_OBJECT*)driver_globals->Driver;
+ driver_object->device = (PDEVICE_OBJECT)*device_init;
*device = *device_init;
return STATUS_SUCCESS;
}
@@ -243,6 +248,20 @@ _WdfIoQueueCreate(
return STATUS_SUCCESS;
}
+_IRQL_requires_max_(DISPATCH_LEVEL) VOID
+_WdfControlFinishInitializing(_In_ PWDF_DRIVER_GLOBALS driver_globals, _In_ WDFDEVICE device)
+{
+ UNREFERENCED_PARAMETER(driver_globals);
+ UNREFERENCED_PARAMETER(device);
+}
+
+_IRQL_requires_max_(DISPATCH_LEVEL) PDEVICE_OBJECT
+_WdfDeviceWdmGetDeviceObject(_In_ PWDF_DRIVER_GLOBALS driver_globals, _In_ WDFDEVICE device)
+{
+ UNREFERENCED_PARAMETER(driver_globals);
+ return (PDEVICE_OBJECT)device;
+}
+
_IRQL_requires_max_(DISPATCH_LEVEL) VOID
_WdfObjectDelete(_In_ PWDF_DRIVER_GLOBALS driver_globals, _In_ WDFOBJECT object)
{
@@ -257,6 +276,8 @@ void
usersim_initialize_wdf()
{
g_UsersimWdfFunctions[WdfControlDeviceInitAllocateTableIndex] = (WDFFUNC)_WdfControlDeviceInitAllocate;
+ g_UsersimWdfFunctions[WdfControlFinishInitializingTableIndex] = (WDFFUNC)_WdfControlFinishInitializing;
+ g_UsersimWdfFunctions[WdfDeviceWdmGetDeviceObjectTableIndex] = (WDFFUNC)_WdfDeviceWdmGetDeviceObject;
g_UsersimWdfFunctions[WdfDeviceInitFreeTableIndex] = (WDFFUNC)_WdfDeviceInitFree;
g_UsersimWdfFunctions[WdfDeviceInitSetDeviceTypeTableIndex] = (WDFFUNC)_WdfDeviceInitSetDeviceType;
g_UsersimWdfFunctions[WdfDeviceInitAssignNameTableIndex] = (WDFFUNC)_WdfDeviceInitAssignName;
diff --git a/src/zw.cpp b/src/zw.cpp
new file mode 100644
index 0000000..b950e5d
--- /dev/null
+++ b/src/zw.cpp
@@ -0,0 +1,82 @@
+// Copyright (c) Microsoft Corporation
+// SPDX-License-Identifier: MIT
+//
+// This file contains Usersim overrides of some Zw* APIs
+// exposed by ntdll.dll, where the behavior of such APIs
+// differs between kernel mode and unprivileged user
+// mode, so that we can emulate kernel mode behavior to
+// unprivileged user mode test processes.
+#include "fault_injection.h"
+#include "usersim/zw.h"
+#include "utilities.h"
+#include
+
+_IRQL_requires_max_(PASSIVE_LEVEL) NTSTATUS ZwCreateKey(
+ _Out_ PHANDLE key_handle,
+ _In_ ACCESS_MASK desired_access,
+ _In_ POBJECT_ATTRIBUTES object_attributes,
+ _Reserved_ ULONG title_index,
+ _In_opt_ PUNICODE_STRING class_string,
+ _In_ ULONG create_options,
+ _Out_opt_ PULONG disposition)
+{
+ if (usersim_fault_injection_inject_fault()) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ if (class_string != nullptr || title_index != 0) {
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ UNREFERENCED_PARAMETER(create_options);
+
+ HKEY root_key = (HKEY)object_attributes->RootDirectory;
+ std::wstring relative_path(object_attributes->ObjectName->Buffer, object_attributes->ObjectName->Length);
+ PCWSTR hklm_path = L"\\Registry\\Machine\\";
+ PCWSTR hkcu_path = L"\\Registry\\User\\";
+ if (relative_path.starts_with(hklm_path)) {
+ root_key = HKEY_LOCAL_MACHINE;
+ relative_path = relative_path.substr(wcslen(hklm_path));
+ } else if (relative_path.starts_with(hkcu_path)) {
+ root_key = HKEY_CURRENT_USER;
+ relative_path = relative_path.substr(wcslen(hkcu_path));
+ }
+
+ std::wstring class_wstring;
+ if (class_string) {
+ class_wstring.assign(class_string->Buffer, class_string->Length);
+ }
+ ULONG result = RegCreateKeyExW(
+ root_key,
+ relative_path.c_str(),
+ 0, // Reserved
+ (class_string ? (PWSTR)class_wstring.c_str() : nullptr),
+ REG_OPTION_VOLATILE,
+ desired_access,
+ nullptr,
+ (PHKEY)key_handle,
+ disposition);
+ if (result == ERROR_ACCESS_DENIED && root_key == HKEY_LOCAL_MACHINE) {
+ // Try again with HKCU, so access to \Registry\Machine will succeed
+ // like it should when called from "kernel-mode" code.
+ //
+ // Note that HKLM will fail with access denied, even when the desired
+ // access is only read access, unless the process is running as admin,
+ // since we use RegCreateKey instead of RegOpenKey. This ensures that
+ // the same registry key will be accessed across multiple calls
+ // regardless of desired access.
+
+ root_key = HKEY_CURRENT_USER;
+ result = RegCreateKeyExW(
+ root_key,
+ relative_path.c_str(),
+ 0, // Reserved
+ (class_string ? (PWSTR)class_wstring.c_str() : nullptr),
+ REG_OPTION_VOLATILE,
+ desired_access,
+ nullptr,
+ (PHKEY)key_handle,
+ disposition);
+ }
+
+ return win32_error_code_to_usersim_result(result);
+}
\ No newline at end of file
diff --git a/tests/tests.vcxproj b/tests/tests.vcxproj
index 4fc6bbc..1e963d0 100644
--- a/tests/tests.vcxproj
+++ b/tests/tests.vcxproj
@@ -129,7 +129,8 @@
Console
true
- ntdll.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)
+ usersim.lib;ntdll.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)
+ $(OutDir);$(VC_LibraryPath_VC_x64_Desktop);%(Link.AdditionalLibraryDirectories)
@@ -148,7 +149,8 @@
true
true
true
- ntdll.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)
+ usersim.lib;ntdll.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)
+ $(OutDir);$(VC_LibraryPath_VC_x64_Desktop);%(Link.AdditionalLibraryDirectories)
@@ -161,6 +163,7 @@
+
diff --git a/tests/tests.vcxproj.filters b/tests/tests.vcxproj.filters
index 7689666..ba4eb82 100644
--- a/tests/tests.vcxproj.filters
+++ b/tests/tests.vcxproj.filters
@@ -42,6 +42,9 @@
Source Files
+
+ Source Files
+
diff --git a/tests/wdf_test.cpp b/tests/wdf_test.cpp
index 860d61d..c3c7f60 100644
--- a/tests/wdf_test.cpp
+++ b/tests/wdf_test.cpp
@@ -66,7 +66,8 @@ TEST_CASE("WdfDeviceCreate", "[wdf]")
NTSTATUS status = WdfDriverCreate(UsersimWdfDriverGlobals, &driver_object, ®istry_path, nullptr, &config, &driver);
REQUIRE(status == STATUS_SUCCESS);
- // Allocate control device object.
+ // Allocate control device object. For usage discussion, see
+ // https://learn.microsoft.com/en-us/windows-hardware/drivers/wdf/using-control-device-objects#creating-a-control-device-object
WdfControlDeviceInitAllocate_t* WdfControlDeviceInitAllocate =
(WdfControlDeviceInitAllocate_t*)UsersimWdfFunctions[WdfControlDeviceInitAllocateTableIndex];
DECLARE_CONST_UNICODE_STRING(security_descriptor, L"");
@@ -111,6 +112,11 @@ TEST_CASE("WdfDeviceCreate", "[wdf]")
REQUIRE(status == STATUS_SUCCESS);
REQUIRE(device != nullptr);
+ WdfDeviceWdmGetDeviceObject_t* WdfDeviceWdmGetDeviceObject =
+ (WdfDeviceWdmGetDeviceObject_t*)UsersimWdfFunctions[WdfDeviceWdmGetDeviceObjectTableIndex];
+ PDEVICE_OBJECT device_object = WdfDeviceWdmGetDeviceObject(UsersimWdfDriverGlobals, device);
+ REQUIRE(device_object != nullptr);
+
// Create symbolic link for control object for user mode.
UNICODE_STRING symbolic_device_name;
RtlInitUnicodeString(&symbolic_device_name, L"symbolic device name");
@@ -127,6 +133,11 @@ TEST_CASE("WdfDeviceCreate", "[wdf]")
UsersimWdfDriverGlobals, device, &io_queue_configuration, WDF_NO_OBJECT_ATTRIBUTES, WDF_NO_HANDLE);
REQUIRE(status == STATUS_SUCCESS);
+ // Finish initializing the control device object.
+ WdfControlFinishInitializing_t* WdfControlFinishInitializing =
+ (WdfControlFinishInitializing_t*)UsersimWdfFunctions[WdfControlFinishInitializingTableIndex];
+ WdfControlFinishInitializing(UsersimWdfDriverGlobals, device);
+
// Free device, which will free the control device object.
WdfObjectDelete_t* WdfObjectDelete = (WdfObjectDelete_t*)UsersimWdfFunctions[WdfObjectDeleteTableIndex];
WdfObjectDelete(UsersimWdfDriverGlobals, device);
diff --git a/tests/zw_test.cpp b/tests/zw_test.cpp
new file mode 100644
index 0000000..382de41
--- /dev/null
+++ b/tests/zw_test.cpp
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation
+// SPDX-License-Identifier: MIT
+
+#if !defined(CMAKE_NUGET)
+#include
+#else
+#include
+#endif
+#include "usersim/zw.h"
+
+static void
+_test_zwcreatekey(_In_ PCWSTR path)
+{
+ NTSTATUS status;
+ HANDLE key_handle = nullptr;
+ UNICODE_STRING object_name;
+ RtlInitUnicodeString(&object_name, path);
+ OBJECT_ATTRIBUTES object_attributes = {.Length = sizeof(OBJECT_ATTRIBUTES), .ObjectName = &object_name};
+ ULONG disposition;
+ status = ZwCreateKey(&key_handle, KEY_WRITE, &object_attributes, 0, nullptr, REG_OPTION_VOLATILE, &disposition);
+ REQUIRE(status == STATUS_SUCCESS);
+ REQUIRE((disposition == REG_CREATED_NEW_KEY || disposition == REG_OPENED_EXISTING_KEY));
+ REQUIRE(key_handle != nullptr);
+ REQUIRE(ZwClose(key_handle) == STATUS_SUCCESS);
+
+ // Try creating when already exists. We use KEY_QUERY_VALUE to make sure that read access
+ // still finds the same key we created above.
+ status =
+ ZwCreateKey(&key_handle, KEY_QUERY_VALUE, &object_attributes, 0, nullptr, REG_OPTION_VOLATILE, &disposition);
+ REQUIRE(status == STATUS_SUCCESS);
+ REQUIRE(disposition == REG_OPENED_EXISTING_KEY);
+ REQUIRE(ZwClose(key_handle) == STATUS_SUCCESS);
+
+ // Delete the key we created above.
+ status =
+ ZwCreateKey(&key_handle, DELETE, &object_attributes, 0, nullptr, REG_OPTION_VOLATILE, &disposition);
+ REQUIRE(status == STATUS_SUCCESS);
+ REQUIRE(disposition == REG_OPENED_EXISTING_KEY);
+ REQUIRE(ZwDeleteKey(key_handle) == STATUS_SUCCESS);
+ REQUIRE(ZwClose(key_handle) == STATUS_SUCCESS);
+}
+
+TEST_CASE("ZwCreateKey HKCU", "[zw]")
+{
+ // In kernel-mode code, HKCU is \Registry\User.
+ _test_zwcreatekey(L"\\Registry\\User\\Software\\Usersim\\Test");
+}
+
+TEST_CASE("ZwCreateKey HKLM", "[zw]")
+{
+ // In kernel-mode code, HKLM is \Registry\Machine.
+ // Usersim will internally use HKCU if the test is not run as admin.
+ _test_zwcreatekey(L"\\Registry\\Machine\\Software\\Usersim\\Test");
+}
\ No newline at end of file
diff --git a/usersim_dll_skeleton/dll_skeleton.c b/usersim_dll_skeleton/dll_skeleton.c
index b105795..5c819b9 100644
--- a/usersim_dll_skeleton/dll_skeleton.c
+++ b/usersim_dll_skeleton/dll_skeleton.c
@@ -40,6 +40,12 @@ static DRIVER_OBJECT _driver_object = {0};
if (driver->config.EvtDriverUnload != NULL) {
driver->config.EvtDriverUnload(driver);
}
+
+ // Free device, which will free the control device object.
+ WdfObjectDelete_t* WdfObjectDelete = (WdfObjectDelete_t*)UsersimWdfFunctions[WdfObjectDeleteTableIndex];
+ WdfObjectDelete(UsersimWdfDriverGlobals, driver->device);
+ driver->device = NULL;
+
WdfDriverGlobals->Driver = NULL;
}