From 97ec131c5008d4b33ee9e42abb7e47e7d8a61af7 Mon Sep 17 00:00:00 2001 From: Igor Pissolati Date: Thu, 24 Apr 2025 01:03:35 -0300 Subject: [PATCH] Improvements to NtQueryDirectoryFileEx --- src/common/utils/container.hpp | 4 +- src/common/utils/string.hpp | 16 ++++ src/common/utils/wildcard.hpp | 99 ++++++++++++++++++++++++ src/windows-emulator/syscalls.cpp | 10 ++- src/windows-emulator/syscalls/file.cpp | 81 +++++++++++++------ src/windows-emulator/windows_objects.hpp | 6 ++ 6 files changed, 190 insertions(+), 26 deletions(-) create mode 100644 src/common/utils/wildcard.hpp diff --git a/src/common/utils/container.hpp b/src/common/utils/container.hpp index e66a4dcf..1dee02e5 100644 --- a/src/common/utils/container.hpp +++ b/src/common/utils/container.hpp @@ -41,9 +41,7 @@ namespace utils bool operator()(const std::string_view lhs, const std::string_view rhs) const { - return std::ranges::equal(lhs, rhs, [](const char c1, const char c2) { - return string::char_to_lower(c1) == string::char_to_lower(c2); - }); + return string::equals_ignore_case(lhs, rhs); } }; diff --git a/src/common/utils/string.hpp b/src/common/utils/string.hpp index d52fb816..5f59bde6 100644 --- a/src/common/utils/string.hpp +++ b/src/common/utils/string.hpp @@ -149,4 +149,20 @@ namespace utils::string return data; } + + template + bool equals_ignore_case(const std::basic_string& lhs, + const std::basic_string& rhs) + { + return std::ranges::equal(lhs, rhs, + [](const auto c1, const auto c2) { return char_to_lower(c1) == char_to_lower(c2); }); + } + + template + bool equals_ignore_case(const std::basic_string_view& lhs, + const std::basic_string_view& rhs) + { + return std::ranges::equal(lhs, rhs, + [](const auto c1, const auto c2) { return char_to_lower(c1) == char_to_lower(c2); }); + } } diff --git a/src/common/utils/wildcard.hpp b/src/common/utils/wildcard.hpp new file mode 100644 index 00000000..0d40da85 --- /dev/null +++ b/src/common/utils/wildcard.hpp @@ -0,0 +1,99 @@ +#pragma once +#include + +namespace utils::wildcard +{ + inline bool is_wildcard(char16_t c) + { + return c == '*' || c == '?' || c == '>' || c == '<' || c == '\"'; + } + + inline bool has_wildcard(const std::u16string_view mask) + { + return std::ranges::any_of(mask, is_wildcard); + } + + inline bool match_filename(std::u16string_view name, std::u16string_view mask) + { + if (mask.empty() || mask == u"*" || mask == u"*.*") + { + return true; + } + + size_t name_pos = 0; + size_t mask_pos = 0; + + size_t star_mask_pos = std::u16string_view::npos; + size_t star_name_pos = 0; + + while (name_pos < name.size()) + { + if (mask_pos < mask.size()) + { + char16_t mask_char = mask[mask_pos]; + char16_t name_char = name[name_pos]; + + bool char_matches; + if (mask_char == u'?' || mask_char == u'>') + { + char_matches = true; + } + else if (mask_char == u'"') + { + char_matches = name_char == u'.'; + } + else + { + char_matches = string::char_to_lower(name_char) == string::char_to_lower(mask_char); + } + + // Advance if current characters match + if (char_matches) + { + name_pos++; + mask_pos++; + continue; + } + + // If this is a wildcard, skip all consecutive wildcards and save position for backtracking + if (mask[mask_pos] == u'*' || mask[mask_pos] == u'<') + { + mask_pos++; + while (mask_pos < mask.size() && (mask[mask_pos] == u'*' || mask[mask_pos] == u'<')) + { + mask_pos++; + } + + if (mask_pos == mask.size()) + { + // There is no need to continue because all that remained were star masks. + return true; + } + + star_mask_pos = mask_pos; + star_name_pos = name_pos; + continue; + } + } + + // The current characters didn't match... + // If we had a wildcard earlier, backtrack to it and try to match at the next position + if (star_mask_pos != std::u16string_view::npos) + { + mask_pos = star_mask_pos; + name_pos = ++star_name_pos; + continue; + } + + return false; + } + + // Skip any remaining wildcards in the mask + while (mask_pos < mask.size() && (mask[mask_pos] == u'*' || mask[mask_pos] == u'<')) + { + mask_pos++; + } + + return mask_pos == mask.size(); + } +} \ No newline at end of file diff --git a/src/windows-emulator/syscalls.cpp b/src/windows-emulator/syscalls.cpp index ce138b95..610361ca 100644 --- a/src/windows-emulator/syscalls.cpp +++ b/src/windows-emulator/syscalls.cpp @@ -55,7 +55,14 @@ namespace syscalls emulator_object>> io_status_block, uint64_t file_information, uint32_t length, uint32_t info_class, ULONG query_flags, - emulator_object>> /*file_name*/); + emulator_object>> file_name); + NTSTATUS handle_NtQueryDirectoryFile(const syscall_context& c, handle file_handle, handle event_handle, + emulator_pointer /*PIO_APC_ROUTINE*/ apc_routine, emulator_pointer apc_context, + emulator_object>> io_status_block, + uint64_t file_information, uint32_t length, uint32_t info_class, + BOOLEAN return_single_entry, + emulator_object>> file_name, + BOOLEAN restart_scan); NTSTATUS handle_NtQueryInformationFile(const syscall_context& c, handle file_handle, emulator_object>> io_status_block, uint64_t file_information, uint32_t length, uint32_t info_class); @@ -831,6 +838,7 @@ void syscall_dispatcher::add_handlers(std::map& ha add_handler(NtSetInformationKey); add_handler(NtUserGetKeyboardLayout); add_handler(NtQueryDirectoryFileEx); + add_handler(NtQueryDirectoryFile); add_handler(NtUserSystemParametersInfo); add_handler(NtGetContextThread); add_handler(NtYieldExecution); diff --git a/src/windows-emulator/syscalls/file.cpp b/src/windows-emulator/syscalls/file.cpp index fdddf8f0..e91d5221 100644 --- a/src/windows-emulator/syscalls/file.cpp +++ b/src/windows-emulator/syscalls/file.cpp @@ -5,6 +5,7 @@ #include #include +#include #include @@ -104,17 +105,27 @@ namespace syscalls } } - std::vector scan_directory(const std::filesystem::path& dir) + std::vector scan_directory(const std::filesystem::path& dir, const std::u16string_view file_mask) { - std::vector files{ - {"."}, - {".."}, - }; + std::vector files{}; + + if (file_mask.empty() || file_mask == u"*") + { + files.emplace_back(file_entry{.file_path = ".", .is_directory = true}); + files.emplace_back(file_entry{.file_path = "..", .is_directory = true}); + } for (const auto& file : std::filesystem::directory_iterator(dir)) { + if (!file_mask.empty() && !utils::wildcard::match_filename(file.path().filename().u16string(), file_mask)) + { + continue; + } + files.emplace_back(file_entry{ .file_path = file.path().filename(), + .file_size = file.file_size(), + .is_directory = file.is_directory(), }); } @@ -125,12 +136,13 @@ namespace syscalls NTSTATUS handle_file_enumeration(const syscall_context& c, const emulator_object>> io_status_block, const uint64_t file_information, const uint32_t length, const ULONG query_flags, - file* f) + const emulator_object>> file_mask, file* f) { if (!f->enumeration_state || query_flags & SL_RESTART_SCAN) { f->enumeration_state.emplace(file_enumeration_state{}); - f->enumeration_state->files = scan_directory(c.win_emu.file_sys.translate(f->name)); + f->enumeration_state->files = scan_directory(c.win_emu.file_sys.translate(f->name), + file_mask ? read_unicode_string(c.emu, file_mask) : u""); } auto& enum_state = *f->enumeration_state; @@ -140,13 +152,13 @@ namespace syscalls size_t current_index = enum_state.current_index; + if (current_index >= enum_state.files.size()) + { + return STATUS_NO_MORE_FILES; + } + do { - if (current_index >= enum_state.files.size()) - { - break; - } - const auto new_offset = align_up(current_offset, 8); const auto& current_file = enum_state.files[current_index]; const auto file_name = current_file.file_path.u16string(); @@ -180,7 +192,12 @@ namespace syscalls info.NextEntryOffset = 0; info.FileIndex = static_cast(current_index); info.FileAttributes = FILE_ATTRIBUTE_NORMAL; + if (current_file.is_directory) + { + info.FileAttributes |= FILE_ATTRIBUTE_DIRECTORY; + } info.FileNameLength = static_cast(file_name.size() * 2); + info.EndOfFile.QuadPart = current_file.file_size; object.set_address(file_information + new_offset); object.write(info); @@ -189,7 +206,7 @@ namespace syscalls ++current_index; current_offset = end_offset; - } while ((query_flags & SL_RETURN_SINGLE_ENTRY) == 0); + } while ((query_flags & SL_RETURN_SINGLE_ENTRY) == 0 && current_index < enum_state.files.size()); if ((query_flags & SL_NO_CURSOR_UPDATE) == 0) { @@ -200,7 +217,7 @@ namespace syscalls block.Information = current_offset; io_status_block.write(block); - return current_index < enum_state.files.size() ? STATUS_SUCCESS : STATUS_NO_MORE_FILES; + return current_index <= enum_state.files.size() ? STATUS_SUCCESS : STATUS_NO_MORE_FILES; } NTSTATUS handle_NtQueryDirectoryFileEx( @@ -208,7 +225,7 @@ namespace syscalls const emulator_pointer /*PIO_APC_ROUTINE*/ /*apc_routine*/, const emulator_pointer /*apc_context*/, const emulator_object>> io_status_block, const uint64_t file_information, const uint32_t length, const uint32_t info_class, const ULONG query_flags, - const emulator_object>> /*file_name*/) + const emulator_object>> file_name) { auto* f = c.proc.files.get(file_handle); if (!f || !f->is_directory()) @@ -219,19 +236,19 @@ namespace syscalls if (info_class == FileDirectoryInformation) { return handle_file_enumeration(c, io_status_block, file_information, length, - query_flags, f); + query_flags, file_name, f); } if (info_class == FileFullDirectoryInformation) { return handle_file_enumeration(c, io_status_block, file_information, length, - query_flags, f); + query_flags, file_name, f); } if (info_class == FileBothDirectoryInformation) { return handle_file_enumeration(c, io_status_block, file_information, length, - query_flags, f); + query_flags, file_name, f); } c.win_emu.log.error("Unsupported query directory file info class: %X\n", info_class); @@ -240,6 +257,24 @@ namespace syscalls return STATUS_NOT_SUPPORTED; } + NTSTATUS handle_NtQueryDirectoryFile(const syscall_context& c, const handle file_handle, const handle event_handle, + const emulator_pointer /*PIO_APC_ROUTINE*/ apc_routine, + const emulator_pointer apc_context, + const emulator_object>> io_status_block, + const uint64_t file_information, const uint32_t length, + const uint32_t info_class, const BOOLEAN return_single_entry, + const emulator_object>> file_name, + const BOOLEAN restart_scan) + { + ULONG query_flags = 0; + if (return_single_entry) + query_flags |= SL_RETURN_SINGLE_ENTRY; + if (restart_scan) + query_flags |= SL_RETURN_SINGLE_ENTRY; + return handle_NtQueryDirectoryFileEx(c, file_handle, event_handle, apc_routine, apc_context, io_status_block, + file_information, length, info_class, query_flags, file_name); + } + NTSTATUS handle_NtQueryInformationFile( const syscall_context& c, const handle file_handle, const emulator_object>> io_status_block, const uint64_t file_information, @@ -604,21 +639,24 @@ namespace syscalls printer.cancel(); - if (f.name.ends_with(u"\\") || create_options & FILE_DIRECTORY_FILE) + const windows_path path = f.name; + const bool is_directory = std::filesystem::is_directory(c.win_emu.file_sys.translate(path)); + + if (is_directory || create_options & FILE_DIRECTORY_FILE) { c.win_emu.log.print(color::dark_gray, "--> Opening folder: %s\n", u16_to_u8(f.name).c_str()); if (create_disposition & FILE_CREATE) { std::error_code ec{}; - create_directory(c.win_emu.file_sys.translate(f.name), ec); + create_directory(c.win_emu.file_sys.translate(path), ec); if (ec) { return STATUS_ACCESS_DENIED; } } - else if (!is_directory(c.win_emu.file_sys.translate(f.name))) + else if (!is_directory) { return STATUS_OBJECT_NAME_NOT_FOUND; } @@ -631,7 +669,6 @@ namespace syscalls c.win_emu.log.print(color::dark_gray, "--> Opening file: %s\n", u16_to_u8(f.name).c_str()); - const windows_path path = f.name; std::u16string mode = map_mode(desired_access, create_disposition); if (mode.empty() || path.is_relative()) diff --git a/src/windows-emulator/windows_objects.hpp b/src/windows-emulator/windows_objects.hpp index 57ea509f..e51143b1 100644 --- a/src/windows-emulator/windows_objects.hpp +++ b/src/windows-emulator/windows_objects.hpp @@ -94,15 +94,21 @@ struct mutant : ref_counted_object struct file_entry { std::filesystem::path file_path{}; + uint64_t file_size{}; + bool is_directory{}; void serialize(utils::buffer_serializer& buffer) const { buffer.write(this->file_path); + buffer.write(this->file_size); + buffer.write(this->is_directory); } void deserialize(utils::buffer_deserializer& buffer) { buffer.read(this->file_path); + buffer.read(this->file_size); + buffer.read(this->is_directory); } };