From 54a0e9040556b741f822fbdbd1f5918699766084 Mon Sep 17 00:00:00 2001 From: lhodges1 Date: Mon, 28 Aug 2023 19:17:38 +1000 Subject: [PATCH] oh yea --- driver/driver.c | 3 +- driver/pool.c | 223 +++++++++++++++++++++++++++++++++--------------- driver/pool.h | 9 ++ 3 files changed, 165 insertions(+), 70 deletions(-) diff --git a/driver/driver.c b/driver/driver.c index 5b8cfda..343b440 100644 --- a/driver/driver.c +++ b/driver/driver.c @@ -5,6 +5,7 @@ #include "callbacks.h" #include "hv.h" +#include "pool.h" #include "integrity.h" @@ -115,7 +116,7 @@ NTSTATUS DriverEntry( NULL, NULL, NULL, - WalkKernelPageTables, + FindUnlinkedProcesses, NULL ); diff --git a/driver/pool.c b/driver/pool.c index ebdccc9..9ffc9a1 100644 --- a/driver/pool.c +++ b/driver/pool.c @@ -2,6 +2,8 @@ #include "common.h" +#include "callbacks.h" + #include #define POOL_TAG_LENGTH 4 @@ -15,6 +17,9 @@ CHAR FILE_OBJECTS_POOL_TAG[ POOL_TAG_LENGTH ] = "\x46\x69\x6C\x65"; CHAR DRIVERS_POOL_TAG[ POOL_TAG_LENGTH ] = "\x44\x72\x69\x76"; CHAR SYMBOLIC_LINKS_POOL_TAG[ POOL_TAG_LENGTH ] = "\x4C\x69\x6E\x6B"; +PVOID process_buffer = NULL; +ULONG process_count = NULL; + PKDDEBUGGER_DATA64 GetGlobalDebuggerData() { CONTEXT context = { 0 }; @@ -57,24 +62,57 @@ end: return debugger_data; } +/* +* For ~70% of EPROCESS structures the header layout is as follows: +* +* Pool base + 0x00 = ?? (not sure what structure lies here) +* Pool base + 0x10 = OBJECT_HEADER_QUOTA_INFO +* Pool base + 0x30 = OBJECT_HEADER_HANDLE_INFO +* Pool base + 0x40 = OBJECT_HEADER +* Pool base + 0x70 = EPROCESS +* +* 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 +*/ + VOID ScanPageForKernelObjectAllocation( _In_ UINT64 PageBase, _In_ ULONG PageSize, - _In_ LPCSTR ObjectTag + _In_ LPCSTR ObjectTag, + _In_ PVOID AddressBuffer ) { INT length = 0; CHAR current_char; CHAR current_sig_byte; PPOOL_HEADER pool_header; - PEPROCESS process; + PEPROCESS process = NULL; LPCSTR process_name; + PUINT64 address_list; + ULONG allocation_size; if ( !PageBase || !PageSize || !ObjectTag) return; - PAGED_CODE(); - for ( INT offset = 0; offset <= PageSize - POOL_TAG_LENGTH; offset++ ) { for ( INT sig_index = 0; sig_index < POOL_TAG_LENGTH + 1; sig_index++ ) @@ -95,45 +133,41 @@ VOID ScanPageForKernelObjectAllocation( /* * This is some hard coded trash, need to figure out how we can differentiate different * types of objects since they would each have a varying number of headers, object sizes etc. + * + * For now we check 2 sizes, one of which is 0x10 smaller then the other (the unknown header?) + * and make sure the pool is still allocated by checking the PoolType != 0. + * + * more: https://www.imf-conference.org/imf2006/23_Schuster-PoolAllocations.pdf */ - if ( pool_header->BlockSize * CHUNK_SIZE - sizeof(POOL_HEADER) == WIN_PROCESS_ALLOCATION_SIZE ) + + allocation_size = pool_header->BlockSize * CHUNK_SIZE - sizeof( POOL_HEADER ); + + if ( ( allocation_size == WIN_PROCESS_ALLOCATION_SIZE || + allocation_size == WIN_PROCESS_ALLOCATION_SIZE_2 ) && + pool_header->PoolType != NULL ) { - /* - * For ~70% of EPROCESS structures the header layout is as follows: - * - * Pool base + 0x00 = ?? (not sure what structure lies here) - * Pool base + 0x10 = OBJECT_HEADER_QUOTA_INFO - * Pool base + 0x30 = OBJECT_HEADER_HANDLE_INFO - * Pool base + 0x40 = OBJECT_HEADER - * Pool base + 0x70 = EPROCESS - * - * 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 - */ + if ( allocation_size == WIN_PROCESS_ALLOCATION_SIZE ) + process = process = ( PEPROCESS )( ( UINT64 )pool_header + sizeof( POOL_HEADER ) + 0x70 ); - process = (PEPROCESS)( ( UINT64 )pool_header + sizeof( POOL_HEADER ) + 0x70 ); + if ( allocation_size == WIN_PROCESS_ALLOCATION_SIZE_2 ) + process = process = ( PEPROCESS )( ( UINT64 )pool_header + sizeof( POOL_HEADER ) + 0x80 ); - process_name = PsGetProcessImageFileName( process ); + if ( process == NULL ) + { + DEBUG_LOG( "Process size is different to expected cos we hardcoded dis trash" ); + break; + } - /* - * 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 - */ - DEBUG_LOG( "Found process: %s", process_name ); + address_list = ( PUINT64 )AddressBuffer; + + for ( INT i = 0; i < process_count; i++ ) + { + if ( address_list[ i ] == NULL ) + { + address_list[ i ] = ( UINT64 )process; + break; + } + } } break; @@ -201,7 +235,7 @@ BOOLEAN IsPhysicalAddressInPhysicalMemoryRange( * and extract the physical address from the value at each virtual address page entry. */ -VOID WalkKernelPageTables() +VOID WalkKernelPageTables(PVOID AddressBuffer) { CR3 cr3; PML4E pml4_base; @@ -228,12 +262,6 @@ VOID WalkKernelPageTables() return; } - /* raise our irql to ensure we arent preempted by NOOB threads */ - KeRaiseIrql( DISPATCH_LEVEL, &irql ); - - /* disable interrupts to prevent any funny business occuring */ - _disable(); - cr3.BitAddress = __readcr3(); physical.QuadPart = cr3.Bits.PhysicalAddress << PAGE_4KB_SHIFT; @@ -310,55 +338,112 @@ VOID WalkKernelPageTables() /* if the page base isnt in a legit region, go next */ if ( IsPhysicalAddressInPhysicalMemoryRange( physical.QuadPart, physical_memory_ranges ) == FALSE ) - continue; + continue; base_virtual_page = MmGetVirtualForPhysical( physical ); /* stupid fucking intellisense error GO AWAY! */ if ( base_virtual_page == NULL || !MmIsAddressValid( base_virtual_page ) ) - continue; + continue; - ScanPageForKernelObjectAllocation( base_virtual_page, PAGE_BASE_SIZE, (LPCSTR)PROCESS_POOL_TAG ); + ScanPageForKernelObjectAllocation( + base_virtual_page, + PAGE_BASE_SIZE, + ( LPCSTR )PROCESS_POOL_TAG, + AddressBuffer + ); } } } } - _enable(); - - KeLowerIrql( irql ); - DEBUG_LOG( "Finished scanning memory" ); } -VOID ScanNonPagedPoolForProcessTags() +VOID IncrementProcessCounter() +{ + process_count++; +} + +VOID CheckIfProcessAllocationIsInProcessList( + _In_ PEPROCESS Process +) +{ + PUINT64 allocation_address; + + for ( INT i = 0; i < process_count; i++ ) + { + allocation_address = ( PUINT64 )process_buffer; + + if ( ( UINT64 )Process >= allocation_address[ i ] - PROCESS_OBJECT_ALLOCATION_MARGIN && + ( UINT64 )Process <= allocation_address[ i ] + PROCESS_OBJECT_ALLOCATION_MARGIN ) + { + RtlZeroMemory( ( UINT64 )process_buffer + i * sizeof( UINT64 ), sizeof( UINT64 ) ); + } + } +} + +/* +* Plan: +* 1. Find number of running procs and allocate a pool equal to size 1.5 * num_procs. +* 2. Walk the pages tables and store all found process allocation base addresses in this pool +* 3. Enumerate the process allocation list and make sure that each allocation is within +* 0x50 bytes of the EPROCESS base of one of the running processes. +* 4. If there exists a process allocation that doesn't have a matching running process, +* make sure it hasnt been deallocated +* 5. If it hasn't been deallocated, search for the .exe via the long string file name +* and report. Maybe do some further analysis can figure this out once we get there. +*/ +NTSTATUS FindUnlinkedProcesses() { NTSTATUS status; - PKDDEBUGGER_DATA64 debugger_data = NULL; - UINT64 non_paged_pool_start = NULL; - UINT64 non_paged_pool_end = NULL; + PUINT64 allocation_address; - /* must free this */ - debugger_data = GetGlobalDebuggerData(); + EnumerateProcessListWithCallbackFunction( + IncrementProcessCounter + ); - if ( debugger_data == NULL ) + if ( process_count == NULL ) { - DEBUG_ERROR( "Debugger data is null" ); + DEBUG_ERROR( "Faield to get process count " ); return STATUS_ABANDONED; } - non_paged_pool_start = debugger_data->MmNonPagedPoolStart; - non_paged_pool_end = debugger_data->MmNonPagedPoolEnd; + DEBUG_LOG( "Proc count: %lx", process_count ); - DEBUG_LOG( "NonPagedPool start: %llx, end %llx", non_paged_pool_start, non_paged_pool_end ); + process_buffer = ExAllocatePool2( POOL_FLAG_NON_PAGED, process_count * 2 * sizeof( UINT64 ), PROCESS_ADDRESS_LIST_TAG ); - WalkKernelPageTables(); + if ( !process_buffer ) + return STATUS_ABANDONED; -/* for ( ; non_paged_pool_start <= non_paged_pool_end; non_paged_pool_start++ ) + WalkKernelPageTables( process_buffer ); + + EnumerateProcessListWithCallbackFunction( + CheckIfProcessAllocationIsInProcessList + ); + + allocation_address = ( PUINT64 )process_buffer; + + DEBUG_LOG( "Allocation addr: %p", allocation_address ); + + for ( INT i = 0; i < process_count; i++ ) { - CHAR current_byte = *( CHAR* )non_paged_pool_start; - DEBUG_LOG( "Current byte: %c", current_byte ); - */ + if ( allocation_address[ i ] == NULL ) + continue; - ExFreePoolWithTag( debugger_data, POOL_DEBUGGER_DATA_TAG ); + /* process has been deallocated yet the pool header hasnt been updated? */ + if ( *( UINT8* )( allocation_address[ i ] + EPROCESS_VIRTUAL_SIZE_OFFSET ) == 0x0 ) + continue; + + DEBUG_ERROR( "INVALID POOL proc OMGGG" ); + } + + DEBUG_LOG( "Finished scaning xd" ); + + __debugbreak(); + + ExFreePoolWithTag( process_buffer, PROCESS_ADDRESS_LIST_TAG ); + process_count = NULL; + + return STATUS_SUCCESS; } \ No newline at end of file diff --git a/driver/pool.h b/driver/pool.h index 8baf637..ebcc4ad 100644 --- a/driver/pool.h +++ b/driver/pool.h @@ -5,6 +5,7 @@ #define POOL_DUMP_BLOCK_TAG 'dump' #define POOL_DEBUGGER_DATA_TAG 'data' +#define PROCESS_ADDRESS_LIST_TAG 'addr' #define PML4_ENTRY_COUNT 512 #define PDPT_ENTRY_COUNT 512 @@ -14,10 +15,18 @@ #define PAGE_BASE_SIZE 0x1000 #define POOL_TAG_SIZE 0x004 +#define PROCESS_OBJECT_ALLOCATION_MARGIN 0x90 + +#define EPROCESS_VIRTUAL_SIZE_OFFSET 0x498 + +/* SIZE_2 = first alloc + 0x10 */ #define WIN_PROCESS_ALLOCATION_SIZE 0xcf0 +#define WIN_PROCESS_ALLOCATION_SIZE_2 0xd00 #define CHUNK_SIZE 16 +NTSTATUS FindUnlinkedProcesses(); + /* creds: https://www.unknowncheats.me/forum/2602838-post2.html */ typedef struct _DBGKD_DEBUG_DATA_HEADER64