diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e6943f..c76351e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,8 @@ set(CMAKE_CXX_FLAGS_FUZZERDEBUG "${CMAKE_CXX_FLAGS_DEBUG} /fsanitize-coverage=in # list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/external/FindWDK/cmake") # find_package(WDK REQUIRED) +set(NUGET_PACKAGE_VERSION "10.0.26100.4204") + set(NUGET_PACKAGES "Microsoft.Windows.SDK.CPP" "Microsoft.Windows.SDK.CPP.arm64" @@ -39,7 +41,7 @@ if(NOT NUGET) message("ERROR: You must first install nuget.exe from https://www.nuget.org/downloads") else() foreach(PACKAGE ${NUGET_PACKAGES}) - execute_process(COMMAND ${NUGET} install ${PACKAGE} -ExcludeVersion -OutputDirectory ${PROJECT_BINARY_DIR}/packages) + execute_process(COMMAND ${NUGET} install ${PACKAGE} -Version ${NUGET_PACKAGE_VERSION} -ExcludeVersion -OutputDirectory ${PROJECT_BINARY_DIR}/packages) endforeach() endif() diff --git a/inc/usersim/ps.h b/inc/usersim/ps.h index 8469042..99067df 100644 --- a/inc/usersim/ps.h +++ b/inc/usersim/ps.h @@ -104,4 +104,18 @@ void usersime_invoke_process_creation_notify_routine( _Inout_ PEPROCESS process, _In_ HANDLE process_id, _Inout_opt_ PPS_CREATE_NOTIFY_INFO create_info); +typedef PACCESS_TOKEN PEPROCESS_ACCESS_TOKEN; + +USERSIM_API +PACCESS_TOKEN +PsReferencePrimaryToken(_In_ PEPROCESS process); + +USERSIM_API +VOID +PsDereferencePrimaryToken(_In_ PACCESS_TOKEN token); + +USERSIM_API +void +usersim_clean_up_ps(); + CXPLAT_EXTERN_C_END diff --git a/inc/usersim/rtl.h b/inc/usersim/rtl.h index 00a689b..7c6ff03 100644 --- a/inc/usersim/rtl.h +++ b/inc/usersim/rtl.h @@ -34,6 +34,11 @@ USERSIM_API BOOLEAN RtlValidSid(_In_ PSID sid); +USERSIM_API +NTSTATUS +RtlCopySid( + _In_ ULONG DestinationSidLength, _Out_writes_bytes_(DestinationSidLength) PSID DestinationSid, _In_ PSID SourceSid); + USERSIM_API NTSTATUS NTAPI diff --git a/inc/usersim/se.h b/inc/usersim/se.h index 5a88506..048591a 100644 --- a/inc/usersim/se.h +++ b/inc/usersim/se.h @@ -5,6 +5,7 @@ #include "..\src\platform.h" #include "ke.h" +#include "rtl.h" CXPLAT_EXTERN_C_BEGIN @@ -180,6 +181,21 @@ USERSIM_API NTSTATUS SeQueryAuthenticationIdToken(_In_ PACCESS_TOKEN token, _Out_ PLUID authentication_id); +USERSIM_API +NTSTATUS +SeQueryInformationToken( + _In_ PACCESS_TOKEN token, _In_ TOKEN_INFORMATION_CLASS token_information_class, _Out_ PVOID* token_information); + +USERSIM_API +NTSTATUS +SecLookupAccountSid( + _In_ PSID Sid, + _Out_ PULONG NameSize, + _Inout_ PUNICODE_STRING NameBuffer, + _Out_ PULONG DomainSize, + _Out_opt_ PUNICODE_STRING DomainBuffer, + _Out_ PSID_NAME_USE NameUse); + void usersim_initialize_se(); diff --git a/src/platform_user.cpp b/src/platform_user.cpp index 3fd0944..85e44df 100644 --- a/src/platform_user.cpp +++ b/src/platform_user.cpp @@ -7,6 +7,7 @@ #include "usersim/ex.h" #include "usersim/ke.h" #include "usersim/mm.h" +#include "usersim/ps.h" #include "usersim/se.h" #include "usersim/wdf.h" #include "utilities.h" @@ -104,6 +105,7 @@ usersim_platform_terminate() usersim_free_semaphores(); usersim_free_threadpool_timers(); + usersim_clean_up_ps(); usersim_clean_up_dpcs(); usersim_clean_up_irql(); if (_cxplat_initialized) { diff --git a/src/ps.cpp b/src/ps.cpp index bb636bd..89c9148 100644 --- a/src/ps.cpp +++ b/src/ps.cpp @@ -5,8 +5,12 @@ #include "platform.h" #include "usersim/ps.h" +#include + // Ps* functions. +static ULONG _primary_token_ref_count = 0; + HANDLE PsGetCurrentProcessId() { return (HANDLE)(uintptr_t)GetCurrentProcessId(); } @@ -212,4 +216,28 @@ usersime_invoke_process_creation_notify_routine( if (_usersim_process_creation_notify_routine != NULL) { _usersim_process_creation_notify_routine(process, process_id, create_info); } -} \ No newline at end of file +} + +PACCESS_TOKEN +PsReferencePrimaryToken(_In_ PEPROCESS process) +{ + UNREFERENCED_PARAMETER(process); + _primary_token_ref_count++; + // In user mode, return the current process token handle as a pseudo-token. + return (PACCESS_TOKEN)GetCurrentProcessToken(); +} + +VOID +PsDereferencePrimaryToken(_In_ PACCESS_TOKEN token) +{ + UNREFERENCED_PARAMETER(token); + assert(_primary_token_ref_count > 0); + _primary_token_ref_count--; +} + +void +usersim_clean_up_ps() +{ + assert(_primary_token_ref_count == 0); + _primary_token_ref_count = 0; +} diff --git a/src/rtl.cpp b/src/rtl.cpp index 6f2c852..5ac1d75 100644 --- a/src/rtl.cpp +++ b/src/rtl.cpp @@ -59,6 +59,19 @@ RtlValidSid(_In_ PSID sid) return (sid != nullptr); } +NTSTATUS +RtlCopySid( + _In_ ULONG DestinationSidLength, _Out_writes_bytes_(DestinationSidLength) PSID DestinationSid, _In_ PSID SourceSid) +{ + ULONG source_sid_length = RtlLengthSid(SourceSid); + if (DestinationSidLength < source_sid_length) { + return STATUS_BUFFER_TOO_SMALL; + } + + memcpy(DestinationSid, SourceSid, source_sid_length); + return STATUS_SUCCESS; +} + NTSTATUS RtlAddAccessAllowedAce(_Inout_ PACL Acl, _In_ unsigned long AceRevision, _In_ ACCESS_MASK AccessMask, _In_ PSID Sid) { diff --git a/src/se.cpp b/src/se.cpp index 9de85ba..bd08fd3 100644 --- a/src/se.cpp +++ b/src/se.cpp @@ -250,3 +250,151 @@ SeQueryAuthenticationIdToken(_In_ PACCESS_TOKEN token, _Out_ PLUID authenticatio usersim_convert_sid_to_luid(((PTOKEN_OWNER)token_owner_buffer)->Owner, authentication_id); return STATUS_SUCCESS; } + +NTSTATUS +SeQueryInformationToken( + _In_ PACCESS_TOKEN token, _In_ TOKEN_INFORMATION_CLASS token_information_class, _Out_ PVOID* token_information) +{ + *token_information = nullptr; + + if (cxplat_fault_injection_inject_fault()) { + return STATUS_UNSUCCESSFUL; + } + + HANDLE token_handle = (HANDLE)token; + DWORD needed = 0; + + // Get required buffer size. + (void)GetTokenInformation(token_handle, token_information_class, nullptr, 0, &needed); + DWORD error = GetLastError(); + if (error != ERROR_INSUFFICIENT_BUFFER || needed == 0) { + return win32_error_to_usersim_error(error); + } + + void* buffer = ExAllocatePoolUninitialized(NonPagedPoolNx, needed, USERSIM_TAG_TOKEN_ACCESS_INFORMATION); + if (buffer == nullptr) { + return STATUS_NO_MEMORY; + } + + if (!GetTokenInformation(token_handle, token_information_class, buffer, needed, &needed)) { + ExFreePool(buffer); + return win32_error_to_usersim_error(GetLastError()); + } + + *token_information = buffer; + return STATUS_SUCCESS; +} + +NTSTATUS +SecLookupAccountSid( + _In_ PSID Sid, + _Out_ PULONG NameSize, + _Inout_ PUNICODE_STRING NameBuffer, + _Out_ PULONG DomainSize, + _Out_opt_ PUNICODE_STRING DomainBuffer, + _Out_ PSID_NAME_USE NameUse) +{ + if (cxplat_fault_injection_inject_fault()) { + return STATUS_UNSUCCESSFUL; + } + + if (Sid == nullptr || NameSize == nullptr || DomainSize == nullptr || NameUse == nullptr) { + return STATUS_INVALID_PARAMETER; + } + + // Use LookupAccountSidW to resolve the SID in user mode. + DWORD name_chars = 0; + DWORD domain_chars = 0; + SID_NAME_USE name_use; + + // First call to get required buffer sizes (in characters including null terminator). + (void)LookupAccountSidW(nullptr, Sid, nullptr, &name_chars, nullptr, &domain_chars, &name_use); + DWORD error = GetLastError(); + if (error != ERROR_INSUFFICIENT_BUFFER) { + return win32_error_to_usersim_error(error); + } + + // If caller just wants sizes (NameBuffer and DomainBuffer are NULL), return sizes in bytes. + if (NameBuffer == nullptr && DomainBuffer == nullptr) { + // Return sizes as byte counts (without null terminator) to match kernel SecLookupAccountSid behavior. + *NameSize = (name_chars > 0) ? (ULONG)((name_chars - 1) * sizeof(WCHAR)) : 0; + *DomainSize = (domain_chars > 0) ? (ULONG)((domain_chars - 1) * sizeof(WCHAR)) : 0; + *NameUse = name_use; + return STATUS_BUFFER_TOO_SMALL; + } + + // Allocate temporary buffers for the LookupAccountSidW call. + WCHAR* name_buf = nullptr; + WCHAR* domain_buf = nullptr; + + if (name_chars > 0) { + name_buf = + (WCHAR*)ExAllocatePoolUninitialized(NonPagedPoolNx, name_chars * sizeof(WCHAR), USERSIM_TAG_ACCOUNT_NAME); + if (name_buf == nullptr) { + return STATUS_NO_MEMORY; + } + } + if (domain_chars > 0) { + domain_buf = + (WCHAR*)ExAllocatePoolUninitialized(NonPagedPoolNx, domain_chars * sizeof(WCHAR), USERSIM_TAG_ACCOUNT_NAME); + if (domain_buf == nullptr) { + ExFreePool(name_buf); + return STATUS_NO_MEMORY; + } + } + + if (!LookupAccountSidW(nullptr, Sid, name_buf, &name_chars, domain_buf, &domain_chars, &name_use)) { + if (name_buf != nullptr) { + ExFreePool(name_buf); + } + if (domain_buf != nullptr) { + ExFreePool(domain_buf); + } + return win32_error_to_usersim_error(GetLastError()); + } + + // Copy results into caller-provided UNICODE_STRING buffers. + // *NameSize and *DomainSize are output-only: + // - On success: set to the actual number of bytes copied. + // - On truncation (buffer too small): set to the required buffer size. + BOOLEAN truncated = FALSE; + ULONG required_name_bytes = (name_chars > 0) ? (ULONG)(name_chars * sizeof(WCHAR)) : 0; + ULONG required_domain_bytes = (domain_chars > 0) ? (ULONG)(domain_chars * sizeof(WCHAR)) : 0; + + if (NameBuffer != nullptr && NameBuffer->Buffer != nullptr && name_chars > 0) { + USHORT byte_len = (USHORT)required_name_bytes; + if (byte_len > NameBuffer->MaximumLength) { + byte_len = NameBuffer->MaximumLength; + truncated = TRUE; + } + memcpy(NameBuffer->Buffer, name_buf, byte_len); + NameBuffer->Length = byte_len; + *NameSize = required_name_bytes; + } else { + *NameSize = 0; + } + + if (DomainBuffer != nullptr && DomainBuffer->Buffer != nullptr && domain_chars > 0) { + USHORT byte_len = (USHORT)required_domain_bytes; + if (byte_len > DomainBuffer->MaximumLength) { + byte_len = DomainBuffer->MaximumLength; + truncated = TRUE; + } + memcpy(DomainBuffer->Buffer, domain_buf, byte_len); + DomainBuffer->Length = byte_len; + *DomainSize = required_domain_bytes; + } else { + *DomainSize = 0; + } + + *NameUse = name_use; + + if (name_buf != nullptr) { + ExFreePool(name_buf); + } + if (domain_buf != nullptr) { + ExFreePool(domain_buf); + } + + return truncated ? STATUS_BUFFER_TOO_SMALL : STATUS_SUCCESS; +} diff --git a/src/tags.h b/src/tags.h index fbc261a..b1a0f46 100644 --- a/src/tags.h +++ b/src/tags.h @@ -3,6 +3,7 @@ #pragma once // Pool tags used by the usersim library. +#define USERSIM_TAG_ACCOUNT_NAME 'ansu' #define USERSIM_TAG_ETW_PROVIDER 'pesu' #define USERSIM_TAG_FWPS_CONNECT_REQUEST0 'cfsu' #define USERSIM_TAG_HANDLE 'ahsu' diff --git a/tests/ps_test.cpp b/tests/ps_test.cpp index d60cdda..403ca86 100644 --- a/tests/ps_test.cpp +++ b/tests/ps_test.cpp @@ -139,3 +139,13 @@ TEST_CASE("PsGetProcessStartKey", "[ps]") key = PsGetProcessStartKey(nullptr); REQUIRE(key == 0); } + +TEST_CASE("PsReferencePrimaryToken", "[ps]") +{ + // PsReferencePrimaryToken should return a non-null pseudo-token. + PACCESS_TOKEN token = PsReferencePrimaryToken(nullptr); + REQUIRE(token != nullptr); + + // PsDereferencePrimaryToken is a no-op but should not crash. + PsDereferencePrimaryToken(token); +} diff --git a/tests/rtl_test.cpp b/tests/rtl_test.cpp index b4efd8a..6cd3441 100644 --- a/tests/rtl_test.cpp +++ b/tests/rtl_test.cpp @@ -438,4 +438,30 @@ TEST_CASE("RtlNumberGenericTableElementsAvl", "[rtl]") for (int i = 0; i < 100; ++i) { REQUIRE(RtlDeleteElementGenericTableAvl(&table, &i)); } +} + +TEST_CASE("RtlCopySid", "[rtl]") +{ + // Build a SID with 1 sub-authority (S-1-5-18, i.e. Local System). + SID source_sid = {0}; + source_sid.Revision = SID_REVISION; + source_sid.SubAuthorityCount = 1; + source_sid.IdentifierAuthority = SECURITY_NT_AUTHORITY; + source_sid.SubAuthority[0] = SECURITY_LOCAL_SYSTEM_RID; + + ULONG sid_length = RtlLengthSid(&source_sid); + REQUIRE(sid_length > 0); + + // Successful copy. + BYTE destination_buffer[SECURITY_MAX_SID_SIZE] = {0}; + REQUIRE(NT_SUCCESS(RtlCopySid(sizeof(destination_buffer), (PSID)destination_buffer, &source_sid))); + REQUIRE(memcmp(destination_buffer, &source_sid, sid_length) == 0); + + // Buffer too small should fail. + REQUIRE(RtlCopySid(sid_length - 1, (PSID)destination_buffer, &source_sid) == STATUS_BUFFER_TOO_SMALL); + + // Exact size should succeed. + memset(destination_buffer, 0, sizeof(destination_buffer)); + REQUIRE(NT_SUCCESS(RtlCopySid(sid_length, (PSID)destination_buffer, &source_sid))); + REQUIRE(memcmp(destination_buffer, &source_sid, sid_length) == 0); } \ No newline at end of file diff --git a/tests/se_test.cpp b/tests/se_test.cpp index 6130f8d..168da77 100644 --- a/tests/se_test.cpp +++ b/tests/se_test.cpp @@ -6,10 +6,33 @@ #else #include #endif +#include "usersim/ex.h" +#include "usersim/ps.h" #include "usersim/rtl.h" #include "usersim/se.h" -static void _verify_sid(_In_ PSID sid) +#include + +struct _ex_pool_free_functor +{ + void + operator()(void* buffer) + { + ExFreePool(buffer); + } +}; + +struct _ps_deref_token_functor +{ + void + operator()(void* token) + { + PsDereferencePrimaryToken((PACCESS_TOKEN)token); + } +}; + +static void +_verify_sid(_In_ PSID sid) { REQUIRE(RtlValidSid(sid)); REQUIRE(RtlLengthSid(sid) > 0); @@ -120,3 +143,133 @@ TEST_CASE("SeAccessCheck", "[se]") REQUIRE(granted_access == STANDARD_RIGHTS_READ); REQUIRE(access_status == STATUS_SUCCESS); } + +TEST_CASE("SeQueryInformationToken", "[se]") +{ + // Get a token handle for the current process. + PACCESS_TOKEN token = PsReferencePrimaryToken(nullptr); + REQUIRE(token != nullptr); + std::unique_ptr token_guard(token); + + // Query TokenUser information. + PVOID token_info = nullptr; + NTSTATUS status = SeQueryInformationToken(token, TokenUser, &token_info); + REQUIRE(status == STATUS_SUCCESS); + REQUIRE(token_info != nullptr); + std::unique_ptr token_info_guard(token_info); + + // Validate the returned SID. + PTOKEN_USER token_user = (PTOKEN_USER)token_info; + REQUIRE(RtlValidSid(token_user->User.Sid)); + REQUIRE(RtlLengthSid(token_user->User.Sid) > 0); +} + +TEST_CASE("SecLookupAccountSid", "[se]") +{ + // Get the current process token's user SID. + PACCESS_TOKEN token = PsReferencePrimaryToken(nullptr); + REQUIRE(token != nullptr); + std::unique_ptr token_guard(token); + + PVOID token_info = nullptr; + NTSTATUS status = SeQueryInformationToken(token, TokenUser, &token_info); + REQUIRE(status == STATUS_SUCCESS); + std::unique_ptr token_info_guard(token_info); + + PTOKEN_USER token_user = (PTOKEN_USER)token_info; + PSID sid = token_user->User.Sid; + + // First call: sizing query with NULL name/domain buffers. + ULONG name_size = 0; + ULONG domain_size = 0; + SID_NAME_USE name_use; + status = SecLookupAccountSid(sid, &name_size, NULL, &domain_size, NULL, &name_use); + REQUIRE(status == STATUS_BUFFER_TOO_SMALL); + REQUIRE(name_size > 0); + REQUIRE(domain_size > 0); + + // Allocate UNICODE_STRING buffers for name and domain. + UNICODE_STRING name_str = {0}; + name_str.Buffer = (PWSTR)ExAllocatePoolUninitialized(NonPagedPoolNx, name_size, 'tset'); + REQUIRE(name_str.Buffer != nullptr); + std::unique_ptr name_buf_guard(name_str.Buffer); + name_str.MaximumLength = (USHORT)name_size; + name_str.Length = 0; + + UNICODE_STRING domain_str = {0}; + domain_str.Buffer = (PWSTR)ExAllocatePoolUninitialized(NonPagedPoolNx, domain_size, 'tset'); + REQUIRE(domain_str.Buffer != nullptr); + std::unique_ptr domain_buf_guard(domain_str.Buffer); + domain_str.MaximumLength = (USHORT)domain_size; + domain_str.Length = 0; + + // Second call: actual lookup. + status = SecLookupAccountSid(sid, &name_size, &name_str, &domain_size, &domain_str, &name_use); + REQUIRE(status == STATUS_SUCCESS); + REQUIRE(name_str.Length > 0); + REQUIRE(domain_str.Length > 0); +} + +TEST_CASE("SecLookupAccountSid_truncation_via_MaximumLength", "[se]") +{ + // Get the current process token's user SID. + PACCESS_TOKEN token = PsReferencePrimaryToken(nullptr); + REQUIRE(token != nullptr); + std::unique_ptr token_guard(token); + + PVOID token_info = nullptr; + NTSTATUS status = SeQueryInformationToken(token, TokenUser, &token_info); + REQUIRE(status == STATUS_SUCCESS); + std::unique_ptr token_info_guard(token_info); + + PTOKEN_USER token_user = (PTOKEN_USER)token_info; + PSID sid = token_user->User.Sid; + + // Get required sizes. + ULONG required_name_size = 0; + ULONG required_domain_size = 0; + SID_NAME_USE name_use; + status = SecLookupAccountSid(sid, &required_name_size, NULL, &required_domain_size, NULL, &name_use); + REQUIRE(status == STATUS_BUFFER_TOO_SMALL); + REQUIRE(required_name_size > 0); + REQUIRE(required_domain_size > 0); + + // Allocate buffers with a MaximumLength smaller than required to trigger truncation. + USHORT small_max = sizeof(WCHAR); + UNICODE_STRING name_str = {0}; + name_str.Buffer = (PWSTR)ExAllocatePoolUninitialized(NonPagedPoolNx, required_name_size, 'tset'); + REQUIRE(name_str.Buffer != nullptr); + std::unique_ptr name_buf_guard(name_str.Buffer); + name_str.MaximumLength = small_max; + name_str.Length = 0; + + UNICODE_STRING domain_str = {0}; + domain_str.Buffer = (PWSTR)ExAllocatePoolUninitialized(NonPagedPoolNx, required_domain_size, 'tset'); + REQUIRE(domain_str.Buffer != nullptr); + std::unique_ptr domain_buf_guard(domain_str.Buffer); + domain_str.MaximumLength = (USHORT)required_domain_size; + domain_str.Length = 0; + + ULONG name_size = 0; + ULONG domain_size = 0; + status = SecLookupAccountSid(sid, &name_size, &name_str, &domain_size, &domain_str, &name_use); + REQUIRE(status == STATUS_BUFFER_TOO_SMALL); + // On failure, NameSize should report the required size. + REQUIRE(name_size == required_name_size); + // Name should have been truncated to MaximumLength. + REQUIRE(name_str.Length == small_max); + // Domain should be fully populated since its MaximumLength was sufficient. + REQUIRE(domain_str.Length > 0); + REQUIRE(domain_size == domain_str.Length); +} + +TEST_CASE("SecLookupAccountSid_invalid_params", "[se]") +{ + ULONG name_size = 0; + ULONG domain_size = 0; + SID_NAME_USE name_use; + + // NULL SID should return STATUS_INVALID_PARAMETER. + NTSTATUS status = SecLookupAccountSid(nullptr, &name_size, NULL, &domain_size, NULL, &name_use); + REQUIRE(status == STATUS_INVALID_PARAMETER); +}