From 27860f0300b142c87ac7705106df315eade1170c Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 27 Oct 2025 14:56:51 -0600 Subject: [PATCH] Implement ntdll NtWriteFile --- CMakeLists.txt | 1 + dll/kernel32/fileapi.cpp | 2 +- dll/ntdll.cpp | 88 +++++++++++++++++++++++++++++++ test/test_ntwritefile.c | 108 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 test/test_ntwritefile.c diff --git a/CMakeLists.txt b/CMakeLists.txt index c7029be..6a1a916 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -278,6 +278,7 @@ if (WIBO_ENABLE_FIXTURE_TESTS) wibo_add_fixture_bin(NAME test_rtl SOURCES test/test_rtl.c) wibo_add_fixture_bin(NAME test_ntquery SOURCES test/test_ntquery.c) wibo_add_fixture_bin(NAME test_ntreadfile SOURCES test/test_ntreadfile.c) + wibo_add_fixture_bin(NAME test_ntwritefile SOURCES test/test_ntwritefile.c) wibo_add_fixture_bin(NAME test_pipe_io SOURCES test/test_pipe_io.c) wibo_add_fixture_bin(NAME test_namedpipe SOURCES test/test_namedpipe.c) wibo_add_fixture_bin(NAME test_sysdir SOURCES test/test_sysdir.c) diff --git a/dll/kernel32/fileapi.cpp b/dll/kernel32/fileapi.cpp index fec9161..ef59b02 100644 --- a/dll/kernel32/fileapi.cpp +++ b/dll/kernel32/fileapi.cpp @@ -1504,7 +1504,7 @@ BOOL WIN_FUNC GetFileInformationByHandle(HANDLE hFile, LPBY_HANDLE_FILE_INFORMAT lpFileInformation->dwVolumeSerialNumber = 0; lpFileInformation->nFileSizeHigh = static_cast(static_cast(st.st_size) >> 32); lpFileInformation->nFileSizeLow = static_cast(st.st_size & 0xFFFFFFFFULL); - lpFileInformation->nNumberOfLinks = 0; + lpFileInformation->nNumberOfLinks = 1; lpFileInformation->nFileIndexHigh = 0; lpFileInformation->nFileIndexLow = 0; return TRUE; diff --git a/dll/ntdll.cpp b/dll/ntdll.cpp index 7e45e2e..3fd79bc 100644 --- a/dll/ntdll.cpp +++ b/dll/ntdll.cpp @@ -9,7 +9,9 @@ #include "processes.h" #include "strutil.h" +#include #include +#include #include #include @@ -199,6 +201,90 @@ NTSTATUS WIN_FUNC NtReadFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE Ap return status; } +NTSTATUS WIN_FUNC NtWriteFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, + PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, + PULONG Key) { + HOST_CONTEXT_GUARD(); + DEBUG_LOG("NtWriteFile(%p, %p, %p, %p, %p, %p, %u, %p, %p) ", FileHandle, Event, ApcRoutine, ApcContext, + IoStatusBlock, Buffer, Length, ByteOffset, Key); + (void)ApcRoutine; + (void)ApcContext; + (void)Key; + + if (!IoStatusBlock) { + return STATUS_INVALID_PARAMETER; + } + IoStatusBlock->Information = 0; + + auto file = wibo::handles().getAs(FileHandle); + if (!file || !file->valid()) { + IoStatusBlock->Status = STATUS_INVALID_HANDLE; + return STATUS_INVALID_HANDLE; + } + + bool useOverlapped = file->overlapped; + bool useCurrentFilePosition = (ByteOffset == nullptr); + bool writeToEndOfFile = false; + if (ByteOffset) { + if (*ByteOffset == FILE_USE_FILE_POINTER_POSITION) { + useCurrentFilePosition = true; + } else if (*ByteOffset == FILE_WRITE_TO_END_OF_FILE) { + writeToEndOfFile = true; + } + } + + std::optional offset; + if (!useCurrentFilePosition && !writeToEndOfFile) { + offset = static_cast(*ByteOffset); + } + + if (useOverlapped && useCurrentFilePosition) { + IoStatusBlock->Status = STATUS_INVALID_PARAMETER; + return STATUS_INVALID_PARAMETER; + } + + Pin ev; + if (Event) { + ev = wibo::handles().getAs(Event); + if (!ev) { + IoStatusBlock->Status = STATUS_INVALID_HANDLE; + return STATUS_INVALID_HANDLE; + } + ev->reset(); + } + + bool updateFilePointer = file->isPipe ? true : !useOverlapped; + + if (writeToEndOfFile && !offset.has_value()) { + if (!file->isPipe) { + struct stat st{}; + if (fstat(file->fd, &st) != 0) { + int err = errno ? errno : EIO; + NTSTATUS status = wibo::statusFromErrno(err); + IoStatusBlock->Status = status; + return status; + } + offset = static_cast(st.st_size); + } + } + + auto io = files::write(file.get(), Buffer, static_cast(Length), offset, updateFilePointer); + NTSTATUS status = STATUS_SUCCESS; + if (io.unixError != 0) { + status = wibo::statusFromErrno(io.unixError); + } + + IoStatusBlock->Status = status; + IoStatusBlock->Information = static_cast(io.bytesTransferred); + + if (ev && status == STATUS_SUCCESS) { + ev->set(); + } + + DEBUG_LOG("-> 0x%x\n", status); + return status; +} + NTSTATUS WIN_FUNC NtAllocateVirtualMemory(HANDLE ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect) { HOST_CONTEXT_GUARD(); @@ -417,6 +503,8 @@ NTSTATUS WIN_FUNC NtQueryInformationProcess(HANDLE ProcessHandle, PROCESSINFOCLA static void *resolveByName(const char *name) { if (strcmp(name, "NtReadFile") == 0) return (void *)ntdll::NtReadFile; + if (strcmp(name, "NtWriteFile") == 0) + return (void *)ntdll::NtWriteFile; if (strcmp(name, "NtAllocateVirtualMemory") == 0) return (void *)ntdll::NtAllocateVirtualMemory; if (strcmp(name, "NtProtectVirtualMemory") == 0) diff --git a/test/test_ntwritefile.c b/test/test_ntwritefile.c new file mode 100644 index 0000000..8018f82 --- /dev/null +++ b/test/test_ntwritefile.c @@ -0,0 +1,108 @@ +#include "test_assert.h" + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#endif + +static const char *kTempFileName = "ntwritefile_fixture.tmp"; + +typedef NTSTATUS(NTAPI *NtWriteFile_t)(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, PIO_STATUS_BLOCK, PVOID, ULONG, + PLARGE_INTEGER, PULONG); + +static NtWriteFile_t load_ntwritefile(void) { + HMODULE mod = GetModuleHandleW(L"ntdll.dll"); + if (!mod) { + mod = LoadLibraryW(L"ntdll.dll"); + } + TEST_CHECK(mod != NULL); + FARPROC proc = GetProcAddress(mod, "NtWriteFile"); + TEST_CHECK(proc != NULL); + NtWriteFile_t fn = NULL; + TEST_CHECK(sizeof(fn) == sizeof(proc)); + memcpy(&fn, &proc, sizeof(fn)); + return fn; +} + +static void read_back(HANDLE file, char *buffer, DWORD expectedLength) { + TEST_CHECK(SetFilePointer(file, 0, NULL, FILE_BEGIN) != INVALID_SET_FILE_POINTER); + DWORD read = 0; + memset(buffer, 0, expectedLength + 1); + TEST_CHECK(ReadFile(file, buffer, expectedLength, &read, NULL)); + TEST_CHECK_EQ(expectedLength, read); +} + +int main(void) { + NtWriteFile_t fn = load_ntwritefile(); + + DeleteFileA(kTempFileName); + HANDLE file = + CreateFileA(kTempFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + TEST_CHECK(file != INVALID_HANDLE_VALUE); + + HANDLE event = CreateEventA(NULL, TRUE, TRUE, NULL); + TEST_CHECK(event != NULL); + + IO_STATUS_BLOCK iosb; + memset(&iosb, 0, sizeof(iosb)); + + char payload[] = "hello"; + SetLastError(ERROR_GEN_FAILURE); + DWORD before = GetLastError(); + NTSTATUS status = fn(file, event, NULL, NULL, &iosb, payload, (ULONG)(sizeof(payload) - 1), NULL, NULL); + TEST_CHECK_EQ((NTSTATUS)STATUS_SUCCESS, status); + TEST_CHECK_EQ(status, iosb.Status); + TEST_CHECK_EQ((ULONG_PTR)(sizeof(payload) - 1), iosb.Information); + TEST_CHECK_EQ(before, GetLastError()); + TEST_CHECK_EQ(WAIT_OBJECT_0, WaitForSingleObject(event, 0)); + + char buffer[16]; + read_back(file, buffer, 5); + TEST_CHECK(memcmp(buffer, "hello", 5) == 0); + TEST_CHECK(ResetEvent(event)); + + LARGE_INTEGER useCurrent; + useCurrent.QuadPart = -2; // FILE_USE_FILE_POINTER_POSITION + TEST_CHECK(SetFilePointer(file, 1, NULL, FILE_BEGIN) != INVALID_SET_FILE_POINTER); + IO_STATUS_BLOCK overwriteIosb; + memset(&overwriteIosb, 0, sizeof(overwriteIosb)); + char middle[] = "abc"; + SetLastError(ERROR_GEN_FAILURE); + before = GetLastError(); + status = fn(file, event, NULL, NULL, &overwriteIosb, middle, (ULONG)(sizeof(middle) - 1), &useCurrent, NULL); + TEST_CHECK_EQ((NTSTATUS)STATUS_SUCCESS, status); + TEST_CHECK_EQ(status, overwriteIosb.Status); + TEST_CHECK_EQ((ULONG_PTR)(sizeof(middle) - 1), overwriteIosb.Information); + TEST_CHECK_EQ(before, GetLastError()); + TEST_CHECK_EQ(WAIT_OBJECT_0, WaitForSingleObject(event, 0)); + + read_back(file, buffer, 5); + TEST_CHECK(memcmp(buffer, "habco", 5) == 0); + TEST_CHECK(ResetEvent(event)); + + LARGE_INTEGER appendPos; + appendPos.QuadPart = -1; // FILE_WRITE_TO_END_OF_FILE + IO_STATUS_BLOCK appendIosb; + memset(&appendIosb, 0, sizeof(appendIosb)); + char tail[] = "!"; + SetLastError(ERROR_GEN_FAILURE); + before = GetLastError(); + status = fn(file, event, NULL, NULL, &appendIosb, tail, (ULONG)(sizeof(tail) - 1), &appendPos, NULL); + TEST_CHECK_EQ((NTSTATUS)STATUS_SUCCESS, status); + TEST_CHECK_EQ(status, appendIosb.Status); + TEST_CHECK_EQ((ULONG_PTR)(sizeof(tail) - 1), appendIosb.Information); + TEST_CHECK_EQ(before, GetLastError()); + TEST_CHECK_EQ(WAIT_OBJECT_0, WaitForSingleObject(event, 0)); + + read_back(file, buffer, 6); + TEST_CHECK(memcmp(buffer, "habco!", 6) == 0); + + TEST_CHECK(CloseHandle(event)); + TEST_CHECK(CloseHandle(file)); + TEST_CHECK(DeleteFileA(kTempFileName)); + return 0; +}