2023-08-22 19:32:25 +02:00
|
|
|
|
#include "integrity.h"
|
|
|
|
|
|
|
|
|
|
#include "common.h"
|
2023-09-01 13:46:31 +02:00
|
|
|
|
#include "driver.h"
|
2023-08-22 19:32:25 +02:00
|
|
|
|
#include "modules.h"
|
|
|
|
|
|
2023-09-01 12:56:27 +02:00
|
|
|
|
#include <bcrypt.h>
|
|
|
|
|
|
2023-08-25 09:38:45 +02:00
|
|
|
|
typedef struct _INTEGRITY_CHECK_HEADER
|
|
|
|
|
{
|
2023-09-05 19:20:21 +02:00
|
|
|
|
INT executable_section_count;
|
|
|
|
|
LONG total_packet_size;
|
2023-08-25 09:38:45 +02:00
|
|
|
|
|
|
|
|
|
}INTEGRITY_CHECK_HEADER, *PINTEGRITY_CHECK_HEADER;
|
|
|
|
|
|
2023-08-23 14:14:20 +02:00
|
|
|
|
/*
|
|
|
|
|
* note: this can be put into its own function wihtout an IRP as argument then it can be used
|
|
|
|
|
* in both the get driver image ioctl handler and the CopyDriverExecvutableRegions func
|
|
|
|
|
*/
|
|
|
|
|
NTSTATUS GetDriverImageSize(
|
2023-09-05 19:20:21 +02:00
|
|
|
|
_In_ PIRP Irp
|
2023-08-23 14:14:20 +02:00
|
|
|
|
)
|
|
|
|
|
{
|
2023-09-05 19:20:21 +02:00
|
|
|
|
NTSTATUS status;
|
|
|
|
|
SYSTEM_MODULES modules = { 0 };
|
|
|
|
|
PRTL_MODULE_EXTENDED_INFO driver_info;
|
|
|
|
|
|
|
|
|
|
status = GetSystemModuleInformation( &modules );
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "GetSystemModuleInformation failed with status %x", status );
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
driver_info = FindSystemModuleByName(
|
|
|
|
|
"driver.sys",
|
|
|
|
|
&modules
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Irp->IoStatus.Information = sizeof( ULONG );
|
|
|
|
|
RtlCopyMemory( Irp->AssociatedIrp.SystemBuffer, &driver_info->ImageSize, sizeof( ULONG ) );
|
|
|
|
|
|
|
|
|
|
if (modules.address )
|
|
|
|
|
ExFreePoolWithTag( modules.address, SYSTEM_MODULES_POOL );
|
|
|
|
|
|
|
|
|
|
return status;
|
2023-08-23 14:14:20 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 17:49:04 +02:00
|
|
|
|
NTSTATUS GetModuleInformationByName(
|
2023-09-05 19:20:21 +02:00
|
|
|
|
_In_ PRTL_MODULE_EXTENDED_INFO ModuleInfo,
|
|
|
|
|
_In_ LPCSTR ModuleName
|
2023-08-22 19:32:25 +02:00
|
|
|
|
)
|
|
|
|
|
{
|
2023-09-05 19:20:21 +02:00
|
|
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
|
SYSTEM_MODULES modules = { 0 };
|
|
|
|
|
PRTL_MODULE_EXTENDED_INFO driver_info;
|
|
|
|
|
|
|
|
|
|
status = GetSystemModuleInformation( &modules );
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "GetSystemModuleInformation failed with status %x", status );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
driver_info = FindSystemModuleByName(
|
|
|
|
|
"driver.sys",
|
|
|
|
|
&modules
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
ModuleInfo->FileNameOffset = driver_info->FileNameOffset;
|
|
|
|
|
ModuleInfo->ImageBase = driver_info->ImageBase;
|
|
|
|
|
ModuleInfo->ImageSize = driver_info->ImageSize;
|
|
|
|
|
|
|
|
|
|
RtlCopyMemory(
|
|
|
|
|
ModuleInfo->FullPathName,
|
|
|
|
|
driver_info->FullPathName,
|
|
|
|
|
sizeof( ModuleInfo->FullPathName )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( modules.address )
|
|
|
|
|
ExFreePoolWithTag( modules.address, SYSTEM_MODULES_POOL );
|
|
|
|
|
|
|
|
|
|
return status;
|
2023-08-28 17:00:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 13:21:49 +02:00
|
|
|
|
NTSTATUS StoreModuleExecutableRegionsInBuffer(
|
2023-09-05 19:20:21 +02:00
|
|
|
|
_In_ PVOID* Buffer,
|
|
|
|
|
_In_ PVOID ModuleBase,
|
|
|
|
|
_In_ SIZE_T ModuleSize,
|
|
|
|
|
_In_ PSIZE_T BytesWritten
|
2023-08-31 13:21:49 +02:00
|
|
|
|
)
|
|
|
|
|
{
|
2023-09-05 19:20:21 +02:00
|
|
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
|
PIMAGE_DOS_HEADER dos_header;
|
|
|
|
|
PLOCAL_NT_HEADER nt_header;
|
|
|
|
|
PIMAGE_SECTION_HEADER section;
|
|
|
|
|
ULONG total_packet_size = 0;
|
|
|
|
|
ULONG num_sections = 0;
|
|
|
|
|
ULONG num_executable_sections = 0;
|
|
|
|
|
UINT64 buffer_base;
|
|
|
|
|
ULONG bytes_returned;
|
|
|
|
|
MM_COPY_ADDRESS address;
|
|
|
|
|
|
|
|
|
|
if ( !ModuleBase || !ModuleSize )
|
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The reason we allocate a buffer to temporarily hold the section data is that
|
|
|
|
|
* we don't know the total size until after we iterate over the sections meaning
|
|
|
|
|
* we cant set Irp->IoStatus.Information to the size of our reponse until we
|
|
|
|
|
* enumerate and count all executable sections for the file.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
*Buffer = ExAllocatePool2( POOL_FLAG_NON_PAGED, ModuleSize + sizeof( INTEGRITY_CHECK_HEADER ), POOL_TAG_INTEGRITY );
|
|
|
|
|
|
|
|
|
|
if ( !*Buffer )
|
|
|
|
|
return STATUS_MEMORY_NOT_ALLOCATED;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Note: Verifier doesn't like it when we map the module so rather then mapping it to our address
|
|
|
|
|
* space we will simply use MmCopyMemory on the module to avoid upsetting verifier :)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
dos_header = ( PIMAGE_DOS_HEADER )ModuleBase;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The IMAGE_DOS_HEADER.e_lfanew stores the offset of the IMAGE_NT_HEADER from the base
|
|
|
|
|
* of the image.
|
|
|
|
|
*/
|
|
|
|
|
nt_header = ( struct _IMAGE_NT_HEADERS64* )( ( UINT64 )ModuleBase + dos_header->e_lfanew );
|
|
|
|
|
|
|
|
|
|
num_sections = nt_header->FileHeader.NumberOfSections;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The IMAGE_FIRST_SECTION macro takes in an IMAGE_NT_HEADER and returns the address of
|
|
|
|
|
* the first section of the PE file.
|
|
|
|
|
*/
|
|
|
|
|
section = IMAGE_FIRST_SECTION( nt_header );
|
|
|
|
|
|
|
|
|
|
buffer_base = ( UINT64 )*Buffer + sizeof( INTEGRITY_CHECK_HEADER );
|
|
|
|
|
|
|
|
|
|
for ( ULONG index = 0; index < num_sections; index++ )
|
|
|
|
|
{
|
|
|
|
|
if ( section->Characteristics & IMAGE_SCN_MEM_EXECUTE )
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Note: MmCopyMemory will fail on discardable sections.
|
|
|
|
|
*/
|
|
|
|
|
address.VirtualAddress = section;
|
|
|
|
|
|
|
|
|
|
status = MmCopyMemory(
|
|
|
|
|
( UINT64 )buffer_base + total_packet_size,
|
|
|
|
|
address,
|
|
|
|
|
sizeof( IMAGE_SECTION_HEADER ),
|
|
|
|
|
MM_COPY_MEMORY_VIRTUAL,
|
|
|
|
|
&bytes_returned
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "MmCopyMemory failed with status %x", status );
|
|
|
|
|
ExFreePoolWithTag( *Buffer, POOL_TAG_INTEGRITY );
|
|
|
|
|
*Buffer = NULL;
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
address.VirtualAddress = ( UINT64 )ModuleBase + section->PointerToRawData;
|
|
|
|
|
|
|
|
|
|
status = MmCopyMemory(
|
|
|
|
|
( UINT64 )buffer_base + total_packet_size + sizeof( IMAGE_SECTION_HEADER ),
|
|
|
|
|
address,
|
|
|
|
|
section->SizeOfRawData,
|
|
|
|
|
MM_COPY_MEMORY_VIRTUAL,
|
|
|
|
|
&bytes_returned
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "MmCopyMemory failed with status %x", status );
|
|
|
|
|
ExFreePoolWithTag( *Buffer, POOL_TAG_INTEGRITY );
|
|
|
|
|
*Buffer = NULL;
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
total_packet_size += section->SizeOfRawData + sizeof( IMAGE_SECTION_HEADER );
|
|
|
|
|
num_executable_sections += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
section++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
INTEGRITY_CHECK_HEADER header = { 0 };
|
|
|
|
|
header.executable_section_count = num_executable_sections;
|
|
|
|
|
header.total_packet_size = total_packet_size + sizeof( INTEGRITY_CHECK_HEADER );
|
|
|
|
|
|
|
|
|
|
RtlCopyMemory(
|
|
|
|
|
*Buffer,
|
|
|
|
|
&header,
|
|
|
|
|
sizeof( INTEGRITY_CHECK_HEADER )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
*BytesWritten = total_packet_size + sizeof( INTEGRITY_CHECK_HEADER );
|
|
|
|
|
|
|
|
|
|
return status;
|
2023-08-31 17:49:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 13:21:49 +02:00
|
|
|
|
NTSTATUS MapDiskImageIntoVirtualAddressSpace(
|
2023-09-05 19:20:21 +02:00
|
|
|
|
_In_ PHANDLE SectionHandle,
|
|
|
|
|
_In_ PVOID* Section,
|
|
|
|
|
_In_ PUNICODE_STRING Path,
|
|
|
|
|
_In_ PSIZE_T Size
|
2023-08-31 13:21:49 +02:00
|
|
|
|
)
|
2023-08-28 17:00:52 +02:00
|
|
|
|
{
|
2023-09-05 19:20:21 +02:00
|
|
|
|
NTSTATUS status;
|
|
|
|
|
HANDLE file_handle;
|
|
|
|
|
OBJECT_ATTRIBUTES object_attributes;
|
|
|
|
|
PIO_STATUS_BLOCK pio_block;
|
|
|
|
|
UNICODE_STRING path;
|
|
|
|
|
|
|
|
|
|
RtlInitUnicodeString( &path, Path->Buffer );
|
|
|
|
|
|
|
|
|
|
InitializeObjectAttributes(
|
|
|
|
|
&object_attributes,
|
|
|
|
|
&path,
|
|
|
|
|
OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
status = ZwOpenFile(
|
|
|
|
|
&file_handle,
|
|
|
|
|
FILE_GENERIC_READ,
|
|
|
|
|
&object_attributes,
|
|
|
|
|
&pio_block,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "ZwOpenFile failed with statsu %x", status );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
object_attributes.ObjectName = NULL;
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "NTSetInformationProcess failed with status %x", status );
|
|
|
|
|
ZwClose( file_handle );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Its important that we set the SEC_IMAGE flag with the PAGE_READONLY
|
|
|
|
|
* flag as we are mapping an executable image.
|
|
|
|
|
*/
|
|
|
|
|
status = ZwCreateSection(
|
|
|
|
|
SectionHandle,
|
|
|
|
|
SECTION_ALL_ACCESS,
|
|
|
|
|
&object_attributes,
|
|
|
|
|
NULL,
|
|
|
|
|
PAGE_READONLY,
|
|
|
|
|
SEC_IMAGE,
|
|
|
|
|
file_handle
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "ZwCreateSection failed with status %x", status );
|
|
|
|
|
ZwClose( file_handle );
|
|
|
|
|
*SectionHandle = NULL;
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Mapping a section with the flag SEC_IMAGE (see function above) tells the os we
|
|
|
|
|
* are mapping an executable image. This then allows the OS to take care of parsing
|
|
|
|
|
* the PE header and dealing with all relocations for us, meaning the mapped image
|
|
|
|
|
* will be identical to the in memory image.
|
|
|
|
|
*/
|
|
|
|
|
status = ZwMapViewOfSection(
|
|
|
|
|
*SectionHandle,
|
|
|
|
|
ZwCurrentProcess(),
|
|
|
|
|
Section,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
Size,
|
|
|
|
|
ViewUnmap,
|
|
|
|
|
MEM_TOP_DOWN,
|
|
|
|
|
PAGE_READONLY
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* It is of utmost importants to mark SectionHandle as null after closing the
|
|
|
|
|
* handle from inside this function since an error has occured. The reason this is
|
|
|
|
|
* so important is because we are not responsible for freeing the function if it succeeds
|
|
|
|
|
* and even if it fails, we still allocate a value to the handle via ZwCreateSection.
|
|
|
|
|
* Meaning when the caller goes to check if the handle is null, it will not be null
|
|
|
|
|
* and will cause a double free.
|
|
|
|
|
*/
|
|
|
|
|
DEBUG_ERROR( "ZwMapViewOfSection failed with status %x", status );
|
|
|
|
|
ZwClose( file_handle );
|
|
|
|
|
ZwClose( *SectionHandle );
|
|
|
|
|
*SectionHandle = NULL;
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ZwClose( file_handle );
|
|
|
|
|
return status;
|
2023-09-01 12:56:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NTSTATUS ComputeHashOfBuffer(
|
2023-09-05 19:20:21 +02:00
|
|
|
|
_In_ PVOID Buffer,
|
|
|
|
|
_In_ ULONG BufferSize,
|
|
|
|
|
_In_ PVOID* HashResult,
|
|
|
|
|
_In_ PULONG HashResultSize
|
2023-09-01 12:56:27 +02:00
|
|
|
|
)
|
|
|
|
|
{
|
2023-09-05 19:20:21 +02:00
|
|
|
|
/*
|
|
|
|
|
* Since the windows documentation for the BCrypt functions contain the worst variable naming scheme
|
|
|
|
|
* in existence, I will try to explain what they do. (for my sake and any readers who also aren't smart
|
|
|
|
|
* enough to understand their otherworldy naming convention)
|
|
|
|
|
*
|
|
|
|
|
* algo_handle: handle to our BCrypt algorithm
|
|
|
|
|
* hash_handle: handle to our BCrypt hash
|
|
|
|
|
* bytes_copied: number of bytes that were copied to the output buffer when using BCryptGetProperty
|
|
|
|
|
* resulting_hash_size: this is the size of the final buffer hash, it should be equal to 32 (sizeof SHA256 hash)
|
|
|
|
|
* hash_object_size: the size of the buffer that will temporarily store our hash object
|
|
|
|
|
* hash_object: pointer to the buffer storing our hash object which is used to hash our buffer
|
|
|
|
|
* resulting_hash: pointer to the buffer that stores the resulting hash of our buffer, this is what we care about
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
NTSTATUS status;
|
|
|
|
|
BCRYPT_ALG_HANDLE algo_handle = NULL;
|
|
|
|
|
BCRYPT_HASH_HANDLE hash_handle = NULL;
|
|
|
|
|
ULONG bytes_copied = 0;
|
|
|
|
|
ULONG resulting_hash_size = 0;
|
|
|
|
|
ULONG hash_object_size = 0;
|
|
|
|
|
PCHAR hash_object = NULL;
|
|
|
|
|
PCHAR resulting_hash = NULL;
|
|
|
|
|
|
|
|
|
|
status = BCryptOpenAlgorithmProvider(
|
|
|
|
|
&algo_handle,
|
|
|
|
|
BCRYPT_SHA256_ALGORITHM,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "BCryptOpenAlogrithmProvider failed with status %x", status );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Request the size of the hash object buffer, this is different then the buffer that
|
|
|
|
|
* will store the resulting hash, instead this will be used to store the hash object
|
|
|
|
|
* used to create the hash.
|
|
|
|
|
*/
|
|
|
|
|
status = BCryptGetProperty(
|
|
|
|
|
algo_handle,
|
|
|
|
|
BCRYPT_OBJECT_LENGTH,
|
|
|
|
|
( PCHAR )&hash_object_size,
|
|
|
|
|
sizeof( ULONG ),
|
|
|
|
|
&bytes_copied,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "BCryptGetProperty failed with status %x", status );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hash_object = ExAllocatePool2( POOL_FLAG_NON_PAGED, hash_object_size, POOL_TAG_INTEGRITY );
|
|
|
|
|
|
|
|
|
|
if ( !hash_object )
|
|
|
|
|
{
|
|
|
|
|
status = STATUS_MEMORY_NOT_ALLOCATED;
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* This call gets the size of the resulting hash, which we will use to allocate the
|
|
|
|
|
* resulting hash buffer.
|
|
|
|
|
*/
|
|
|
|
|
status = BCryptGetProperty(
|
|
|
|
|
algo_handle,
|
|
|
|
|
BCRYPT_HASH_LENGTH,
|
|
|
|
|
( PCHAR )&resulting_hash_size,
|
|
|
|
|
sizeof( ULONG ),
|
|
|
|
|
&bytes_copied,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "BCryptGetProperty failed with status %x", status );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resulting_hash = ExAllocatePool2( POOL_FLAG_NON_PAGED, resulting_hash_size, POOL_TAG_INTEGRITY );
|
|
|
|
|
|
|
|
|
|
if ( !resulting_hash )
|
|
|
|
|
{
|
|
|
|
|
status = STATUS_MEMORY_NOT_ALLOCATED;
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Here we create our hash object and store it in the hash_object buffer.
|
|
|
|
|
*/
|
|
|
|
|
status = BCryptCreateHash(
|
|
|
|
|
algo_handle,
|
|
|
|
|
&hash_handle,
|
|
|
|
|
hash_object,
|
|
|
|
|
hash_object_size,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "BCryptCreateHash failed with status %x", status );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* This function hashes the buffer, but does NOT store it in our resulting buffer yet,
|
|
|
|
|
* we need to call BCryptFinishHash to retrieve the final hash.
|
|
|
|
|
*/
|
|
|
|
|
status = BCryptHashData(
|
|
|
|
|
hash_handle,
|
|
|
|
|
Buffer,
|
|
|
|
|
BufferSize,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "BCryptHashData failed with status %x", status );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* As said in the previous comment, this is where we retrieve the final hash and store
|
|
|
|
|
* it in our output buffer.
|
|
|
|
|
*/
|
|
|
|
|
status = BCryptFinishHash(
|
|
|
|
|
hash_handle,
|
|
|
|
|
resulting_hash,
|
|
|
|
|
resulting_hash_size,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "BCryptFinishHash failed with status %x", status );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*HashResult = resulting_hash;
|
|
|
|
|
*HashResultSize = resulting_hash_size;
|
2023-09-01 12:56:27 +02:00
|
|
|
|
|
|
|
|
|
end:
|
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( algo_handle )
|
|
|
|
|
BCryptCloseAlgorithmProvider( algo_handle, NULL );
|
2023-09-01 12:56:27 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( hash_handle )
|
|
|
|
|
BCryptDestroyHash( hash_handle );
|
2023-09-01 12:56:27 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( hash_object )
|
|
|
|
|
ExFreePoolWithTag( hash_object, POOL_TAG_INTEGRITY );
|
2023-08-31 17:49:04 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
return status;
|
2023-08-31 13:21:49 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 1. map driver to memory
|
|
|
|
|
* 2. store executable sections in buffer
|
|
|
|
|
* 3. do the same with the in-memory module
|
2023-09-01 12:56:27 +02:00
|
|
|
|
* 4. hash both buffers
|
2023-08-31 13:21:49 +02:00
|
|
|
|
* 5. compare
|
|
|
|
|
*/
|
|
|
|
|
NTSTATUS VerifyInMemoryImageVsDiskImage(
|
2023-09-05 19:20:21 +02:00
|
|
|
|
//_In_ PIRP Irp
|
2023-08-31 13:21:49 +02:00
|
|
|
|
)
|
|
|
|
|
{
|
2023-09-05 19:20:21 +02:00
|
|
|
|
NTSTATUS status;
|
|
|
|
|
UNICODE_STRING path = { 0 };
|
|
|
|
|
HANDLE section_handle = NULL;
|
|
|
|
|
PVOID section = NULL;
|
|
|
|
|
SIZE_T section_size = NULL;
|
|
|
|
|
SIZE_T bytes_written = NULL;
|
|
|
|
|
PVOID disk_buffer = NULL;
|
|
|
|
|
PVOID in_memory_buffer = NULL;
|
|
|
|
|
RTL_MODULE_EXTENDED_INFO module_info = { 0 };
|
|
|
|
|
UINT64 disk_base = NULL;
|
|
|
|
|
UINT64 memory_base = NULL;
|
|
|
|
|
PIMAGE_SECTION_HEADER disk_text_header = NULL;
|
|
|
|
|
PIMAGE_SECTION_HEADER memory_text_header = NULL;
|
|
|
|
|
PVOID disk_text_hash = NULL;
|
|
|
|
|
PVOID memory_text_hash = NULL;
|
|
|
|
|
ULONG disk_text_hash_size = NULL;
|
|
|
|
|
ULONG memory_text_hash_size = NULL;
|
|
|
|
|
SIZE_T result = NULL;
|
|
|
|
|
|
|
|
|
|
GetDriverPath( &path );
|
|
|
|
|
|
|
|
|
|
status = MapDiskImageIntoVirtualAddressSpace(
|
|
|
|
|
§ion_handle,
|
|
|
|
|
§ion,
|
|
|
|
|
&path,
|
|
|
|
|
§ion_size
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "MapDiskImageIntoVirtualAddressSpace failed with status %x", status );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status = StoreModuleExecutableRegionsInBuffer(
|
|
|
|
|
&disk_buffer,
|
|
|
|
|
section,
|
|
|
|
|
section_size,
|
|
|
|
|
&bytes_written
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "StoreModuleExecutableRegionsInBuffer failed with status %x", status );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Parse the in-memory module
|
|
|
|
|
*/
|
|
|
|
|
status = GetModuleInformationByName(
|
|
|
|
|
&module_info,
|
|
|
|
|
"driver.sys"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "GetModuleInformationByName failed with status %x", status );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status = StoreModuleExecutableRegionsInBuffer(
|
|
|
|
|
&in_memory_buffer,
|
|
|
|
|
module_info.ImageBase,
|
|
|
|
|
module_info.ImageSize,
|
|
|
|
|
&bytes_written
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "StoreModuleExecutableRegionsInBuffe failed with status %x", status );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
disk_base = ( UINT64 )( ( UINT64 )disk_buffer + sizeof( INTEGRITY_CHECK_HEADER ) + sizeof( IMAGE_SECTION_HEADER ) );
|
|
|
|
|
memory_base = ( UINT64 )( ( UINT64 )in_memory_buffer + sizeof( INTEGRITY_CHECK_HEADER ) + sizeof( IMAGE_SECTION_HEADER ) );
|
|
|
|
|
|
|
|
|
|
disk_text_header = ( PIMAGE_SECTION_HEADER )( ( UINT64 )disk_buffer + sizeof( INTEGRITY_CHECK_HEADER ) );
|
|
|
|
|
memory_text_header = ( PIMAGE_SECTION_HEADER )( ( UINT64 )in_memory_buffer + sizeof( INTEGRITY_CHECK_HEADER ) );
|
|
|
|
|
|
|
|
|
|
if ( !disk_base || !memory_base || !disk_buffer || !in_memory_buffer )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "buffers are null lmao" );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( disk_text_header->SizeOfRawData != memory_text_header->SizeOfRawData )
|
|
|
|
|
{
|
|
|
|
|
/* report or bug check etc. */
|
|
|
|
|
DEBUG_LOG( "Executable section size differs, LOL" );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status = ComputeHashOfBuffer(
|
|
|
|
|
disk_base,
|
|
|
|
|
disk_text_header->SizeOfRawData,
|
|
|
|
|
&disk_text_hash,
|
|
|
|
|
&disk_text_hash_size
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "ComputeHashOfBuffer failed with status %x", status );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status = ComputeHashOfBuffer(
|
|
|
|
|
memory_base,
|
|
|
|
|
memory_text_header->SizeOfRawData,
|
|
|
|
|
&memory_text_hash,
|
|
|
|
|
&memory_text_hash_size
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "ComputeHashOfBuffer failed with status %x", status );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( memory_text_hash_size != disk_text_hash_size )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "Error with the hash algorithm, hash sizes are different." );
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = RtlCompareMemory(
|
|
|
|
|
memory_text_hash,
|
|
|
|
|
disk_text_hash,
|
|
|
|
|
memory_text_hash_size
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (result != memory_text_hash_size)
|
|
|
|
|
{
|
|
|
|
|
/* report etc. bug check etc. */
|
|
|
|
|
DEBUG_ERROR( "Text sections are different from each other!!");
|
|
|
|
|
//TerminateProtectedProcessOnViolation();
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DEBUG_LOG( "Text sections are fine, integrity check complete." );
|
2023-09-01 12:56:27 +02:00
|
|
|
|
|
|
|
|
|
end:
|
2023-08-31 13:21:49 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( section_handle != NULL )
|
|
|
|
|
ZwClose( section_handle );
|
2023-08-31 13:21:49 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( section )
|
|
|
|
|
ZwUnmapViewOfSection( ZwCurrentProcess(), section );
|
2023-09-01 12:56:27 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( disk_buffer )
|
|
|
|
|
ExFreePoolWithTag( disk_buffer, POOL_TAG_INTEGRITY );
|
2023-08-31 17:49:04 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( in_memory_buffer )
|
|
|
|
|
ExFreePoolWithTag( in_memory_buffer, POOL_TAG_INTEGRITY );
|
2023-09-01 12:56:27 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( memory_text_hash )
|
|
|
|
|
ExFreePoolWithTag( memory_text_hash, POOL_TAG_INTEGRITY );
|
2023-09-01 12:56:27 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( disk_text_hash )
|
|
|
|
|
ExFreePoolWithTag( disk_text_hash, POOL_TAG_INTEGRITY );
|
2023-09-01 12:56:27 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
return status;
|
2023-08-31 18:42:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NTSTATUS RetrieveInMemoryModuleExecutableSections(
|
2023-09-05 19:20:21 +02:00
|
|
|
|
_In_ PIRP Irp
|
2023-08-31 18:42:38 +02:00
|
|
|
|
)
|
|
|
|
|
{
|
2023-09-05 19:20:21 +02:00
|
|
|
|
NTSTATUS status;
|
|
|
|
|
SIZE_T bytes_written = NULL;
|
|
|
|
|
PVOID buffer = NULL;
|
|
|
|
|
RTL_MODULE_EXTENDED_INFO module_info = { 0 };
|
|
|
|
|
|
|
|
|
|
status = GetModuleInformationByName(
|
|
|
|
|
&module_info,
|
|
|
|
|
"driver.sys"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "GetModuleInformationByName failed with status %x", status );
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status = StoreModuleExecutableRegionsInBuffer(
|
|
|
|
|
&buffer,
|
|
|
|
|
module_info.ImageBase,
|
|
|
|
|
module_info.ImageSize,
|
|
|
|
|
&bytes_written
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "StoreModuleExecutableRegionsInBuffe failed with status %x", status );
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Irp->IoStatus.Information = bytes_written;
|
|
|
|
|
|
|
|
|
|
RtlCopyMemory(
|
|
|
|
|
Irp->AssociatedIrp.SystemBuffer,
|
|
|
|
|
buffer,
|
|
|
|
|
bytes_written
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( buffer )
|
|
|
|
|
ExFreePoolWithTag( buffer, POOL_TAG_INTEGRITY );
|
|
|
|
|
|
|
|
|
|
return status;
|
2023-09-03 19:33:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-04 15:36:26 +02:00
|
|
|
|
/*
|
|
|
|
|
* From line 727 in the SMBIOS Specification:
|
|
|
|
|
*
|
|
|
|
|
* 727 <EFBFBD> Each structure shall be terminated by a double-null (0000h), either directly following the
|
|
|
|
|
* 728 formatted area (if no strings are present) or directly following the last string. This includes
|
|
|
|
|
* 729 system- and OEM-specific structures and allows upper-level software to easily traverse the
|
|
|
|
|
* 730 structure table. (See structure-termination examples later in this clause.)
|
|
|
|
|
*
|
2023-09-05 11:16:32 +02:00
|
|
|
|
* TLDR is that if the first two characters proceeding the structure are null terminators, then there are no strings,
|
2023-09-04 15:36:26 +02:00
|
|
|
|
* otherwise to find the end of the string section simply iterate until there is a double null terminator.
|
|
|
|
|
*
|
|
|
|
|
* source: https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
|
|
|
|
|
*/
|
|
|
|
|
VOID GetNextSMBIOSStructureInTable(
|
2023-09-05 19:20:21 +02:00
|
|
|
|
_In_ PSMBIOS_TABLE_HEADER* CurrentStructure
|
2023-09-04 15:36:26 +02:00
|
|
|
|
)
|
|
|
|
|
{
|
2023-09-05 19:20:21 +02:00
|
|
|
|
PCHAR string_section_start = ( PCHAR )( ( UINT64 )*CurrentStructure + ( *CurrentStructure )->Length );
|
|
|
|
|
PCHAR current_char_in_strings = string_section_start;
|
|
|
|
|
PCHAR next_char_in_strings = string_section_start + 1;
|
|
|
|
|
|
|
|
|
|
for ( ;; )
|
|
|
|
|
{
|
|
|
|
|
if ( *current_char_in_strings == NULL_TERMINATOR && *next_char_in_strings == NULL_TERMINATOR )
|
|
|
|
|
{
|
|
|
|
|
*CurrentStructure = ( PSMBIOS_TABLE_HEADER )( ( UINT64 )next_char_in_strings + 1 );
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
current_char_in_strings++;
|
|
|
|
|
next_char_in_strings++;
|
|
|
|
|
}
|
2023-09-04 15:36:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-04 17:00:36 +02:00
|
|
|
|
/*
|
|
|
|
|
* Remember that the string index does not start from the beginning of the struct. For example, lets take
|
|
|
|
|
* RAW_SMBIOS_TABLE_02: the first string is NOT "Type" at index 0, the first string is Manufacturer. So if we
|
|
|
|
|
* want to find the SerialNumber, the string index would be 4, as the previous 3 values (after the header) are
|
|
|
|
|
* all strings. So remember, the index is into the number of strings that exist for the given table, NOT the
|
|
|
|
|
* size of the structure or a values index into the struct.
|
|
|
|
|
*
|
|
|
|
|
* Here we count the number of strings by incrementing the string_count each time we pass a null terminator
|
|
|
|
|
* so we know when we're at the beginning of the target string.
|
|
|
|
|
*/
|
2023-09-04 15:36:26 +02:00
|
|
|
|
NTSTATUS GetStringAtIndexFromSMBIOSTable(
|
2023-09-05 19:20:21 +02:00
|
|
|
|
_In_ PSMBIOS_TABLE_HEADER Table,
|
|
|
|
|
_In_ INT Index,
|
|
|
|
|
_In_ PVOID Buffer,
|
|
|
|
|
_In_ SIZE_T BufferSize
|
2023-09-04 15:36:26 +02:00
|
|
|
|
)
|
|
|
|
|
{
|
2023-09-05 19:20:21 +02:00
|
|
|
|
INT current_string_char_index = 0;
|
|
|
|
|
INT string_count = 0;
|
|
|
|
|
PCHAR current_string_char = ( PCHAR )( ( UINT64 )Table + Table->Length );
|
|
|
|
|
PCHAR next_string_char = current_string_char + 1;
|
|
|
|
|
|
|
|
|
|
for ( ;; )
|
|
|
|
|
{
|
|
|
|
|
if ( *current_string_char == NULL_TERMINATOR && *next_string_char == NULL_TERMINATOR )
|
|
|
|
|
return STATUS_NOT_FOUND;
|
|
|
|
|
|
|
|
|
|
if ( current_string_char_index >= BufferSize )
|
|
|
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
|
|
|
|
|
|
|
|
if ( string_count + 1 == Index )
|
|
|
|
|
{
|
|
|
|
|
if ( *current_string_char == NULL_TERMINATOR )
|
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
|
|
|
|
|
|
RtlCopyMemory( ( UINT64 )Buffer + current_string_char_index, current_string_char, sizeof( CHAR ) );
|
|
|
|
|
current_string_char_index++;
|
|
|
|
|
goto increment;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( *current_string_char == NULL_TERMINATOR )
|
|
|
|
|
{
|
|
|
|
|
current_string_char_index = 0;
|
|
|
|
|
string_count++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
increment:
|
|
|
|
|
|
|
|
|
|
current_string_char++;
|
|
|
|
|
next_string_char++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return STATUS_NOT_FOUND;
|
2023-09-04 15:36:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NTSTATUS ParseSMBIOSTable(
|
2023-09-05 19:20:21 +02:00
|
|
|
|
_In_ PVOID ConfigMotherboardSerialNumber,
|
2023-09-06 17:33:08 +02:00
|
|
|
|
_In_ SIZE_T ConfigMotherboardSerialNumberMaxSize
|
2023-09-04 15:36:26 +02:00
|
|
|
|
)
|
|
|
|
|
{
|
2023-09-05 19:20:21 +02:00
|
|
|
|
NTSTATUS status;
|
|
|
|
|
PVOID firmware_table_buffer;
|
|
|
|
|
ULONG firmware_table_buffer_size = NULL;
|
|
|
|
|
ULONG bytes_returned;
|
|
|
|
|
PRAW_SMBIOS_DATA smbios_data;
|
|
|
|
|
PSMBIOS_TABLE_HEADER smbios_table_header;
|
|
|
|
|
PRAW_SMBIOS_TABLE_01 smbios_baseboard_information;
|
|
|
|
|
|
|
|
|
|
status = ExGetSystemFirmwareTable(
|
|
|
|
|
SMBIOS_TABLE,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
&firmware_table_buffer_size
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Because we pass a null buffer here, the NTSTATUS result will be a BUFFER_TOO_SMALL error, so to validate
|
|
|
|
|
* this function call we check the return bytes returned (which indicate required buffer size) is above 0.
|
|
|
|
|
*/
|
|
|
|
|
if ( firmware_table_buffer_size == NULL )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "ExGetSystemFirmwareTable call 1 failed to get required buffer size." );
|
|
|
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
firmware_table_buffer = ExAllocatePool2( POOL_FLAG_NON_PAGED, firmware_table_buffer_size, POOL_TAG_INTEGRITY );
|
|
|
|
|
|
|
|
|
|
if ( !firmware_table_buffer )
|
|
|
|
|
return STATUS_MEMORY_NOT_ALLOCATED;
|
|
|
|
|
|
|
|
|
|
status = ExGetSystemFirmwareTable(
|
|
|
|
|
SMBIOS_TABLE,
|
|
|
|
|
NULL,
|
|
|
|
|
firmware_table_buffer,
|
|
|
|
|
firmware_table_buffer_size,
|
|
|
|
|
&bytes_returned
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "ExGetSystemFirmwareTable call 2 failed with status %x", status );
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
smbios_data = ( PRAW_SMBIOS_DATA )firmware_table_buffer;
|
|
|
|
|
smbios_table_header = ( PSMBIOS_TABLE_HEADER )(&smbios_data->SMBIOSTableData[0] );
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The System Information table is equal to Type == 2 and contains the serial number of the motherboard
|
|
|
|
|
* in the computer among various other things.
|
|
|
|
|
*
|
|
|
|
|
* source: https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf line 823
|
|
|
|
|
*/
|
|
|
|
|
while ( smbios_table_header->Type != VMWARE_SMBIOS_TABLE )
|
|
|
|
|
GetNextSMBIOSStructureInTable( &smbios_table_header );
|
|
|
|
|
|
|
|
|
|
status = GetStringAtIndexFromSMBIOSTable(
|
|
|
|
|
smbios_table_header,
|
|
|
|
|
VMWARE_SMBIOS_TABLE_INDEX,
|
|
|
|
|
ConfigMotherboardSerialNumber,
|
2023-09-06 17:33:08 +02:00
|
|
|
|
ConfigMotherboardSerialNumberMaxSize
|
2023-09-05 19:20:21 +02:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "GetStringAtIndexFromSMBIOSTable failed with status %x", status );
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
2023-09-04 15:36:26 +02:00
|
|
|
|
|
|
|
|
|
end:
|
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( firmware_table_buffer )
|
|
|
|
|
ExFreePoolWithTag( firmware_table_buffer, POOL_TAG_INTEGRITY );
|
2023-09-04 15:36:26 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
return status;
|
2023-09-04 15:36:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-09-04 17:56:28 +02:00
|
|
|
|
/*
|
2023-09-05 11:16:32 +02:00
|
|
|
|
* Because the infrastructure has already been setup to validate modules in the driver, that
|
|
|
|
|
* is how I will validate the usermode modules as well. Another reason is that the win32 api
|
|
|
|
|
* makes it very easy to take a snapshot of the modules and enumerate them with easy to use
|
|
|
|
|
* functions and macros.
|
|
|
|
|
*
|
|
|
|
|
* 1. Take a snapshot of the modules in the process from our dll
|
|
|
|
|
* 2. pass the image base, image size and the image path to our driver via an IRP
|
|
|
|
|
* 3. from our driver, to first verify the in memory module, attach to our protected process
|
|
|
|
|
* and using the base + size simply use StoreModuleExecutableRegionsInBuffer()
|
|
|
|
|
* 4. Next we use the path to map the image on disk into memory, and pass the image to
|
|
|
|
|
* StoreModuleExecutableRegionsInBuffer() just as we did before.
|
|
|
|
|
* 5. With the 2 buffers that contain both images executable regions, we hash them and compare
|
|
|
|
|
* for anomalies.
|
2023-09-04 17:56:28 +02:00
|
|
|
|
*/
|
2023-09-05 11:16:32 +02:00
|
|
|
|
NTSTATUS ValidateProcessLoadedModule(
|
2023-09-05 19:20:21 +02:00
|
|
|
|
_In_ PIRP Irp
|
2023-09-05 11:16:32 +02:00
|
|
|
|
)
|
2023-09-04 17:00:36 +02:00
|
|
|
|
{
|
2023-09-05 19:20:21 +02:00
|
|
|
|
NTSTATUS status;
|
|
|
|
|
BOOLEAN bstatus;
|
|
|
|
|
PROCESS_MODULE_VALIDATION_RESULT validation_result;
|
|
|
|
|
PPROCESS_MODULE_INFORMATION module_info;
|
|
|
|
|
PKPROCESS process;
|
|
|
|
|
KAPC_STATE apc_state;
|
|
|
|
|
PVOID in_memory_buffer = NULL;
|
|
|
|
|
PVOID disk_buffer = NULL;
|
|
|
|
|
PVOID in_memory_hash = NULL;
|
|
|
|
|
PVOID disk_hash = NULL;
|
|
|
|
|
ULONG in_memory_hash_size = NULL;
|
|
|
|
|
ULONG disk_hash_size = NULL;
|
|
|
|
|
SIZE_T bytes_written = NULL;
|
|
|
|
|
UNICODE_STRING module_path;
|
|
|
|
|
HANDLE section_handle = NULL;
|
|
|
|
|
PVOID section = NULL;
|
|
|
|
|
ULONG section_size = NULL;
|
|
|
|
|
|
|
|
|
|
module_info = ( PPROCESS_MODULE_INFORMATION )Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
|
|
|
|
|
|
GetProtectedProcessEProcess( &process );
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Attach because the offsets given are from the process' context.
|
|
|
|
|
*/
|
|
|
|
|
KeStackAttachProcess( process, &apc_state );
|
|
|
|
|
|
|
|
|
|
status = StoreModuleExecutableRegionsInBuffer(
|
|
|
|
|
&in_memory_buffer,
|
|
|
|
|
module_info->module_base,
|
|
|
|
|
module_info->module_size,
|
|
|
|
|
&bytes_written
|
|
|
|
|
);
|
|
|
|
|
|
2023-09-07 09:21:00 +02:00
|
|
|
|
KeUnstackDetachProcess( &apc_state );
|
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "StoreModuleExecutableRegionsInBuffer failed with status %x", status );
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status = ComputeHashOfBuffer(
|
|
|
|
|
in_memory_buffer,
|
|
|
|
|
bytes_written,
|
|
|
|
|
&in_memory_hash,
|
|
|
|
|
&in_memory_hash_size
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "ComputeHashOfBuffer failed with status %x:", status );
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RtlInitUnicodeString( &module_path, &module_info->module_path );
|
|
|
|
|
|
|
|
|
|
status = MapDiskImageIntoVirtualAddressSpace(
|
|
|
|
|
§ion_handle,
|
|
|
|
|
§ion,
|
|
|
|
|
&module_path,
|
|
|
|
|
§ion_size
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "MapDiskImageIntoVirtualAddressSpace failed with status %x", status );
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status = StoreModuleExecutableRegionsInBuffer(
|
|
|
|
|
&disk_buffer,
|
|
|
|
|
section,
|
|
|
|
|
section_size,
|
|
|
|
|
&bytes_written
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "StoreModuleExecutableRegionsInbuffer 2 failed with status %x", status );
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status = ComputeHashOfBuffer(
|
|
|
|
|
disk_buffer,
|
|
|
|
|
bytes_written,
|
|
|
|
|
&disk_hash,
|
|
|
|
|
&disk_hash_size
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "ComputeHashOfBuffer 2 failed with status %x", status );
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !in_memory_hash || !disk_hash )
|
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
|
|
bstatus = RtlEqualMemory( in_memory_hash, disk_hash, in_memory_hash_size );
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Because each module is passed per IRP we don't need to send any reports
|
|
|
|
|
* to the queue we can simply pass it back to usermode via the same IRP.
|
|
|
|
|
* We also don't need to send any module information since usermode has everything
|
|
|
|
|
* needed to file the report.
|
|
|
|
|
*/
|
|
|
|
|
validation_result.is_module_valid = bstatus;
|
|
|
|
|
|
|
|
|
|
Irp->IoStatus.Information = sizeof( PROCESS_MODULE_VALIDATION_RESULT );
|
|
|
|
|
|
|
|
|
|
RtlCopyMemory(
|
|
|
|
|
Irp->AssociatedIrp.SystemBuffer,
|
|
|
|
|
&validation_result,
|
|
|
|
|
sizeof( PROCESS_MODULE_VALIDATION_RESULT )
|
|
|
|
|
);
|
2023-09-04 17:00:36 +02:00
|
|
|
|
|
|
|
|
|
end:
|
2023-09-05 19:20:21 +02:00
|
|
|
|
|
|
|
|
|
if ( section_handle != NULL )
|
|
|
|
|
ZwClose( section_handle );
|
2023-09-05 18:04:06 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( section )
|
|
|
|
|
ZwUnmapViewOfSection( ZwCurrentProcess(), section );
|
2023-09-04 17:00:36 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( in_memory_buffer )
|
|
|
|
|
ExFreePoolWithTag( in_memory_buffer, POOL_TAG_INTEGRITY );
|
2023-09-05 11:16:32 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( in_memory_hash )
|
|
|
|
|
ExFreePoolWithTag( in_memory_hash, POOL_TAG_INTEGRITY );
|
2023-09-04 17:00:36 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( disk_buffer )
|
|
|
|
|
ExFreePoolWithTag( disk_buffer, POOL_TAG_INTEGRITY );
|
2023-09-05 18:04:06 +02:00
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
if ( disk_hash )
|
|
|
|
|
ExFreePoolWithTag( disk_hash, POOL_TAG_INTEGRITY );
|
2023-09-05 18:04:06 +02:00
|
|
|
|
|
2023-09-06 17:33:08 +02:00
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-06 17:44:57 +02:00
|
|
|
|
/*
|
|
|
|
|
* TODO: Query PhysicalDrive%n to get the serial numbers for all harddrives, can use the command
|
|
|
|
|
* "wmic diskdrive" check in console.
|
|
|
|
|
*/
|
2023-09-06 17:33:08 +02:00
|
|
|
|
NTSTATUS GetHardDiskDriveSerialNumber(
|
|
|
|
|
_In_ PVOID ConfigDrive0Serial,
|
|
|
|
|
_In_ SIZE_T ConfigDrive0MaxSize
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
NTSTATUS status;
|
|
|
|
|
HANDLE handle;
|
|
|
|
|
OBJECT_ATTRIBUTES attributes;
|
|
|
|
|
IO_STATUS_BLOCK status_block;
|
|
|
|
|
STORAGE_PROPERTY_QUERY storage_property = { 0 };
|
|
|
|
|
STORAGE_DESCRIPTOR_HEADER storage_descriptor_header = { 0 };
|
|
|
|
|
PSTORAGE_DEVICE_DESCRIPTOR device_descriptor = NULL;
|
|
|
|
|
UNICODE_STRING physical_drive_path;
|
|
|
|
|
PCHAR serial_number = NULL;
|
|
|
|
|
SIZE_T serial_length = NULL;
|
|
|
|
|
|
|
|
|
|
RtlInitUnicodeString( &physical_drive_path, L"\\DosDevices\\PhysicalDrive0" );
|
|
|
|
|
|
|
|
|
|
InitializeObjectAttributes(
|
|
|
|
|
&attributes,
|
|
|
|
|
&physical_drive_path,
|
|
|
|
|
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
|
|
|
|
|
2023-09-06 17:44:57 +02:00
|
|
|
|
status = ZwOpenFile(
|
2023-09-06 17:33:08 +02:00
|
|
|
|
&handle,
|
|
|
|
|
GENERIC_READ,
|
|
|
|
|
&attributes,
|
|
|
|
|
&status_block,
|
|
|
|
|
NULL,
|
2023-09-06 17:44:57 +02:00
|
|
|
|
NULL
|
2023-09-06 17:33:08 +02:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
2023-09-06 17:44:57 +02:00
|
|
|
|
DEBUG_LOG( "ZwOpenFile on PhysicalDrive0 failed with status %x", status);
|
2023-09-06 17:33:08 +02:00
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
storage_property.PropertyId = StorageDeviceProperty;
|
|
|
|
|
storage_property.QueryType = PropertyStandardQuery;
|
|
|
|
|
|
|
|
|
|
status = ZwDeviceIoControlFile(
|
|
|
|
|
handle,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
&status_block,
|
|
|
|
|
IOCTL_STORAGE_QUERY_PROPERTY,
|
|
|
|
|
&storage_property,
|
|
|
|
|
sizeof( STORAGE_PROPERTY_QUERY ),
|
|
|
|
|
&storage_descriptor_header,
|
|
|
|
|
sizeof( STORAGE_DESCRIPTOR_HEADER )
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_LOG( "ZwDeviceIoControlFile first call failed with status %x", status );
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
device_descriptor = ExAllocatePool2( POOL_FLAG_NON_PAGED, storage_descriptor_header.Size, POOL_TAG_INTEGRITY );
|
|
|
|
|
|
|
|
|
|
if ( !device_descriptor )
|
|
|
|
|
{
|
|
|
|
|
status = STATUS_MEMORY_NOT_ALLOCATED;
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
status = ZwDeviceIoControlFile(
|
|
|
|
|
handle,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
NULL,
|
|
|
|
|
&status_block,
|
|
|
|
|
IOCTL_STORAGE_QUERY_PROPERTY,
|
|
|
|
|
&storage_property,
|
|
|
|
|
sizeof( STORAGE_PROPERTY_QUERY ),
|
|
|
|
|
device_descriptor,
|
|
|
|
|
storage_descriptor_header.Size
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if ( !NT_SUCCESS( status ) )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_LOG( "ZwDeviceIoControlFile second call failed with status %x", status );
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( device_descriptor->SerialNumberOffset > 0 )
|
|
|
|
|
{
|
|
|
|
|
serial_number = ( PCHAR )( ( UINT64 )device_descriptor + device_descriptor->SerialNumberOffset );
|
|
|
|
|
serial_length = strnlen_s( serial_number, DEVICE_DRIVE_0_SERIAL_CODE_LENGTH ) + 1;
|
|
|
|
|
|
|
|
|
|
if ( serial_length > ConfigDrive0MaxSize )
|
|
|
|
|
{
|
|
|
|
|
DEBUG_ERROR( "Serial length is greater then config drive 0 buffer size" );
|
|
|
|
|
status = STATUS_BUFFER_TOO_SMALL;
|
|
|
|
|
goto end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RtlCopyMemory(
|
|
|
|
|
ConfigDrive0Serial,
|
|
|
|
|
serial_number,
|
|
|
|
|
serial_length
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
end:
|
|
|
|
|
|
|
|
|
|
if ( handle )
|
|
|
|
|
ZwClose( handle );
|
|
|
|
|
|
|
|
|
|
if ( device_descriptor )
|
|
|
|
|
ExFreePoolWithTag( device_descriptor, POOL_TAG_INTEGRITY );
|
|
|
|
|
|
2023-09-05 19:20:21 +02:00
|
|
|
|
return status;
|
2023-09-04 17:00:36 +02:00
|
|
|
|
}
|