This commit is contained in:
lhodges1 2023-08-28 19:17:38 +10:00
parent af7aa80a5c
commit 54a0e90405
3 changed files with 165 additions and 70 deletions

View file

@ -5,6 +5,7 @@
#include "callbacks.h" #include "callbacks.h"
#include "hv.h" #include "hv.h"
#include "pool.h"
#include "integrity.h" #include "integrity.h"
@ -115,7 +116,7 @@ NTSTATUS DriverEntry(
NULL, NULL,
NULL, NULL,
NULL, NULL,
WalkKernelPageTables, FindUnlinkedProcesses,
NULL NULL
); );

View file

@ -2,6 +2,8 @@
#include "common.h" #include "common.h"
#include "callbacks.h"
#include <intrin.h> #include <intrin.h>
#define POOL_TAG_LENGTH 4 #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 DRIVERS_POOL_TAG[ POOL_TAG_LENGTH ] = "\x44\x72\x69\x76";
CHAR SYMBOLIC_LINKS_POOL_TAG[ POOL_TAG_LENGTH ] = "\x4C\x69\x6E\x6B"; CHAR SYMBOLIC_LINKS_POOL_TAG[ POOL_TAG_LENGTH ] = "\x4C\x69\x6E\x6B";
PVOID process_buffer = NULL;
ULONG process_count = NULL;
PKDDEBUGGER_DATA64 GetGlobalDebuggerData() PKDDEBUGGER_DATA64 GetGlobalDebuggerData()
{ {
CONTEXT context = { 0 }; CONTEXT context = { 0 };
@ -57,24 +62,57 @@ end:
return debugger_data; 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( VOID ScanPageForKernelObjectAllocation(
_In_ UINT64 PageBase, _In_ UINT64 PageBase,
_In_ ULONG PageSize, _In_ ULONG PageSize,
_In_ LPCSTR ObjectTag _In_ LPCSTR ObjectTag,
_In_ PVOID AddressBuffer
) )
{ {
INT length = 0; INT length = 0;
CHAR current_char; CHAR current_char;
CHAR current_sig_byte; CHAR current_sig_byte;
PPOOL_HEADER pool_header; PPOOL_HEADER pool_header;
PEPROCESS process; PEPROCESS process = NULL;
LPCSTR process_name; LPCSTR process_name;
PUINT64 address_list;
ULONG allocation_size;
if ( !PageBase || !PageSize || !ObjectTag) if ( !PageBase || !PageSize || !ObjectTag)
return; return;
PAGED_CODE();
for ( INT offset = 0; offset <= PageSize - POOL_TAG_LENGTH; offset++ ) for ( INT offset = 0; offset <= PageSize - POOL_TAG_LENGTH; offset++ )
{ {
for ( INT sig_index = 0; sig_index < POOL_TAG_LENGTH + 1; sig_index++ ) 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 * 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. * 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 )
{ {
/* if ( allocation_size == WIN_PROCESS_ALLOCATION_SIZE )
* For ~70% of EPROCESS structures the header layout is as follows: process = process = ( PEPROCESS )( ( UINT64 )pool_header + sizeof( POOL_HEADER ) + 0x70 );
*
* 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
*/
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;
}
/* address_list = ( PUINT64 )AddressBuffer;
* 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 for ( INT i = 0; i < process_count; i++ )
* 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 if ( address_list[ i ] == NULL )
* 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? address_list[ i ] = ( UINT64 )process;
* break;
* 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 );
} }
break; break;
@ -201,7 +235,7 @@ BOOLEAN IsPhysicalAddressInPhysicalMemoryRange(
* and extract the physical address from the value at each virtual address page entry. * and extract the physical address from the value at each virtual address page entry.
*/ */
VOID WalkKernelPageTables() VOID WalkKernelPageTables(PVOID AddressBuffer)
{ {
CR3 cr3; CR3 cr3;
PML4E pml4_base; PML4E pml4_base;
@ -228,12 +262,6 @@ VOID WalkKernelPageTables()
return; 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(); cr3.BitAddress = __readcr3();
physical.QuadPart = cr3.Bits.PhysicalAddress << PAGE_4KB_SHIFT; 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 the page base isnt in a legit region, go next */
if ( IsPhysicalAddressInPhysicalMemoryRange( physical.QuadPart, physical_memory_ranges ) == FALSE ) if ( IsPhysicalAddressInPhysicalMemoryRange( physical.QuadPart, physical_memory_ranges ) == FALSE )
continue; continue;
base_virtual_page = MmGetVirtualForPhysical( physical ); base_virtual_page = MmGetVirtualForPhysical( physical );
/* stupid fucking intellisense error GO AWAY! */ /* stupid fucking intellisense error GO AWAY! */
if ( base_virtual_page == NULL || !MmIsAddressValid( base_virtual_page ) ) 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" ); 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; NTSTATUS status;
PKDDEBUGGER_DATA64 debugger_data = NULL; PUINT64 allocation_address;
UINT64 non_paged_pool_start = NULL;
UINT64 non_paged_pool_end = NULL;
/* must free this */ EnumerateProcessListWithCallbackFunction(
debugger_data = GetGlobalDebuggerData(); 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; return STATUS_ABANDONED;
} }
non_paged_pool_start = debugger_data->MmNonPagedPoolStart; DEBUG_LOG( "Proc count: %lx", process_count );
non_paged_pool_end = debugger_data->MmNonPagedPoolEnd;
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; if ( allocation_address[ i ] == NULL )
DEBUG_LOG( "Current byte: %c", current_byte ); 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;
} }

View file

@ -5,6 +5,7 @@
#define POOL_DUMP_BLOCK_TAG 'dump' #define POOL_DUMP_BLOCK_TAG 'dump'
#define POOL_DEBUGGER_DATA_TAG 'data' #define POOL_DEBUGGER_DATA_TAG 'data'
#define PROCESS_ADDRESS_LIST_TAG 'addr'
#define PML4_ENTRY_COUNT 512 #define PML4_ENTRY_COUNT 512
#define PDPT_ENTRY_COUNT 512 #define PDPT_ENTRY_COUNT 512
@ -14,10 +15,18 @@
#define PAGE_BASE_SIZE 0x1000 #define PAGE_BASE_SIZE 0x1000
#define POOL_TAG_SIZE 0x004 #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 0xcf0
#define WIN_PROCESS_ALLOCATION_SIZE_2 0xd00
#define CHUNK_SIZE 16 #define CHUNK_SIZE 16
NTSTATUS FindUnlinkedProcesses();
/* creds: https://www.unknowncheats.me/forum/2602838-post2.html */ /* creds: https://www.unknowncheats.me/forum/2602838-post2.html */
typedef struct _DBGKD_DEBUG_DATA_HEADER64 typedef struct _DBGKD_DEBUG_DATA_HEADER64