diff --git a/CMakeLists.txt b/CMakeLists.txt index cd9a557..3dcd971 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -251,6 +251,17 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/test/test_virtualalloc.c ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_virtualquery.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_virtualquery.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_virtualquery.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_virtualquery.c + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + add_custom_command( OUTPUT ${WIBO_TEST_BIN_DIR}/test_rtl.exe COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 @@ -299,6 +310,7 @@ if(BUILD_TESTING) ${WIBO_TEST_BIN_DIR}/test_overlapped_io.exe ${WIBO_TEST_BIN_DIR}/test_time.exe ${WIBO_TEST_BIN_DIR}/test_virtualalloc.exe + ${WIBO_TEST_BIN_DIR}/test_virtualquery.exe ${WIBO_TEST_BIN_DIR}/test_rtl.exe ${WIBO_TEST_BIN_DIR}/test_ntquery.exe ${WIBO_TEST_BIN_DIR}/test_sysdir.exe) @@ -361,6 +373,12 @@ if(BUILD_TESTING) WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} DEPENDS wibo.build_fixtures) + add_test(NAME wibo.test_virtualquery + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_virtualquery.exe) + set_tests_properties(wibo.test_virtualquery PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) + add_test(NAME wibo.test_rtl COMMAND $ ${WIBO_TEST_BIN_DIR}/test_rtl.exe) set_tests_properties(wibo.test_rtl PROPERTIES diff --git a/common.h b/common.h index 7ad03e1..b75fff1 100644 --- a/common.h +++ b/common.h @@ -78,6 +78,44 @@ using LPWSTR = uint16_t *; using LPCWSTR = const uint16_t *; using LPCWCH = const uint16_t *; using WCHAR = uint16_t; + +// Page protection constants +constexpr DWORD PAGE_NOACCESS = 0x01; +constexpr DWORD PAGE_READONLY = 0x02; +constexpr DWORD PAGE_READWRITE = 0x04; +constexpr DWORD PAGE_WRITECOPY = 0x08; +constexpr DWORD PAGE_EXECUTE = 0x10; +constexpr DWORD PAGE_EXECUTE_READ = 0x20; +constexpr DWORD PAGE_EXECUTE_READWRITE = 0x40; +constexpr DWORD PAGE_EXECUTE_WRITECOPY = 0x80; +constexpr DWORD PAGE_GUARD = 0x100; +constexpr DWORD PAGE_NOCACHE = 0x200; +constexpr DWORD PAGE_WRITECOMBINE = 0x400; + +// Allocation type and memory state constants +constexpr DWORD MEM_COMMIT = 0x00001000; +constexpr DWORD MEM_RESERVE = 0x00002000; +constexpr DWORD MEM_DECOMMIT = 0x00004000; +constexpr DWORD MEM_RELEASE = 0x00008000; +constexpr DWORD MEM_FREE = 0x00010000; +constexpr DWORD MEM_PRIVATE = 0x00020000; +constexpr DWORD MEM_MAPPED = 0x00040000; +constexpr DWORD MEM_RESET = 0x00080000; +constexpr DWORD MEM_TOP_DOWN = 0x00100000; +constexpr DWORD MEM_WRITE_WATCH = 0x00200000; +constexpr DWORD MEM_PHYSICAL = 0x00400000; +constexpr DWORD MEM_RESET_UNDO = 0x01000000; +constexpr DWORD MEM_LARGE_PAGES = 0x20000000; +constexpr DWORD MEM_COALESCE_PLACEHOLDERS = 0x00000001; +constexpr DWORD MEM_PRESERVE_PLACEHOLDER = 0x00000002; +constexpr DWORD MEM_IMAGE = 0x01000000; + +// File mapping access flags +constexpr DWORD FILE_MAP_COPY = 0x00000001; +constexpr DWORD FILE_MAP_WRITE = 0x00000002; +constexpr DWORD FILE_MAP_READ = 0x00000004; +constexpr DWORD FILE_MAP_EXECUTE = 0x00000020; +constexpr DWORD FILE_MAP_ALL_ACCESS = 0x000f001f; using LPCH = char *; using LPWCH = uint16_t *; using BOOL = int; @@ -241,6 +279,13 @@ struct Executable { bool loadPE(FILE *file, bool exec); bool resolveImports(); + struct SectionInfo { + uintptr_t base = 0; + size_t size = 0; + DWORD protect = PAGE_NOACCESS; + DWORD characteristics = 0; + }; + void *imageBase = nullptr; size_t imageSize = 0; void *entryPoint = nullptr; @@ -259,6 +304,7 @@ struct Executable { bool execMapped = false; bool importsResolved = false; bool importsResolving = false; + std::vector sections; bool findResource(const ResourceIdentifier &type, const ResourceIdentifier &name, std::optional language, ResourceLocation &out) const; @@ -296,6 +342,7 @@ struct ModuleInfo { ModuleInfo *registerProcessModule(std::unique_ptr executable, std::filesystem::path resolvedPath, std::string originalName); Executable *executableFromModule(HMODULE module); +ModuleInfo *moduleInfoFromAddress(void *addr); /** * HMODULE will be `nullptr` or `mainModule->imageBase` if it's the main module, diff --git a/dll/kernel32/memoryapi.cpp b/dll/kernel32/memoryapi.cpp index 3714753..7006eba 100644 --- a/dll/kernel32/memoryapi.cpp +++ b/dll/kernel32/memoryapi.cpp @@ -15,13 +15,13 @@ #include #include #include -#include #include #include namespace { constexpr size_t kVirtualAllocationGranularity = 64 * 1024; +constexpr uintptr_t kProcessAddressLimit = 0x80000000; struct MappingObject { int fd = -1; @@ -33,12 +33,18 @@ struct MappingObject { }; struct ViewInfo { - void *mapBase = nullptr; - size_t mapLength = 0; + uintptr_t viewBase = 0; + size_t viewLength = 0; + uintptr_t allocationBase = 0; + size_t allocationLength = 0; MappingObject *owner = nullptr; + DWORD protect = PAGE_NOACCESS; + DWORD allocationProtect = PAGE_NOACCESS; + DWORD type = MEM_PRIVATE; }; -std::unordered_map g_viewInfo; +std::map g_viewInfo; +std::mutex g_viewInfoMutex; void closeMappingIfPossible(MappingObject *mapping) { if (!mapping) { @@ -157,6 +163,69 @@ bool rangeWithinRegion(const VirtualAllocation ®ion, uintptr_t start, size_t return (start + length) <= regionEnd(region); } +DWORD desiredAccessToProtect(DWORD desiredAccess, DWORD mappingProtect) { + DWORD access = desiredAccess; + if ((access & FILE_MAP_ALL_ACCESS) == FILE_MAP_ALL_ACCESS) { + access |= FILE_MAP_READ | FILE_MAP_WRITE; + } + bool wantExecute = (access & FILE_MAP_EXECUTE) != 0; + bool wantWrite = (access & FILE_MAP_WRITE) != 0; + bool wantCopy = (access & FILE_MAP_COPY) != 0; + bool wantRead = (access & (FILE_MAP_READ | FILE_MAP_WRITE | FILE_MAP_COPY)) != 0; + if (wantCopy) { + wantWrite = true; + } + const bool supportsWrite = + mappingProtect == PAGE_READWRITE || mappingProtect == PAGE_EXECUTE_READWRITE || mappingProtect == PAGE_WRITECOPY || + mappingProtect == PAGE_EXECUTE_WRITECOPY; + const bool supportsCopy = mappingProtect == PAGE_WRITECOPY || mappingProtect == PAGE_EXECUTE_WRITECOPY; + + if (wantCopy && !supportsCopy) { + wantCopy = false; + } + if (wantWrite && !supportsWrite) { + if (supportsCopy) { + wantCopy = true; + wantWrite = false; + } else { + wantWrite = false; + } + } + if (!wantRead && (mappingProtect == PAGE_READONLY || mappingProtect == PAGE_EXECUTE_READ || + mappingProtect == PAGE_WRITECOPY || mappingProtect == PAGE_EXECUTE_WRITECOPY)) { + wantRead = true; + } + + DWORD protect = PAGE_NOACCESS; + if (wantCopy && supportsCopy) { + protect = wantExecute ? PAGE_EXECUTE_WRITECOPY : PAGE_WRITECOPY; + } else if (wantExecute) { + if (wantWrite) { + protect = PAGE_EXECUTE_READWRITE; + } else if (wantRead) { + protect = PAGE_EXECUTE_READ; + } else { + protect = PAGE_EXECUTE; + } + } else { + if (wantWrite) { + protect = PAGE_READWRITE; + } else if (wantRead) { + protect = PAGE_READONLY; + } + } + if ((mappingProtect & PAGE_NOCACHE) != 0) { + protect |= PAGE_NOCACHE; + } + if ((mappingProtect & PAGE_GUARD) != 0) { + protect |= PAGE_GUARD; + } + if ((mappingProtect & PAGE_WRITECOMBINE) != 0) { + protect |= PAGE_WRITECOMBINE; + } + return protect; +} + void markCommitted(VirtualAllocation ®ion, uintptr_t start, size_t length, DWORD protect) { if (length == 0) { return; @@ -181,6 +250,174 @@ void markDecommitted(VirtualAllocation ®ion, uintptr_t start, size_t length) } } +bool moduleRegionForAddress(uintptr_t pageBase, MEMORY_BASIC_INFORMATION &info) { + if (pageBase == 0) { + return false; + } + wibo::ModuleInfo *module = wibo::moduleInfoFromAddress(reinterpret_cast(pageBase)); + if (!module || !module->executable) { + return false; + } + const auto §ions = module->executable->sections; + if (sections.empty()) { + return false; + } + size_t matchIndex = sections.size(); + for (size_t i = 0; i < sections.size(); ++i) { + const auto §ion = sections[i]; + if (pageBase >= section.base && pageBase < section.base + section.size) { + matchIndex = i; + break; + } + } + if (matchIndex == sections.size()) { + return false; + } + uintptr_t blockStart = sections[matchIndex].base; + uintptr_t blockEnd = sections[matchIndex].base + sections[matchIndex].size; + DWORD blockProtect = sections[matchIndex].protect; + for (size_t prev = matchIndex; prev > 0; ) { + --prev; + const auto §ion = sections[prev]; + if (section.base + section.size != blockStart) { + break; + } + if (section.protect != blockProtect) { + break; + } + blockStart = section.base; + } + for (size_t next = matchIndex + 1; next < sections.size(); ++next) { + const auto §ion = sections[next]; + if (section.base != blockEnd) { + break; + } + if (section.protect != blockProtect) { + break; + } + blockEnd = section.base + section.size; + } + info.BaseAddress = reinterpret_cast(blockStart); + info.AllocationBase = module->executable->imageBase; + info.AllocationProtect = blockProtect; + info.RegionSize = blockEnd > blockStart ? blockEnd - blockStart : 0; + info.State = MEM_COMMIT; + info.Protect = blockProtect; + info.Type = MEM_IMAGE; + return true; +} + +bool mappedViewRegionForAddress(uintptr_t request, uintptr_t pageBase, MEMORY_BASIC_INFORMATION &info) { + std::lock_guard guard(g_viewInfoMutex); + if (g_viewInfo.empty()) { + return false; + } + const size_t pageSize = systemPageSize(); + for (const auto &entry : g_viewInfo) { + const ViewInfo &view = entry.second; + if (view.viewLength == 0) { + continue; + } + uintptr_t allocationStart = view.allocationBase; + uintptr_t allocationEnd = allocationStart + view.allocationLength; + if (pageBase < allocationStart || pageBase >= allocationEnd) { + continue; + } + uintptr_t viewStart = view.viewBase; + uintptr_t viewEnd = view.viewBase + view.viewLength; + if (request != 0 && (request < viewStart || request >= viewEnd)) { + continue; + } + uintptr_t blockStart = viewStart; + uintptr_t blockEnd = alignUp(viewEnd, pageSize); + info.BaseAddress = reinterpret_cast(blockStart); + info.AllocationBase = reinterpret_cast(view.viewBase); + info.AllocationProtect = view.allocationProtect; + info.RegionSize = blockEnd > blockStart ? blockEnd - blockStart : 0; + info.State = MEM_COMMIT; + info.Protect = view.protect; + info.Type = view.type; + return true; + } + return false; +} + +bool virtualAllocationRegionForAddress(uintptr_t pageBase, MEMORY_BASIC_INFORMATION &info) { + const size_t pageSize = systemPageSize(); + std::unique_lock lock(g_virtualAllocMutex); + VirtualAllocation *region = lookupRegion(pageBase); + if (!region) { + uintptr_t regionStart = pageBase; + uintptr_t regionEnd = regionStart; + auto next = g_virtualAllocations.lower_bound(pageBase); + if (next != g_virtualAllocations.end()) { + regionEnd = next->second.base; + } else { + regionEnd = kProcessAddressLimit; + } + if (regionEnd <= regionStart) { + regionEnd = regionStart + pageSize; + } + lock.unlock(); + info.BaseAddress = reinterpret_cast(regionStart); + info.AllocationBase = nullptr; + info.AllocationProtect = 0; + info.RegionSize = regionEnd - regionStart; + info.State = MEM_FREE; + info.Protect = PAGE_NOACCESS; + info.Type = 0; + return true; + } + const uintptr_t regionLimit = region->base + region->size; + const size_t pageIndex = (pageBase - region->base) / pageSize; + if (pageIndex >= region->pageProtect.size()) { + return false; + } + const DWORD pageProtect = region->pageProtect[pageIndex]; + const bool committed = pageProtect != 0; + uintptr_t blockStart = pageBase; + uintptr_t blockEnd = pageBase + pageSize; + while (blockStart > region->base) { + size_t idx = (blockStart - region->base) / pageSize - 1; + DWORD protect = region->pageProtect[idx]; + bool pageCommitted = protect != 0; + if (pageCommitted != committed) { + break; + } + if (committed && protect != pageProtect) { + break; + } + blockStart -= pageSize; + } + while (blockEnd < regionLimit) { + size_t idx = (blockEnd - region->base) / pageSize; + if (idx >= region->pageProtect.size()) { + break; + } + DWORD protect = region->pageProtect[idx]; + bool pageCommitted = protect != 0; + if (pageCommitted != committed) { + break; + } + if (committed && protect != pageProtect) { + break; + } + blockEnd += pageSize; + } + uintptr_t allocationBase = region->base; + DWORD allocationProtect = region->allocationProtect != 0 ? region->allocationProtect : PAGE_NOACCESS; + DWORD finalProtect = committed ? pageProtect : PAGE_NOACCESS; + lock.unlock(); + info.BaseAddress = reinterpret_cast(blockStart); + info.AllocationBase = reinterpret_cast(allocationBase); + info.AllocationProtect = allocationProtect; + info.RegionSize = blockEnd - blockStart; + info.State = committed ? MEM_COMMIT : MEM_RESERVE; + info.Protect = finalProtect; + info.Type = MEM_PRIVATE; + return true; +} + void *alignedReserve(size_t length, int prot, int flags) { const size_t granularity = kVirtualAllocationGranularity; const size_t request = length + granularity; @@ -335,11 +572,14 @@ static LPVOID mapViewOfFileInternal(MappingObject *mapping, DWORD dwDesiredAcces return nullptr; } - int prot = PROT_READ; bool wantWrite = (dwDesiredAccess & FILE_MAP_WRITE) != 0; bool wantExecute = (dwDesiredAccess & FILE_MAP_EXECUTE) != 0; bool wantCopy = (dwDesiredAccess & FILE_MAP_COPY) != 0; - + bool wantAllAccess = (dwDesiredAccess & FILE_MAP_ALL_ACCESS) == FILE_MAP_ALL_ACCESS; + if (wantAllAccess) { + wantWrite = true; + } + int prot = PROT_READ; if (mapping->protect == PAGE_READWRITE) { if (wantWrite || wantCopy) { prot |= PROT_WRITE; @@ -358,7 +598,7 @@ static LPVOID mapViewOfFileInternal(MappingObject *mapping, DWORD dwDesiredAcces } int flags = (mapping->anonymous ? MAP_ANONYMOUS : 0) | (wantCopy ? MAP_PRIVATE : MAP_SHARED); - const size_t pageSize = static_cast(sysconf(_SC_PAGESIZE)); + const size_t pageSize = systemPageSize(); off_t alignedOffset = mapping->anonymous ? 0 : static_cast(offset & ~static_cast(pageSize - 1)); size_t offsetDelta = static_cast(offset - static_cast(alignedOffset)); uint64_t requestedLength = length + offsetDelta; @@ -415,7 +655,24 @@ static LPVOID mapViewOfFileInternal(MappingObject *mapping, DWORD dwDesiredAcces wibo::lastError = ERROR_INVALID_ADDRESS; return nullptr; } - g_viewInfo[viewPtr] = ViewInfo{mapBase, mapLength, mapping}; + uintptr_t viewLength = static_cast(length); + uintptr_t alignedViewLength = alignUp(viewLength, pageSize); + if (alignedViewLength == std::numeric_limits::max()) { + alignedViewLength = viewLength; + } + ViewInfo view{}; + view.viewBase = reinterpret_cast(viewPtr); + view.viewLength = static_cast(alignedViewLength); + view.allocationBase = reinterpret_cast(mapBase); + view.allocationLength = mapLength; + view.owner = mapping; + view.protect = desiredAccessToProtect(dwDesiredAccess, mapping->protect); + view.allocationProtect = mapping->protect; + view.type = MEM_MAPPED; + { + std::lock_guard guard(g_viewInfoMutex); + g_viewInfo[view.viewBase] = view; + } mapping->refCount++; wibo::lastError = ERROR_SUCCESS; return viewPtr; @@ -453,15 +710,18 @@ LPVOID WIN_FUNC MapViewOfFileEx(HANDLE hFileMappingObject, DWORD dwDesiredAccess BOOL WIN_FUNC UnmapViewOfFile(LPCVOID lpBaseAddress) { DEBUG_LOG("UnmapViewOfFile(%p)\n", lpBaseAddress); - auto it = g_viewInfo.find(const_cast(lpBaseAddress)); + std::unique_lock lock(g_viewInfoMutex); + auto it = g_viewInfo.find(reinterpret_cast(lpBaseAddress)); if (it == g_viewInfo.end()) { + lock.unlock(); wibo::lastError = ERROR_INVALID_PARAMETER; return FALSE; } ViewInfo info = it->second; g_viewInfo.erase(it); - if (info.mapBase && info.mapLength) { - munmap(info.mapBase, info.mapLength); + lock.unlock(); + if (info.allocationLength != 0) { + munmap(reinterpret_cast(info.allocationBase), info.allocationLength); } if (info.owner && info.owner->refCount > 0) { info.owner->refCount--; @@ -823,7 +1083,7 @@ BOOL WIN_FUNC VirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect SIZE_T WIN_FUNC VirtualQuery(LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength) { DEBUG_LOG("VirtualQuery(%p, %p, %zu)\n", lpAddress, lpBuffer, dwLength); - if (!lpBuffer || dwLength < sizeof(MEMORY_BASIC_INFORMATION) || !lpAddress) { + if (!lpBuffer || dwLength < sizeof(MEMORY_BASIC_INFORMATION)) { wibo::lastError = ERROR_INVALID_PARAMETER; DEBUG_LOG("-> ERROR_INVALID_PARAMETER\n"); return 0; @@ -831,53 +1091,34 @@ SIZE_T WIN_FUNC VirtualQuery(LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuff std::memset(lpBuffer, 0, sizeof(MEMORY_BASIC_INFORMATION)); const size_t pageSize = systemPageSize(); - uintptr_t request = reinterpret_cast(lpAddress); + uintptr_t request = lpAddress ? reinterpret_cast(lpAddress) : 0; uintptr_t pageBase = alignDown(request, pageSize); - - std::unique_lock lock(g_virtualAllocMutex); - VirtualAllocation *region = lookupRegion(pageBase); - if (!region) { - wibo::lastError = ERROR_INVALID_ADDRESS; - DEBUG_LOG("-> ERROR_INVALID_ADDRESS\n"); + if (pageBase >= kProcessAddressLimit) { + wibo::lastError = ERROR_INVALID_PARAMETER; + DEBUG_LOG("-> ERROR_INVALID_PARAMETER (beyond address space)\n"); return 0; } - const size_t pageIndex = (pageBase - region->base) / pageSize; - if (pageIndex >= region->pageProtect.size()) { - wibo::lastError = ERROR_INVALID_ADDRESS; - DEBUG_LOG("-> ERROR_INVALID_ADDRESS\n"); - return 0; + MEMORY_BASIC_INFORMATION info{}; + if (moduleRegionForAddress(pageBase, info)) { + *lpBuffer = info; + wibo::lastError = ERROR_SUCCESS; + return sizeof(MEMORY_BASIC_INFORMATION); } - const bool committed = region->pageProtect[pageIndex] != 0; - uintptr_t blockStart = pageBase; - uintptr_t blockEnd = pageBase + pageSize; - while (blockStart > region->base) { - size_t idx = (blockStart - region->base) / pageSize - 1; - bool pageCommitted = region->pageProtect[idx] != 0; - if (pageCommitted != committed) { - break; - } - blockStart -= pageSize; + if (mappedViewRegionForAddress(request, pageBase, info)) { + *lpBuffer = info; + wibo::lastError = ERROR_SUCCESS; + return sizeof(MEMORY_BASIC_INFORMATION); } - while (blockEnd < region->base + region->size) { - size_t idx = (blockEnd - region->base) / pageSize; - bool pageCommitted = region->pageProtect[idx] != 0; - if (pageCommitted != committed) { - break; - } - blockEnd += pageSize; + if (virtualAllocationRegionForAddress(pageBase, info)) { + *lpBuffer = info; + wibo::lastError = ERROR_SUCCESS; + return sizeof(MEMORY_BASIC_INFORMATION); } - lpBuffer->BaseAddress = reinterpret_cast(blockStart); - lpBuffer->AllocationBase = reinterpret_cast(region->base); - lpBuffer->AllocationProtect = region->allocationProtect != 0 ? region->allocationProtect : PAGE_NOACCESS; - lpBuffer->RegionSize = blockEnd - blockStart; - lpBuffer->State = committed ? MEM_COMMIT : MEM_RESERVE; - lpBuffer->Protect = committed ? region->pageProtect[pageIndex] : 0; - lpBuffer->Type = MEM_PRIVATE; - lock.unlock(); - wibo::lastError = ERROR_SUCCESS; - return sizeof(MEMORY_BASIC_INFORMATION); + wibo::lastError = ERROR_INVALID_ADDRESS; + DEBUG_LOG("-> ERROR_INVALID_ADDRESS\n"); + return 0; } BOOL WIN_FUNC GetProcessWorkingSetSize(HANDLE hProcess, PSIZE_T lpMinimumWorkingSetSize, diff --git a/dll/kernel32/memoryapi.h b/dll/kernel32/memoryapi.h index 25e33cf..ffceb2e 100644 --- a/dll/kernel32/memoryapi.h +++ b/dll/kernel32/memoryapi.h @@ -3,36 +3,6 @@ #include "common.h" #include "minwinbase.h" -// Allocation type flags -constexpr DWORD MEM_COMMIT = 0x00001000; -constexpr DWORD MEM_RESERVE = 0x00002000; -constexpr DWORD MEM_DECOMMIT = 0x00004000; -constexpr DWORD MEM_RELEASE = 0x00008000; -constexpr DWORD MEM_RESET = 0x00080000; -constexpr DWORD MEM_RESET_UNDO = 0x01000000; -constexpr DWORD MEM_TOP_DOWN = 0x00100000; -constexpr DWORD MEM_WRITE_WATCH = 0x00200000; -constexpr DWORD MEM_PHYSICAL = 0x00400000; -constexpr DWORD MEM_PRIVATE = 0x00020000; -constexpr DWORD MEM_LARGE_PAGES = 0x20000000; -constexpr DWORD MEM_COALESCE_PLACEHOLDERS = 0x00000001; -constexpr DWORD MEM_PRESERVE_PLACEHOLDER = 0x00000002; - -// Page protection constants -constexpr DWORD PAGE_NOACCESS = 0x01; -constexpr DWORD PAGE_READONLY = 0x02; -constexpr DWORD PAGE_READWRITE = 0x04; -constexpr DWORD PAGE_WRITECOPY = 0x08; -constexpr DWORD PAGE_EXECUTE = 0x10; -constexpr DWORD PAGE_EXECUTE_READ = 0x20; -constexpr DWORD PAGE_EXECUTE_READWRITE = 0x40; -constexpr DWORD PAGE_EXECUTE_WRITECOPY = 0x80; - -constexpr DWORD FILE_MAP_COPY = 0x00000001; -constexpr DWORD FILE_MAP_WRITE = 0x00000002; -constexpr DWORD FILE_MAP_READ = 0x00000004; -constexpr DWORD FILE_MAP_EXECUTE = 0x00000020; - struct MEMORY_BASIC_INFORMATION { PVOID BaseAddress; PVOID AllocationBase; diff --git a/dll/ntdll.cpp b/dll/ntdll.cpp index 5644e60..f72de92 100644 --- a/dll/ntdll.cpp +++ b/dll/ntdll.cpp @@ -178,18 +178,6 @@ NTSTATUS WIN_FUNC NtReadFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE Ap return status; } -#define PAGE_NOACCESS 0x1 -#define PAGE_READONLY 0x2 -#define PAGE_READWRITE 0x4 -#define PAGE_WRITECOPY 0x8 -#define PAGE_EXECUTE 0x10 -#define PAGE_EXECUTE_READ 0x20 -#define PAGE_EXECUTE_READWRITE 0x40 -#define PAGE_EXECUTE_WRITECOPY 0x80 -#define PAGE_GUARD 0x100 -#define PAGE_NOCACHE 0x200 -#define PAGE_WRITECOMBINE 0x400 - NTSTATUS WIN_FUNC NtAllocateVirtualMemory(HANDLE ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect) { DEBUG_LOG("NtAllocateVirtualMemory(%p, %p, %lu, %p, %lu, %lu) ", ProcessHandle, BaseAddress, ZeroBits, RegionSize, diff --git a/loader.cpp b/loader.cpp index 2c3e2c4..cd2890b 100644 --- a/loader.cpp +++ b/loader.cpp @@ -114,6 +114,61 @@ struct PEBaseRelocationBlock { constexpr uint16_t IMAGE_REL_BASED_ABSOLUTE = 0; constexpr uint16_t IMAGE_REL_BASED_HIGHLOW = 3; +constexpr uint32_t IMAGE_SCN_MEM_EXECUTE = 0x20000000; +constexpr uint32_t IMAGE_SCN_MEM_READ = 0x40000000; +constexpr uint32_t IMAGE_SCN_MEM_WRITE = 0x80000000; +constexpr uint32_t IMAGE_SCN_MEM_NOT_CACHED = 0x04000000; + +static uintptr_t alignDown(uintptr_t value, size_t alignment) { + if (alignment == 0) { + return value; + } + return value - (value % alignment); +} + +static uintptr_t alignUp(uintptr_t value, size_t alignment) { + if (alignment == 0) { + return value; + } + const uintptr_t remainder = value % alignment; + if (remainder == 0) { + return value; + } + if (value > std::numeric_limits::max() - (alignment - remainder)) { + return std::numeric_limits::max(); + } + return value + (alignment - remainder); +} + +static DWORD sectionProtectFromCharacteristics(uint32_t characteristics) { + const bool executable = (characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; + bool readable = (characteristics & IMAGE_SCN_MEM_READ) != 0; + const bool writable = (characteristics & IMAGE_SCN_MEM_WRITE) != 0; + if (!readable && !writable && !executable) { + readable = true; + } + DWORD protect = PAGE_NOACCESS; + if (executable) { + if (writable) { + protect = PAGE_EXECUTE_READWRITE; + } else if (readable) { + protect = PAGE_EXECUTE_READ; + } else { + protect = PAGE_EXECUTE; + } + } else { + if (writable) { + protect = PAGE_READWRITE; + } else if (readable) { + protect = PAGE_READONLY; + } + } + if ((characteristics & IMAGE_SCN_MEM_NOT_CACHED) != 0) { + protect |= PAGE_NOCACHE; + } + return protect; +} + uint16_t read16(FILE *file) { uint16_t v = 0; fread(&v, 2, 1, file); @@ -164,6 +219,7 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { long pageSize = sysconf(_SC_PAGE_SIZE); DEBUG_LOG("Page size: %x\n", (unsigned int)pageSize); + const size_t pageSizeValue = pageSize > 0 ? static_cast(pageSize) : static_cast(4096); preferredImageBase = header32.imageBase; exportDirectoryRVA = header32.exportTable.virtualAddress; @@ -195,6 +251,16 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { } relocationDelta = (intptr_t)((uintptr_t)imageBase - (uintptr_t)header32.imageBase); memset(imageBase, 0, header32.sizeOfImage); + sections.clear(); + uintptr_t imageBaseAddr = reinterpret_cast(imageBase); + uintptr_t headerSpan = alignUp(static_cast(header32.sizeOfHeaders), pageSizeValue); + if (headerSpan != 0) { + Executable::SectionInfo headerInfo{}; + headerInfo.base = imageBaseAddr; + headerInfo.size = static_cast(headerSpan); + headerInfo.protect = PAGE_READONLY; + sections.push_back(headerInfo); + } // Read the sections fseek(file, offsetToPE + sizeof header + header.sizeOfOptionalHeader, SEEK_SET); @@ -222,7 +288,26 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { rsrcBase = sectionBase; rsrcSize = std::max(section.virtualSize, section.sizeOfRawData); } + + size_t sectionSpan = std::max(section.virtualSize, section.sizeOfRawData); + if (sectionSpan != 0) { + uintptr_t sectionStart = alignDown(imageBaseAddr + static_cast(section.virtualAddress), pageSizeValue); + uintptr_t sectionEnd = alignUp(imageBaseAddr + static_cast(section.virtualAddress) + + static_cast(sectionSpan), + pageSizeValue); + if (sectionEnd > sectionStart) { + Executable::SectionInfo sectionInfo{}; + sectionInfo.base = sectionStart; + sectionInfo.size = static_cast(sectionEnd - sectionStart); + sectionInfo.protect = sectionProtectFromCharacteristics(section.characteristics); + sectionInfo.characteristics = section.characteristics; + sections.push_back(sectionInfo); + } + } } + std::sort(sections.begin(), sections.end(), [](const SectionInfo &lhs, const SectionInfo &rhs) { + return lhs.base < rhs.base; + }); if (exec && relocationDelta != 0) { if (relocationDirectoryRVA == 0 || relocationDirectorySize == 0) { diff --git a/module_registry.cpp b/module_registry.cpp index cd318c2..38f7df4 100644 --- a/module_registry.cpp +++ b/module_registry.cpp @@ -711,6 +711,14 @@ std::optional dllDirectoryOverride() { return reg->dllDirectory; } +ModuleInfo *moduleInfoFromAddress(void *addr) { + if (!addr) { + return nullptr; + } + auto reg = registry(); + return moduleFromAddress(*reg, addr); +} + void registerOnExitTable(void *table) { if (!table) return; diff --git a/test/test_virtualquery.c b/test/test_virtualquery.c new file mode 100644 index 0000000..7488afd --- /dev/null +++ b/test/test_virtualquery.c @@ -0,0 +1,104 @@ +#include + +#include +#include + +#include "test_assert.h" + +static SIZE_T query_page_size(void) { + SYSTEM_INFO info; + GetSystemInfo(&info); + return info.dwPageSize; +} + +static void test_null_address(void) { + MEMORY_BASIC_INFORMATION mbi; + SIZE_T got = VirtualQuery(NULL, &mbi, sizeof(mbi)); + TEST_CHECK_EQ(sizeof(mbi), got); + TEST_CHECK(mbi.BaseAddress == (PVOID)0); + TEST_CHECK(mbi.AllocationBase == NULL); + TEST_CHECK_EQ(0u, mbi.AllocationProtect); + TEST_CHECK_EQ(MEM_FREE, mbi.State); + TEST_CHECK_EQ(PAGE_NOACCESS, mbi.Protect); + TEST_CHECK_EQ(0u, mbi.Type); + TEST_CHECK(mbi.RegionSize >= query_page_size()); +} + +static void test_module_region(void) { + MEMORY_BASIC_INFORMATION mbi; + void *address = (void *)&test_module_region; + SIZE_T got = VirtualQuery(address, &mbi, sizeof(mbi)); + TEST_CHECK_EQ(sizeof(mbi), got); + TEST_CHECK_EQ(MEM_COMMIT, mbi.State); + TEST_CHECK_EQ(MEM_IMAGE, mbi.Type); + TEST_CHECK(mbi.RegionSize >= query_page_size()); + HMODULE module = GetModuleHandleA(NULL); + TEST_CHECK(module != NULL); + TEST_CHECK((HMODULE)mbi.AllocationBase == module); + TEST_CHECK(mbi.AllocationProtect != 0); + TEST_CHECK((mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) != 0); + TEST_CHECK((uintptr_t)address >= (uintptr_t)mbi.BaseAddress); + TEST_CHECK((uintptr_t)address < (uintptr_t)mbi.BaseAddress + mbi.RegionSize); +} + +static void test_anonymous_mapping(void) { + const SIZE_T page = query_page_size(); + HANDLE mapping = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, (DWORD)page, NULL); + TEST_CHECK(mapping != NULL); + uint8_t *view = (uint8_t *)MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, page); + TEST_CHECK(view != NULL); + + MEMORY_BASIC_INFORMATION mbi; + SIZE_T got = VirtualQuery(view, &mbi, sizeof(mbi)); + TEST_CHECK_EQ(sizeof(mbi), got); + TEST_CHECK_EQ(MEM_COMMIT, mbi.State); + TEST_CHECK_EQ(MEM_MAPPED, mbi.Type); + TEST_CHECK_EQ(PAGE_READWRITE, mbi.AllocationProtect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)); + TEST_CHECK_EQ(PAGE_READWRITE, mbi.Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)); + TEST_CHECK(mbi.RegionSize >= page); + TEST_CHECK((uint8_t *)mbi.BaseAddress == view); + TEST_CHECK((uint8_t *)mbi.AllocationBase == view); + + TEST_CHECK(UnmapViewOfFile(view)); + TEST_CHECK(CloseHandle(mapping)); +} + +static void test_file_mapping(void) { + const SIZE_T page = query_page_size(); + HANDLE file = CreateFileA("test_virtualquery.tmp", GENERIC_READ | GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); + TEST_CHECK(file != INVALID_HANDLE_VALUE); + + DWORD newPos = SetFilePointer(file, (LONG)page, NULL, FILE_BEGIN); + TEST_CHECK(newPos != INVALID_SET_FILE_POINTER); + TEST_CHECK(SetEndOfFile(file)); + TEST_CHECK(SetFilePointer(file, 0, NULL, FILE_BEGIN) != INVALID_SET_FILE_POINTER); + + HANDLE mapping = CreateFileMappingA(file, NULL, PAGE_READONLY, 0, 0, NULL); + TEST_CHECK(mapping != NULL); + const uint8_t *view = (const uint8_t *)MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0); + TEST_CHECK(view != NULL); + + MEMORY_BASIC_INFORMATION mbi; + SIZE_T got = VirtualQuery(view, &mbi, sizeof(mbi)); + TEST_CHECK_EQ(sizeof(mbi), got); + TEST_CHECK_EQ(MEM_COMMIT, mbi.State); + TEST_CHECK_EQ(MEM_MAPPED, mbi.Type); + TEST_CHECK_EQ(PAGE_READONLY, mbi.AllocationProtect & (PAGE_READONLY | PAGE_EXECUTE_READ)); + TEST_CHECK_EQ(PAGE_READONLY, mbi.Protect & (PAGE_READONLY | PAGE_EXECUTE_READ)); + TEST_CHECK(mbi.RegionSize >= page); + TEST_CHECK((const uint8_t *)mbi.BaseAddress == view); + TEST_CHECK((const uint8_t *)mbi.AllocationBase == view); + + TEST_CHECK(UnmapViewOfFile(view)); + TEST_CHECK(CloseHandle(mapping)); + TEST_CHECK(CloseHandle(file)); +} + +int main(void) { + test_null_address(); + test_module_region(); + test_anonymous_mapping(); + test_file_mapping(); + return 0; +}