From 87ffb31b83adaca810ecccf6d978bdf44cd4ce3a Mon Sep 17 00:00:00 2001 From: donnaskiez Date: Sun, 4 Aug 2024 15:15:37 +1000 Subject: [PATCH] completely refactor pool.c --- README.md | 12 +- driver/common.h | 2 - driver/containers/map.c | 6 + driver/containers/map.h | 6 +- driver/driver.c | 2 + driver/hv.c | 16 +- driver/pool.c | 1030 +++++++++------------------------------ driver/pool.h | 14 +- 8 files changed, 269 insertions(+), 819 deletions(-) diff --git a/README.md b/README.md index 348c4b5..a6a1549 100644 --- a/README.md +++ b/README.md @@ -7,20 +7,18 @@ open source anti cheat (lol) which I made for fun. - Attached thread detection - Process module .text section integrity checks - NMI stackwalking via isr iretq -- APC stackwalking via RtlCaptureStackBackTrace -- DPC stackwalking via RtlCaptureStackBackTrace +- APC, DPC stackwalking - Return address exception hooking detection - Chained .data pointer detection (iffy) - Handle stripping via obj callbacks - Process handle table enumeration - System module device object verification - System module .text integrity checks -- Unlinked process detection -- Removed thread PspCidTable entry detection -- Dispatch routine validation -- Extraction of hardware identifiers +- Removal of threads cid table entry detection +- Driver dispatch routine validation +- Extraction of various hardware identifiers - EPT hook detection -- Driver integrity checks both locally and over server +- Various image integrity checks both of driver + module - Hypervisor detection - HalDispatch and HalPrivateDispatch routine validation - Dynamic import resolving & encryption diff --git a/driver/common.h b/driver/common.h index 682c8bd..ce5b1cf 100644 --- a/driver/common.h +++ b/driver/common.h @@ -414,8 +414,6 @@ typedef struct _ACTIVE_SESSION { * Some nice macros courtesy of: * https://www.unknowncheats.me/forum/general-programming-and-reversing/523359-introduction-physical-memory.html */ -#define IS_LARGE_PAGE(x) ((BOOLEAN)((x >> 7) & 1)) -#define IS_PAGE_PRESENT(x) ((BOOLEAN)(x & 1)) #define PAGE_1GB_SHIFT 30 #define PAGE_1GB_OFFSET(x) (x & (~(MAXUINT64 << PAGE_1GB_SHIFT))) diff --git a/driver/containers/map.c b/driver/containers/map.c index b1d3fe0..1c637af 100644 --- a/driver/containers/map.c +++ b/driver/containers/map.c @@ -10,6 +10,12 @@ RtlHashmapDelete(_In_ PRTL_HASHMAP Hashmap) ExDeleteLookasideListEx(&Hashmap->pool); } +VOID +RtlHashmapSetInactive(_Inout_ PRTL_HASHMAP Hashmap) +{ + Hashmap->active = FALSE; +} + NTSTATUS RtlHashmapCreate( _In_ UINT32 BucketCount, diff --git a/driver/containers/map.h b/driver/containers/map.h index 5a2b827..33dd073 100644 --- a/driver/containers/map.h +++ b/driver/containers/map.h @@ -85,11 +85,7 @@ RtlHashmapHashKeyAndAcquireBucket(_Inout_ PRTL_HASHMAP Hashmap, VOID RtlHashmapReleaseBucket(_Inout_ PRTL_HASHMAP Hashmap, _In_ UINT32 Index); -FORCEINLINE VOID -RtlHashmapSetInactive(_Inout_ PRTL_HASHMAP Hashmap) -{ - Hashmap->active = FALSE; -} +RtlHashmapSetInactive(_Inout_ PRTL_HASHMAP Hashmap); #endif \ No newline at end of file diff --git a/driver/driver.c b/driver/driver.c index df03d4b..452b57f 100644 --- a/driver/driver.c +++ b/driver/driver.c @@ -466,6 +466,7 @@ DriverUnload(_In_ PDRIVER_OBJECT DriverObject) UnregisterProcessCreateNotifyRoutine(); UnregisterImageLoadNotifyRoutine(); + DrvUnloadFreeThreadList(); DrvUnloadFreeProcessList(); DrvUnloadFreeDriverList(); @@ -1048,6 +1049,7 @@ DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) SetDriverLoadedFlag(); TpmExtractEndorsementKey(); + //PoolScanForManualMappedDrivers(); DEBUG_INFO("Driver Entry Complete."); return STATUS_SUCCESS; diff --git a/driver/hv.c b/driver/hv.c index 5f34c0e..ecafbcd 100644 --- a/driver/hv.c +++ b/driver/hv.c @@ -30,6 +30,9 @@ APERFMsrTimingCheck() KAFFINITY new_affinity = {0}; KAFFINITY old_affinity = {0}; UINT64 old_irql = 0; + UINT64 aperf_delta = 0; + UINT64 aperf_before = 0; + UINT64 aperf_after = 0; INT cpuid_result[4]; /* @@ -59,9 +62,9 @@ APERFMsrTimingCheck() * which we don't really care about and immediately after read the APERF * counter once again and store it in a seperate variable. */ - UINT64 aperf_before = __readmsr(IA32_APERF_MSR) << 32; + aperf_before = __readmsr(IA32_APERF_MSR) << 32; __cpuid(cpuid_result, 1); - UINT64 aperf_after = __readmsr(IA32_APERF_MSR) << 32; + aperf_after = __readmsr(IA32_APERF_MSR) << 32; /* * Once we have performed our test, we want to make sure we are not @@ -79,7 +82,7 @@ APERFMsrTimingCheck() * VMs such as VMWARE the aperf value will be 0, meaning the change will * be 0. This is a dead giveaway we are executing in a VM. */ - UINT64 aperf_delta = aperf_after - aperf_before; + aperf_delta = aperf_after - aperf_before; return aperf_delta == 0 ? TRUE : FALSE; } @@ -89,15 +92,16 @@ PerformVirtualizationDetection(_Inout_ PIRP Irp) { PAGED_CODE(); - NTSTATUS status = - ValidateIrpOutputBuffer(Irp, sizeof(HYPERVISOR_DETECTION_REPORT)); + NTSTATUS status = STATUS_UNSUCCESSFUL; + HYPERVISOR_DETECTION_REPORT report = {0}; + + status = ValidateIrpOutputBuffer(Irp, sizeof(HYPERVISOR_DETECTION_REPORT)); if (!NT_SUCCESS(status)) { DEBUG_ERROR("ValidateIrpOutputBuffer failed with status %x", status); return status; } - HYPERVISOR_DETECTION_REPORT report = {0}; report.aperf_msr_timing_check = APERFMsrTimingCheck(); report.invd_emulation_check = TestINVDEmulation(); diff --git a/driver/pool.c b/driver/pool.c index f47004e..985a5d8 100644 --- a/driver/pool.c +++ b/driver/pool.c @@ -1,387 +1,25 @@ #include "pool.h" -#include - #include "callbacks.h" - #include "crypt.h" #include "ia32.h" #include "imports.h" - #include "lib/stdlib.h" -#define PAGE_BASE_SIZE 0x1000 -#define POOL_TAG_SIZE 0x004 +#include #define PML4_ENTRY_COUNT 512 #define PDPT_ENTRY_COUNT 512 #define PD_ENTRY_COUNT 512 #define PT_ENTRY_COUNT 512 -#define LARGE_PAGE_2MB_ENTRIES 512 +#define LARGE_PAGE_2MB_ENTRIES 0x200 #define LARGE_PAGE_1GB_ENTRIES 0x40000 -#define CHUNK_SIZE 16 +#define IS_VALID_PAGE(pt, idx) ((pt)[(index)].Present) +#define IS_LARGE_PAGE(pt) ((pt).LargePage) -#define PROCESS_OBJECT_ALLOCATION_MARGIN 0x90 - -#define POOL_TAG_LENGTH 4 -#define EXECUTIVE_OBJECT_COUNT 8 - -#define INDEX_PROCESS_POOL_TAG 0 -#define INDEX_THREAD_POOL_TAG 1 -#define INDEX_DESKTOP_POOL_TAG 2 -#define INDEX_WINDOW_STATIONS_POOL_TAG 3 -#define INDEX_MUTANTS_POOL_TAG 4 -#define INDEX_FILE_OBJECTS_POOL_TAG 5 -#define INDEX_DRIVERS_POOL_TAG 6 -#define INDEX_SYMBOLIC_LINKS_POOL_TAG 7 - -CHAR EXECUTIVE_OBJECT_POOL_TAGS[EXECUTIVE_OBJECT_COUNT][POOL_TAG_LENGTH] = { - "\x50\x72\x6f\x63", /* Process */ - "\x54\x68\x72\x64", /* Thread */ - "\x44\x65\x73\x6B", /* Desktop */ - "\x57\x69\x6E\x64", /* Windows Station */ - "\x4D\x75\x74\x65", /* Mutants i.e mutex etc. */ - "\x46\x69\x6C\x65", /* File objects */ - "\x44\x72\x69\x76", /* Drivers */ - "\x4C\x69\x6E\x6B" /* Symbolic links */ -}; - -typedef struct _PROCESS_SCAN_CONTEXT { - ULONG process_count; - PVOID process_buffer; - -} PROCESS_SCAN_CONTEXT, *PPROCESS_SCAN_CONTEXT; - -STATIC -BOOLEAN -ValidateIfAddressIsProcessStructure( - _In_ PVOID Address, _In_ PPOOL_HEADER PoolHeader); - -STATIC -VOID -ScanPageForKernelObjectAllocation( - _In_ UINT64 PageBase, - _In_ ULONG PageSize, - _In_ ULONG ObjectIndex, - _Inout_ PPROCESS_SCAN_CONTEXT Context); - -STATIC -BOOLEAN -IsPhysicalAddressInPhysicalMemoryRange( - _In_ UINT64 PhysicalAddress, - _In_ PPHYSICAL_MEMORY_RANGE PhysicalMemoryRanges); - -STATIC -VOID -EnumerateKernelLargePages( - _In_ UINT64 PageBase, - _In_ ULONG PageSize, - _In_ PPROCESS_SCAN_CONTEXT Context, - _In_ ULONG ObjectIndex); - -STATIC -VOID -WalkKernelPageTables(_In_ PPROCESS_SCAN_CONTEXT Context); - -STATIC -VOID -IncrementProcessCounter(_In_ PPROCESS_LIST_ENTRY Node, _In_opt_ PVOID Context); - -STATIC -VOID -CheckIfProcessAllocationIsInProcessList( - _In_ PPROCESS_LIST_ENTRY Node, _In_opt_ PVOID Context); - -#ifdef ALLOC_PRAGMA -# pragma alloc_text(PAGE, GetGlobalDebuggerData) -# pragma alloc_text(PAGE, GetPsActiveProcessHead) -# pragma alloc_text(PAGE, IncrementProcessCounter) -# pragma alloc_text(PAGE, CheckIfProcessAllocationIsInProcessList) -# pragma alloc_text(PAGE, FindUnlinkedProcesses) -#endif - -PKDDEBUGGER_DATA64 -GetGlobalDebuggerData() -{ - PAGED_CODE(); - - CONTEXT context = {0}; - PDUMP_HEADER dump_header = {0}; - UINT64 thread_state = 0; - PKDDEBUGGER_DATA64 debugger_data = NULL; - - context.ContextFlags = CONTEXT_FULL; - - RtlCaptureContext(&context); - - dump_header = ImpExAllocatePool2( - POOL_FLAG_NON_PAGED, - DUMP_BLOCK_SIZE, - POOL_DUMP_BLOCK_TAG); - - if (!dump_header) - goto end; - - KeCapturePersistentThreadState( - &context, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - dump_header); - - debugger_data = (PKDDEBUGGER_DATA64)ExAllocatePool2( - POOL_FLAG_NON_PAGED, - sizeof(KDDEBUGGER_DATA64), - POOL_DEBUGGER_DATA_TAG); - - if (!debugger_data) - goto end; - - IntCopyMemory( - debugger_data, - dump_header->KdDebuggerDataBlock, - sizeof(KDDEBUGGER_DATA64)); - -end: - - if (dump_header) - ImpExFreePoolWithTag(dump_header, POOL_DUMP_BLOCK_TAG); - - return debugger_data; -} - -VOID -GetPsActiveProcessHead(_Out_ PUINT64 Address) -{ - PAGED_CODE(); - - *Address = NULL; - - PKDDEBUGGER_DATA64 debugger_data = GetGlobalDebuggerData(); - - if (!debugger_data) - return; - - *Address = *(UINT64*)(debugger_data->PsActiveProcessHead); - ImpExFreePoolWithTag(debugger_data, POOL_DEBUGGER_DATA_TAG); -} - -/* - * Here we define a signature that can be used to find EPROCESS structures - *consistently across major windows versions. The fields we test have proven to - *be consistent in the following study: - * - * https://www.cise.ufl.edu/~traynor/papers/ccs09b.pdf - * - * Aswell as some of my own additional research and testing. The following - *signature is used: - * - * PeakVirtualSize must be greater then 0 for any valid process: - * -> EPROCESS->PeakVirtualSize > 0 - * - * The DirectoryTableBase must be 0x20 aligned: - * -> EPROCESS->DirectoryTableBase % 20 == 0 - * - * The pool allocation size must be greater then the size of an EPROCESS - *allocation and less then the size of a page. Allocation size can be found with - *the following formula: - * -> AllocationSize = POOL_HEADER->BlockSize * CHUNK_SIZE - - *sizeof(POOL_HEADER) - * -> AllocationSize > sizeof(EPROCESS) - * -> AllocationSize < PAGE_SIZE (4096) - * - * Pool type must be non-null: - * -> POOL_HEADER->PoolType != NULL - * - * The process PEB must be a usermode address and 0x1000 aligned: - * -> EPROCESS->Peb & 0x7ffd0000 == 0x7ffd0000 && EPROCESS->Peb % 0x1000 == 0 - * - * The object table must have the following properties and be 0x8 aligned: - * -> EPROCESS->ObjectTable & 0xe0000000 == 0xe0000000 && EPROCESS->ObjectTable - *% 0x8 == 0 - * - * The allocation size, when AND'd with 0xfff0 must not equal 0xfff0: - * -> AllocationSize & 0xfff0 != 0xfff0 - * - * This signature will allow us to consistently and accurately determine if a - *given pool allocation is indeed an executive process allocation across major - *versions of Windows. - */ -STATIC -BOOLEAN -ValidateIfAddressIsProcessStructure( - _In_ PVOID Address, _In_ PPOOL_HEADER PoolHeader) -{ - UINT64 peak_virtual_size = 0; - UINT64 dir_table_base = 0; - UINT64 allocation_size = 0; - UINT64 peb = 0; - UINT64 object_table = 0; - BOOLEAN peb_test = FALSE; - BOOLEAN object_table_test = FALSE; - UINT64 allocation_size_test = 0; - - if (ImpMmIsAddressValid( - (UINT64)Address + KPROCESS_DIRECTORY_TABLE_BASE_OFFSET)) - dir_table_base = - *(UINT64*)((UINT64)Address + KPROCESS_DIRECTORY_TABLE_BASE_OFFSET); - - if (ImpMmIsAddressValid( - (UINT64)Address + EPROCESS_PEAK_VIRTUAL_SIZE_OFFSET)) - peak_virtual_size = - *(UINT64*)((UINT64)Address + EPROCESS_PEAK_VIRTUAL_SIZE_OFFSET); - - if (ImpMmIsAddressValid((UINT64)PoolHeader + POOL_HEADER_BLOCK_SIZE_OFFSET)) - allocation_size = - PoolHeader->BlockSize * CHUNK_SIZE - sizeof(POOL_HEADER); - - if (ImpMmIsAddressValid((UINT64)Address + EPROCESS_PEB_OFFSET)) - peb = *(UINT64*)((UINT64)Address + EPROCESS_PEB_OFFSET); - - if (ImpMmIsAddressValid((UINT64)Address + EPROCESS_OBJECT_TABLE_OFFSET)) - object_table = - *(UINT64*)((UINT64)Address + EPROCESS_OBJECT_TABLE_OFFSET); - - peb_test = - peb == NULL || (peb & 0x7ffd0000 == 0x7ffd0000 && peb % 0x1000 == NULL); - object_table_test = - object_table == NULL || - (object_table & 0xe0000000 == 0xe0000000 && object_table % 0x8 == 0); - allocation_size_test = allocation_size & 0xfff0; - - if (peak_virtual_size > 0 && (dir_table_base & 0x20) == 0 && - allocation_size > - (EPROCESS_SIZE + OBJECT_HEADER_SIZE + sizeof(POOL_HEADER)) && - PoolHeader->PoolType != NULL && !(allocation_size_test == 0xfff0) && - !peb_test && !object_table_test) { - return TRUE; - } - - return FALSE; -} - -/* - * OBJECT_HEADER->InfoMask is a bit mask that tells us which optional - * headers the object has. The bits are as follows: - * - * 0x1 = OBJECT_HEADER_CREATOR_INFO - * 0x2 = OBJECT_HEADER_NAME_INFO - * 0x4 = OBJECT_HEADER_HANDLE_INFO - * 0x8 = OBJECT_HEADER_QUOTA_INFO - * 0x10 = OBJECT_HEADER_PROCESS_INFO - * 0x20 = OBJECT_HEADER_AUDIT_INFO - * 0x40 = OBJECT_HEADER_HANDLE_REVOCATION_INFO - */ - -/* - * Idea: since we don't know the number of headers or the exact memory layout of - * the object header section for these proc allocations, we can form an estimate - * address of base + 0x70 and then iterate the loaded process list and if theres - * an address within say 0x50 of it we can assume that the process is legitmate. - * Then to find an unlinked process, it wouldn't exist in the loaded module - * list, check that it hasnt been deallocated and then focus on scanning it for - * name etc. Maybe scan for .exe extension? - * - * Also use the full name so we get the file extension and path not the 15 char - * long one - */ -STATIC -VOID -ScanPageForKernelObjectAllocation( - _In_ UINT64 PageBase, - _In_ ULONG PageSize, - _In_ ULONG ObjectIndex, - _Inout_ PPROCESS_SCAN_CONTEXT Context) -{ - INT length = 0; - CHAR current_char = 0; - CHAR current_sig_byte = 0; - PPOOL_HEADER pool_header = NULL; - PEPROCESS process = NULL; - PEPROCESS process_size_one = NULL; - PEPROCESS process_size_two = NULL; - PEPROCESS test_process = NULL; - LPCSTR process_name = NULL; - PUINT64 address_list = NULL; - ULONG allocation_size = 0; - ULONG minimum_process_allocation_size = - EPROCESS_SIZE - sizeof(POOL_HEADER) - OBJECT_HEADER_SIZE; - - if (!PageBase || !PageSize) - return; - - for (INT offset = 0; - offset <= PageSize - POOL_TAG_LENGTH - minimum_process_allocation_size; - offset++) { - for (INT sig_index = 0; sig_index < POOL_TAG_LENGTH + 1; sig_index++) { - if (!ImpMmIsAddressValid(PageBase + offset + sig_index)) - break; - - current_char = *(PCHAR)(PageBase + offset + sig_index); - current_sig_byte = - EXECUTIVE_OBJECT_POOL_TAGS[ObjectIndex][sig_index]; - - if (sig_index == POOL_TAG_LENGTH) { - pool_header = - (UINT64)PageBase + offset - POOL_HEADER_TAG_OFFSET; - - if (!ImpMmIsAddressValid((PVOID)pool_header)) - break; - - /* - * Since every executive allocation is required - * to have an _OBJECT_HEADER, we start iterating - * from the size of this object header, then - * jump up in blocks of 0x10 since every object - * header is divisible by 0x10. We iterate up to - * 0xb0 which is equal to the following: - * - * 0xb0 = sizeof(ALL_HEADER_OBJECTS) + 0x10 - * where the 0x10 is 16 bytes of padding. - */ - for (ULONG header_size = OBJECT_HEADER_SIZE; header_size < 0xb0; - header_size += 0x10) { - test_process = - (PEPROCESS)((UINT64)pool_header + sizeof(POOL_HEADER) + - header_size); - - if (ValidateIfAddressIsProcessStructure( - test_process, - pool_header)) { - process = test_process; - break; - } - } - - if (!process) - break; - - DEBUG_VERBOSE( - "Found process via pt walk: %llx", - (UINT64)process); - - address_list = (PUINT64)Context->process_buffer; - - for (INT i = 0; i < Context->process_count; i++) { - if (address_list[i] == NULL) { - address_list[i] = (UINT64)process; - break; - } - } - - break; - } - - if (current_char != current_sig_byte) - break; - } - } -} +STATIC PVOID local_page_copy_buf = NULL; /* * Using MmGetPhysicalMemoryRangesEx2(), we can get a block of structures that @@ -391,468 +29,280 @@ ScanPageForKernelObjectAllocation( */ STATIC BOOLEAN -IsPhysicalAddressInPhysicalMemoryRange( +PoolpIsAddressInPhysicalRange( _In_ UINT64 PhysicalAddress, _In_ PPHYSICAL_MEMORY_RANGE PhysicalMemoryRanges) { - ULONG page_index = 0; - UINT64 start_address = 0; - UINT64 end_address = 0; + ULONG index = 0; + UINT64 start = 0; + UINT64 end = 0; - while (PhysicalMemoryRanges[page_index].NumberOfBytes.QuadPart != NULL) { - start_address = PhysicalMemoryRanges[page_index].BaseAddress.QuadPart; - end_address = start_address + - PhysicalMemoryRanges[page_index].NumberOfBytes.QuadPart; + while (PhysicalMemoryRanges[index].NumberOfBytes.QuadPart) { + start = PhysicalMemoryRanges[index].BaseAddress.QuadPart; + end = start + PhysicalMemoryRanges[index].NumberOfBytes.QuadPart; - if (PhysicalAddress >= start_address && PhysicalAddress <= end_address) + if (PhysicalAddress >= start && PhysicalAddress <= end) return TRUE; - page_index++; + index++; } return FALSE; } STATIC -VOID -EnumerateKernelLargePages( +BOOLEAN +PoolpScanLargePage( _In_ UINT64 PageBase, - _In_ ULONG PageSize, - _In_ PPROCESS_SCAN_CONTEXT Context, - _In_ ULONG ObjectIndex) + _In_ UINT32 PageSize, + _In_ PAGE_CALLBACK Callback, + _In_opt_ PVOID Context) { - /* - * Split the large pages up into blocks of 0x1000 and scan each block - */ - for (UINT64 page_index = 0; page_index < PageSize; page_index++) { - UINT64 page_base = PageBase + (page_index * PAGE_SIZE); - ScanPageForKernelObjectAllocation( - page_base, + UINT64 page = 0; + BOOLEAN stop = FALSE; + + if (!PageBase || !ImpMmIsAddressValid(PageBase)) + return FALSE; + + for (UINT32 page_index = 0; page_index < PageSize; page_index++) { + page = PageBase + (page_index * PAGE_SIZE); + + if (Callback(page, PAGE_SIZE, Context)) + return TRUE; + } + + return FALSE; +} + +STATIC +BOOLEAN +PoolpScanPageTable( + _In_ PTE_64 Pte, _In_ PAGE_CALLBACK Callback, _In_opt_ PVOID Context) +{ + UINT64 page = 0; + PHYSICAL_ADDRESS pa = {0}; + NTSTATUS status = STATUS_UNSUCCESSFUL; + MM_COPY_ADDRESS addr = {0}; + UINT32 bytes = 0; + + pa.QuadPart = Pte.PageFrameNumber << PAGE_4KB_SHIFT; + page = MmGetVirtualForPhysical(pa); + + if (!page || !ImpMmIsAddressValid(page)) { + addr.PhysicalAddress = pa; + status = MmCopyMemory( + local_page_copy_buf, + addr, PAGE_SIZE, - ObjectIndex, + MM_COPY_MEMORY_PHYSICAL, + &bytes); + + if (!NT_SUCCESS(status)) + return FALSE; + + DEBUG_VERBOSE( + "valid mm page: %llx, pa: %llx, copied: %lx", + local_page_copy_buf, + pa.QuadPart, + bytes); + return Callback(local_page_copy_buf, PAGE_SIZE, Context); + } + + return Callback(page, PAGE_SIZE, Context); +} + +STATIC +BOOLEAN +PoolpScanPageDirectory( + _In_ PDE_64 Pde, _In_ PAGE_CALLBACK Callback, _In_opt_ PVOID Context) +{ + PTE_64* pt = NULL; + PDE_2MB_64 pdel = {0}; + PHYSICAL_ADDRESS pa = {0}; + + if (IS_LARGE_PAGE(Pde)) { + pdel.AsUInt = Pde.AsUInt; + pa.QuadPart = pdel.PageFrameNumber << PAGE_2MB_SHIFT; + + return PoolpScanLargePage( + ImpMmGetVirtualForPhysical(pa), + LARGE_PAGE_2MB_ENTRIES, + Callback, Context); } + + pa.QuadPart = Pde.PageFrameNumber << PAGE_4KB_SHIFT; + pt = ImpMmGetVirtualForPhysical(pa); + + if (!pt || !ImpMmIsAddressValid(pt)) + return FALSE; + + for (UINT32 index = 0; index < PT_ENTRY_COUNT; index++) { + if (!IS_VALID_PAGE(pt, index)) + continue; + + // DEBUG_VERBOSE( + // "------> pt va: %llx, pte: %llx, index: %lx", + // pt, + // pt[index], + // index); + + if (PoolpScanPageTable(pt[index], Callback, Context)) + return TRUE; + } + + return FALSE; } -/* - * This is your basic page table walk function. On intel systems, paging has 4 - * levels, each table holds 512 entries with a total size of 0x1000 (512 * - * sizeof(QWORD)). Each entry in each table contains a value with a subset - * bitfield containing the physical address of the base of the next table in the - * structure. So for example, a PML4 entry contains a physical address that - * points to the base of the PDPT table, it is the same for a PDPT entry -> PD - * base and so on. - * - * However, as with all good things Windows has implemented security features - * meaning we cannot use functions such as MmCopyMemory or MmMapIoSpace on - * paging structures, so we must find another way to walk the pages. Luckily for - * us, there exists MmGetVirtualForPhysical. This function is self explanatory - * and returns the corresponding virtual address given a physical address. What - * this means is that we can extract a page entry physical address, pass it to - * MmGetVirtualForPhysical which returns us the virtual address of the base of - * the next page structure. This is because page tables are still mapped by the - * kernel and exist in virtual memory just like everything else and hence - * reading the value at all 512 entries from the virtual base will give us the - * equivalent value as directly reading the physical address. - * - * Using this, we essentially walk the page tables as any regular translation - * would except instead of simply reading the physical we translate it to a - * virtual address and extract the physical address from the value at each - * virtual address page entry. - * - * TODO: rewrite this its kinda ugly - */ STATIC -VOID -WalkKernelPageTables(_In_ PPROCESS_SCAN_CONTEXT Context) +BOOLEAN +PoolpScanPageDirectoryPointerTable( + _In_ PDPTE_64 Pdpte, _In_ PAGE_CALLBACK Callback, _In_opt_ PVOID Context) { - CR3 cr3 = {0}; - PML4E pml4_base = {0}; - PML4E pml4_entry = {0}; - UINT64 pdpt_base = 0; - UINT64 pd_base = 0; - UINT64 pt_base = 0; - PDPTE pdpt_entry = {0}; - PDPTE_LARGE pdpt_large_entry = {0}; - PDE pd_entry = {0}; - PDE_LARGE pd_large_entry = {0}; - PTE pt_entry = {0}; - UINT64 base_physical_page = 0; - UINT64 base_virtual_page = 0; - UINT64 base_2mb_virtual_page = 0; - UINT64 base_1gb_virtual_page = 0; - PHYSICAL_ADDRESS physical = {0}; - PPHYSICAL_MEMORY_RANGE physical_memory_ranges = NULL; - KIRQL irql = {0}; + PDE_64* pd = NULL; + PDPTE_1GB_64 pdptel = {0}; + PHYSICAL_ADDRESS pa = {0}; - physical_memory_ranges = ImpMmGetPhysicalMemoryRangesEx2(NULL, NULL); + if (IS_LARGE_PAGE(Pdpte)) { + pdptel.AsUInt = Pdpte.AsUInt; + pa.QuadPart = pdptel.PageFrameNumber << PAGE_1GB_SHIFT; - if (!physical_memory_ranges) { - DEBUG_ERROR("MmGetPhysicalMemoryRangesEx2 failed with no status."); - return; + return PoolpScanLargePage( + ImpMmGetVirtualForPhysical(pa), + LARGE_PAGE_1GB_ENTRIES, + Callback, + Context); } + pa.QuadPart = Pdpte.PageFrameNumber << PAGE_4KB_SHIFT; + pd = ImpMmGetVirtualForPhysical(pa); + + if (!pd || !ImpMmIsAddressValid(pd)) + return FALSE; + + for (UINT32 index = 0; index < PD_ENTRY_COUNT; index++) { + if (!IS_VALID_PAGE(pd, index)) + continue; + + // DEBUG_VERBOSE( + // "----> pd va: %llx, pde: %llx, index: %lx", + // pd, + // pd[index], + // index); + + if (PoolpScanPageDirectory(pd[index], Callback, Context)) + return TRUE; + } + + return FALSE; +} + +STATIC +BOOLEAN +PoolpScanPageMapLevel4( + _In_ PML4E_64 Pml4e, _In_ PAGE_CALLBACK Callback, _In_opt_ PVOID Context) +{ + BOOLEAN stop = FALSE; + PDPTE_64* pdpt = NULL; + PHYSICAL_ADDRESS pa = {0}; + + pa.QuadPart = Pml4e.PageFrameNumber << PAGE_4KB_SHIFT; + pdpt = ImpMmGetVirtualForPhysical(pa); + + if (!pdpt || !ImpMmIsAddressValid(pdpt)) + return FALSE; + + for (UINT32 index = 0; index < PDPT_ENTRY_COUNT; index++) { + if (!IS_VALID_PAGE(pdpt, index)) + continue; + + // DEBUG_VERBOSE( + // "--> pdpt va: %llx, pdpte: %llx, index: %lx", + // pdpt, + // pdpt[index], + // index); + + if (PoolpScanPageDirectoryPointerTable(pdpt[index], Callback, Context)) + return TRUE; + } + + return FALSE; +} + +NTSTATUS +PoolScanSystemSpace(_In_ PAGE_CALLBACK Callback, _In_opt_ PVOID Context) +{ + NT_ASSERT(Callback != NULL); + + CR3 cr3 = {0}; + PML4E_64* pml4 = NULL; + PHYSICAL_ADDRESS pa = {0}; + + if (!Callback) + return STATUS_INVALID_PARAMETER; + cr3.AsUInt = __readcr3(); + pa.QuadPart = cr3.AddressOfPageDirectory << PAGE_4KB_SHIFT; + pml4 = ImpMmGetVirtualForPhysical(pa); - physical.QuadPart = cr3.AddressOfPageDirectory << PAGE_4KB_SHIFT; + // DEBUG_VERBOSE("system cr3: %llx", cr3.AsUInt); - pml4_base.BitAddress = ImpMmGetVirtualForPhysical(physical); + if (!pml4 || !ImpMmIsAddressValid(pml4)) + return STATUS_UNSUCCESSFUL; - if (!ImpMmIsAddressValid(pml4_base.BitAddress) || !pml4_base.BitAddress) - return; - - for (INT pml4_index = 0; pml4_index < PML4_ENTRY_COUNT; pml4_index++) { - if (!ImpMmIsAddressValid( - pml4_base.BitAddress + pml4_index * sizeof(UINT64))) + for (UINT32 index = 490; index < PML4_ENTRY_COUNT; index++) { + if (!IS_VALID_PAGE(pml4, index)) continue; - pml4_entry.BitAddress = - *(UINT64*)(pml4_base.BitAddress + pml4_index * sizeof(UINT64)); + // DEBUG_VERBOSE( + // "pml4 va: %llx, pml4e: %llx, index: %lx", + // pml4, + // pml4[index], + // index); - if (pml4_entry.Bits.Present == NULL) - continue; - - physical.QuadPart = pml4_entry.Bits.PhysicalAddress << PAGE_4KB_SHIFT; - - pdpt_base = ImpMmGetVirtualForPhysical(physical); - - if (!pdpt_base || !ImpMmIsAddressValid(pdpt_base)) - continue; - - for (INT pdpt_index = 0; pdpt_index < PDPT_ENTRY_COUNT; pdpt_index++) { - if (!ImpMmIsAddressValid(pdpt_base + pdpt_index * sizeof(UINT64))) - continue; - - pdpt_entry.BitAddress = - *(UINT64*)(pdpt_base + pdpt_index * sizeof(UINT64)); - - if (pdpt_entry.Bits.Present == NULL) - continue; - - if (IS_LARGE_PAGE(pdpt_entry.BitAddress)) { - /* 1gb size page */ - pdpt_large_entry.BitAddress = pdpt_entry.BitAddress; - - physical.QuadPart = pdpt_large_entry.Bits.PhysicalAddress - << PAGE_1GB_SHIFT; - - if (IsPhysicalAddressInPhysicalMemoryRange( - physical.QuadPart, - physical_memory_ranges) == FALSE) - continue; - - base_1gb_virtual_page = ImpMmGetVirtualForPhysical(physical); - - if (!base_1gb_virtual_page || - !ImpMmIsAddressValid(base_1gb_virtual_page)) - continue; - - EnumerateKernelLargePages( - base_1gb_virtual_page, - LARGE_PAGE_1GB_ENTRIES, - Context, - INDEX_PROCESS_POOL_TAG); - - continue; - } - - physical.QuadPart = pdpt_entry.Bits.PhysicalAddress - << PAGE_4KB_SHIFT; - - pd_base = ImpMmGetVirtualForPhysical(physical); - - if (!pd_base || !ImpMmIsAddressValid(pd_base)) - continue; - - for (INT pd_index = 0; pd_index < PD_ENTRY_COUNT; pd_index++) { - if (!ImpMmIsAddressValid(pd_base + pd_index * sizeof(UINT64))) - continue; - - pd_entry.BitAddress = - *(UINT64*)(pd_base + pd_index * sizeof(UINT64)); - - if (pd_entry.Bits.Present == NULL) - continue; - - if (IS_LARGE_PAGE(pd_entry.BitAddress)) { - /* 2MB size page */ - pd_large_entry.BitAddress = pd_entry.BitAddress; - - physical.QuadPart = pd_large_entry.Bits.PhysicalAddress - << PAGE_2MB_SHIFT; - - if (IsPhysicalAddressInPhysicalMemoryRange( - physical.QuadPart, - physical_memory_ranges) == FALSE) - continue; - - base_2mb_virtual_page = - ImpMmGetVirtualForPhysical(physical); - - if (!base_2mb_virtual_page || - !ImpMmIsAddressValid(base_2mb_virtual_page)) - continue; - - EnumerateKernelLargePages( - base_2mb_virtual_page, - LARGE_PAGE_2MB_ENTRIES, - Context, - INDEX_PROCESS_POOL_TAG); - - continue; - } - - physical.QuadPart = pd_entry.Bits.PhysicalAddress - << PAGE_4KB_SHIFT; - - if (!ImpMmIsAddressValid(pd_base + pd_index * sizeof(UINT64))) - continue; - - pt_base = ImpMmGetVirtualForPhysical(physical); - - if (!pt_base || !ImpMmIsAddressValid(pt_base)) - continue; - - for (INT pt_index = 0; pt_index < PT_ENTRY_COUNT; pt_index++) { - if (!ImpMmIsAddressValid( - pt_base + pt_index * sizeof(UINT64))) - continue; - - pt_entry.BitAddress = - *(UINT64*)(pt_base + pt_index * sizeof(UINT64)); - - if (pt_entry.Bits.Present == NULL) - continue; - - physical.QuadPart = pt_entry.Bits.PhysicalAddress - << PAGE_4KB_SHIFT; - - /* if the page base isnt in a legit - * region, go next */ - if (IsPhysicalAddressInPhysicalMemoryRange( - physical.QuadPart, - physical_memory_ranges) == FALSE) - continue; - - base_virtual_page = ImpMmGetVirtualForPhysical(physical); - - /* stupid fucking intellisense error GO - * AWAY! */ - if (base_virtual_page == NULL || - !ImpMmIsAddressValid(base_virtual_page)) - continue; - - ScanPageForKernelObjectAllocation( - base_virtual_page, - PAGE_BASE_SIZE, - INDEX_PROCESS_POOL_TAG, - Context); - } - } - } + if (PoolpScanPageMapLevel4(pml4[index], Callback, Context)) + break; } - DEBUG_VERBOSE("Finished scanning memory for unlinked processes."); -} - -STATIC -VOID -IncrementProcessCounter(_In_ PPROCESS_LIST_ENTRY Node, _In_opt_ PVOID Context) -{ - PAGED_CODE(); - - UNREFERENCED_PARAMETER(Node); - - PPROCESS_SCAN_CONTEXT context = (PPROCESS_SCAN_CONTEXT)Context; - - if (!context) - return; - - context->process_count++; -} - -STATIC -VOID -CheckIfProcessAllocationIsInProcessList( - _In_ PPROCESS_LIST_ENTRY Node, _In_opt_ PVOID Context) -{ - PAGED_CODE(); - - PUINT64 allocation_address = NULL; - PPROCESS_SCAN_CONTEXT context = (PPROCESS_SCAN_CONTEXT)Context; - - if (!context) - return; - - for (INT i = 0; i < context->process_count; i++) { - allocation_address = (PUINT64)context->process_buffer; - - if ((UINT64)Node->process >= - allocation_address[i] - PROCESS_OBJECT_ALLOCATION_MARGIN && - (UINT64)Node->process <= - allocation_address[i] + PROCESS_OBJECT_ALLOCATION_MARGIN) { - RtlZeroMemory( - (UINT64)context->process_buffer + i * sizeof(UINT64), - sizeof(UINT64)); - } - } -} - -/* - * This is actually broken right now since changing to use our process list, - * will need to fix at somepoint. - */ -NTSTATUS -FindUnlinkedProcesses() -{ - PAGED_CODE(); - - NTSTATUS status = STATUS_UNSUCCESSFUL; - PUINT64 allocation_address = NULL; - PROCESS_SCAN_CONTEXT context = {0}; - PINVALID_PROCESS_ALLOCATION_REPORT report = NULL; - UINT32 packet_size = CryptRequestRequiredBufferLength( - sizeof(INVALID_PROCESS_ALLOCATION_REPORT)); - - RtlHashmapEnumerate(GetProcessHashmap(), IncrementProcessCounter, &context); - - if (context.process_count == 0) { - DEBUG_ERROR("IncrementProcessCounter failed with no status."); - return STATUS_ABANDONED; - } - - context.process_buffer = ExAllocatePool2( - POOL_FLAG_NON_PAGED, - context.process_count * 2 * sizeof(UINT64), - PROCESS_ADDRESS_LIST_TAG); - - if (!context.process_buffer) - return STATUS_MEMORY_NOT_ALLOCATED; - - WalkKernelPageTables(&context); - - RtlHashmapEnumerate( - GetProcessHashmap(), - CheckIfProcessAllocationIsInProcessList, - &context); - - allocation_address = (PUINT64)context.process_buffer; - - for (INT index = 0; index < context.process_count; index++) { - if (allocation_address[index] == NULL) - continue; - - UINT64 allocation = - (UINT64)allocation_address[index] - OBJECT_HEADER_SIZE; - - /* - * It's important to remember that at this point it is still not - * guaranteed that we have found an unlinked process allocation. - * It is better to have a few false positives that can be later - * analysed rather then enforce a strict signature and - * potentially miss a real unlinked process. - */ - DEBUG_WARNING( - "Potentially found an unlinked process allocation at address: %llx", - allocation); - - report = ImpExAllocatePool2( - POOL_FLAG_NON_PAGED, - packet_size, - REPORT_POOL_TAG); - - if (!report) - continue; - - INIT_REPORT_PACKET(report, REPORT_INVALID_PROCESS_ALLOCATION, 0); - - IntCopyMemory( - report->process, - allocation, - REPORT_INVALID_PROCESS_BUFFER_SIZE); - - status = CryptEncryptBuffer(report, packet_size); - - if (!NT_SUCCESS(status)) { - DEBUG_ERROR("CryptEncryptBuffer: %lx", status); - ImpExFreePoolWithTag(report, REPORT_POOL_TAG); - continue; - } - - IrpQueueSchedulePacket(report, packet_size); - } - -end: - - if (context.process_buffer) - ImpExFreePoolWithTag(context.process_buffer, PROCESS_ADDRESS_LIST_TAG); - return STATUS_SUCCESS; } -/* - * Allocations greater then a page in size are stored in a linked list and are - * called big pool allocations. - */ +/* Credits to Samuel Tulach c: + * https://tulach.cc/detecting-manually-mapped-drivers/ */ + +// #36aae000 4d 5a 90 +#define GADGET_BYTE_ONE 0x4D // 0xFF +#define GADGET_BYTE_TWO 0x5A // 0x25 +#define GADGET_BYTE_THREE 0x90 // 0x25 + +STATIC +BOOLEAN +PoolScanForManualMappedDriverCallback( + _In_ UINT64 Page, _In_ UINT32 PageSize, _In_opt_ PVOID Context) +{ + PCHAR byte = (PCHAR)Page; + + // DEBUG_VERBOSE("--------> page: %llx", Page); + + for (UINT32 index = 0; index < PageSize - 1; index++) { + if (byte[index] == GADGET_BYTE_ONE && + byte[index + 1] == GADGET_BYTE_TWO && + byte[index + 2] == GADGET_BYTE_THREE) { + DEBUG_VERBOSE("FOUND!"); + } + } + + return FALSE; +} NTSTATUS -EnumerateBigPoolAllocations() +PoolScanForManualMappedDrivers() { - ULONG return_length = 0; NTSTATUS status = STATUS_UNSUCCESSFUL; - PSYSTEM_BIGPOOL_ENTRY entry = NULL; - SYSTEM_BIGPOOL_INFORMATION pool_information = {0}; - PSYSTEM_BIGPOOL_INFORMATION pool_entries = NULL; - UNICODE_STRING routine = RTL_CONSTANT_STRING(L"ZwQuerySystemInformation"); - ZwQuerySystemInformation pZwQuerySystemInformation = - ImpMmGetSystemRoutineAddress(&routine); - - if (!pZwQuerySystemInformation) { - DEBUG_ERROR("MmGetSystemRoutineAddress failed with no status."); - return status; - } - - status = pZwQuerySystemInformation( - SYSTEM_BIGPOOL_INFORMATION_ID, - &pool_information, - sizeof(pool_information), - &return_length); - - if (status != STATUS_INFO_LENGTH_MISMATCH) { - DEBUG_ERROR("ZwQuerySystemInformation failed with status %x", status); - return status; - } - - return_length += sizeof(SYSTEM_BIGPOOL_INFORMATION); - - pool_entries = ImpExAllocatePool2( - POOL_FLAG_NON_PAGED, - return_length, - POOL_TAG_INTEGRITY); - - if (!pool_entries) - return STATUS_MEMORY_NOT_ALLOCATED; - - status = pZwQuerySystemInformation( - SYSTEM_BIGPOOL_INFORMATION_ID, - pool_entries, - return_length, - &return_length); - - if (!NT_SUCCESS(status)) { - DEBUG_ERROR("ZwQuerySystemInformation 2 failed with status %x", status); - goto end; - } - - for (INT index = 0; index < pool_entries->Count; index++) { - entry = &pool_entries->AllocatedInfo[index]; - } - // MiGetPteAddress of va - // check if page is executaable -end: - - if (pool_entries) - ImpExFreePoolWithTag(pool_entries, POOL_TAG_INTEGRITY); - - return status; -} \ No newline at end of file + DEBUG_VERBOSE("scanning for gadget"); + local_page_copy_buf = + ImpExAllocatePool2(POOL_FLAG_NON_PAGED, PAGE_SIZE, POOL_TAG_INTEGRITY); + PoolScanSystemSpace(PoolScanForManualMappedDriverCallback, NULL); + DEBUG_VERBOSE("fnished scanning"); + ImpExFreePoolWithTag(local_page_copy_buf, POOL_TAG_INTEGRITY); +} diff --git a/driver/pool.h b/driver/pool.h index f439976..da67067 100644 --- a/driver/pool.h +++ b/driver/pool.h @@ -4,16 +4,12 @@ #include #include "common.h" -NTSTATUS -FindUnlinkedProcesses(); - -VOID -GetPsActiveProcessHead(_Out_ PUINT64 Address); - -PKDDEBUGGER_DATA64 -GetGlobalDebuggerData(); +typedef BOOLEAN (*PAGE_CALLBACK)(_In_ UINT64 Page, _In_ UINT32 PageSize, _In_opt_ PVOID Context); NTSTATUS -EnumerateBigPoolAllocations(); +PoolScanSystemSpace(_In_ PAGE_CALLBACK Callback, _In_opt_ PVOID Context); + +NTSTATUS +PoolScanForManualMappedDrivers(); #endif \ No newline at end of file