2025-05-13 19:45:22 +03:00

497 lines
13 KiB
C

/*
* KProcessHacker
*
* Copyright (C) 2016 wj32
*
* This file is part of Process Hacker.
*
* Process Hacker is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Process Hacker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
*/
#include <kph.h>
#define FILE_BUFFER_SIZE (2 * PAGE_SIZE)
#define FILE_MAX_SIZE (32 * 1024 * 1024) // 32 MB
VOID KphpBackoffKey(
__in PKPH_CLIENT Client
);
static UCHAR KphpTrustedPublicKey[] =
{
0x45, 0x43, 0x53, 0x31, 0x20, 0x00, 0x00, 0x00, 0x5f, 0xe8, 0xab, 0xac, 0x01, 0xad, 0x6b, 0x48,
0xfd, 0x84, 0x7f, 0x43, 0x70, 0xb6, 0x57, 0xb0, 0x76, 0xe3, 0x10, 0x07, 0x19, 0xbd, 0x0e, 0xd4,
0x10, 0x5c, 0x1f, 0xfc, 0x40, 0x91, 0xb6, 0xed, 0x94, 0x37, 0x76, 0xb7, 0x86, 0x88, 0xf7, 0x34,
0x12, 0x91, 0xf6, 0x65, 0x23, 0x58, 0xc9, 0xeb, 0x2f, 0xcb, 0x96, 0x13, 0x8f, 0xca, 0x57, 0x7a,
0xd0, 0x7a, 0xbf, 0x22, 0xde, 0xd2, 0x15, 0xfc
};
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, KphHashFile)
#pragma alloc_text(PAGE, KphVerifyFile)
#pragma alloc_text(PAGE, KphVerifyClient)
#pragma alloc_text(PAGE, KpiVerifyClient)
#pragma alloc_text(PAGE, KphGenerateKeysClient)
#pragma alloc_text(PAGE, KphRetrieveKeyViaApc)
#pragma alloc_text(PAGE, KphValidateKey)
#pragma alloc_text(PAGE, KphpBackoffKey)
#endif
NTSTATUS KphHashFile(
__in PUNICODE_STRING FileName,
__out PVOID *Hash,
__out PULONG HashSize
)
{
NTSTATUS status;
BCRYPT_ALG_HANDLE hashAlgHandle = NULL;
ULONG querySize;
ULONG hashObjectSize;
ULONG hashSize;
PVOID hashObject = NULL;
PVOID hash = NULL;
BCRYPT_HASH_HANDLE hashHandle = NULL;
OBJECT_ATTRIBUTES objectAttributes;
IO_STATUS_BLOCK iosb;
HANDLE fileHandle = NULL;
FILE_STANDARD_INFORMATION standardInfo;
ULONG remainingBytes;
ULONG bytesToRead;
PVOID buffer = NULL;
PAGED_CODE();
// Open the hash algorithm and allocate memory for the hash object.
if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&hashAlgHandle, KPH_HASH_ALGORITHM, NULL, 0)))
goto CleanupExit;
if (!NT_SUCCESS(status = BCryptGetProperty(hashAlgHandle, BCRYPT_OBJECT_LENGTH,
(PUCHAR)&hashObjectSize, sizeof(ULONG), &querySize, 0)))
{
goto CleanupExit;
}
if (!NT_SUCCESS(status = BCryptGetProperty(hashAlgHandle, BCRYPT_HASH_LENGTH, (PUCHAR)&hashSize,
sizeof(ULONG), &querySize, 0)))
{
goto CleanupExit;
}
if (!(hashObject = ExAllocatePoolWithTag(PagedPool, hashObjectSize, 'vhpK')))
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto CleanupExit;
}
if (!(hash = ExAllocatePoolWithTag(PagedPool, hashSize, 'vhpK')))
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto CleanupExit;
}
if (!NT_SUCCESS(status = BCryptCreateHash(hashAlgHandle, &hashHandle, hashObject, hashObjectSize,
NULL, 0, 0)))
{
goto CleanupExit;
}
// Open the file and compute the hash.
InitializeObjectAttributes(&objectAttributes, FileName, OBJ_KERNEL_HANDLE, NULL, NULL);
if (!NT_SUCCESS(status = ZwCreateFile(&fileHandle, FILE_GENERIC_READ, &objectAttributes,
&iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN,
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0)))
{
goto CleanupExit;
}
if (!NT_SUCCESS(status = ZwQueryInformationFile(fileHandle, &iosb, &standardInfo,
sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation)))
{
goto CleanupExit;
}
if (standardInfo.EndOfFile.QuadPart <= 0)
{
status = STATUS_UNSUCCESSFUL;
goto CleanupExit;
}
if (standardInfo.EndOfFile.QuadPart > FILE_MAX_SIZE)
{
status = STATUS_FILE_TOO_LARGE;
goto CleanupExit;
}
if (!(buffer = ExAllocatePoolWithTag(PagedPool, FILE_BUFFER_SIZE, 'vhpK')))
{
status = STATUS_INSUFFICIENT_RESOURCES;
goto CleanupExit;
}
remainingBytes = (ULONG)standardInfo.EndOfFile.QuadPart;
while (remainingBytes != 0)
{
bytesToRead = FILE_BUFFER_SIZE;
if (bytesToRead > remainingBytes)
bytesToRead = remainingBytes;
if (!NT_SUCCESS(status = ZwReadFile(fileHandle, NULL, NULL, NULL, &iosb, buffer, bytesToRead,
NULL, NULL)))
{
goto CleanupExit;
}
if ((ULONG)iosb.Information != bytesToRead)
{
status = STATUS_INTERNAL_ERROR;
goto CleanupExit;
}
if (!NT_SUCCESS(status = BCryptHashData(hashHandle, buffer, bytesToRead, 0)))
goto CleanupExit;
remainingBytes -= bytesToRead;
}
if (!NT_SUCCESS(status = BCryptFinishHash(hashHandle, hash, hashSize, 0)))
goto CleanupExit;
if (NT_SUCCESS(status))
{
*Hash = hash;
*HashSize = hashSize;
hash = NULL; // Don't free this in the cleanup section
}
CleanupExit:
if (buffer)
ExFreePoolWithTag(buffer, 'vhpK');
if (fileHandle)
ZwClose(fileHandle);
if (hashHandle)
BCryptDestroyHash(hashHandle);
if (hash)
ExFreePoolWithTag(hash, 'vhpK');
if (hashObject)
ExFreePoolWithTag(hashObject, 'vhpK');
if (hashAlgHandle)
BCryptCloseAlgorithmProvider(hashAlgHandle, 0);
return status;
}
NTSTATUS KphVerifyFile(
__in PUNICODE_STRING FileName,
__in_bcount(SignatureSize) PUCHAR Signature,
__in ULONG SignatureSize
)
{
NTSTATUS status;
BCRYPT_ALG_HANDLE signAlgHandle = NULL;
BCRYPT_KEY_HANDLE keyHandle = NULL;
PVOID hash = NULL;
ULONG hashSize;
PAGED_CODE();
// Import the trusted public key.
if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&signAlgHandle, KPH_SIGN_ALGORITHM, NULL, 0)))
goto CleanupExit;
if (!NT_SUCCESS(status = BCryptImportKeyPair(signAlgHandle, NULL, KPH_BLOB_PUBLIC, &keyHandle,
KphpTrustedPublicKey, sizeof(KphpTrustedPublicKey), 0)))
{
goto CleanupExit;
}
// Hash the file.
if (!NT_SUCCESS(status = KphHashFile(FileName, &hash, &hashSize)))
goto CleanupExit;
// Verify the hash.
if (!NT_SUCCESS(status = BCryptVerifySignature(keyHandle, NULL, hash, hashSize, Signature,
SignatureSize, 0)))
{
goto CleanupExit;
}
CleanupExit:
if (hash)
ExFreePoolWithTag(hash, 'vhpK');
if (keyHandle)
BCryptDestroyKey(keyHandle);
if (signAlgHandle)
BCryptCloseAlgorithmProvider(signAlgHandle, 0);
return status;
}
VOID KphVerifyClient(
__inout PKPH_CLIENT Client,
__in PVOID CodeAddress,
__in_bcount(SignatureSize) PUCHAR Signature,
__in ULONG SignatureSize
)
{
NTSTATUS status;
PUNICODE_STRING processFileName = NULL;
MEMORY_BASIC_INFORMATION memoryBasicInfo;
PUNICODE_STRING mappedFileName = NULL;
PAGED_CODE();
if (Client->VerificationPerformed)
return;
if ((ULONG_PTR)CodeAddress > (ULONG_PTR)MmHighestUserAddress)
{
status = STATUS_ACCESS_VIOLATION;
goto CleanupExit;
}
if (!NT_SUCCESS(status = SeLocateProcessImageName(PsGetCurrentProcess(), &processFileName)))
goto CleanupExit;
if (!NT_SUCCESS(status = ZwQueryVirtualMemory(NtCurrentProcess(), CodeAddress,
MemoryBasicInformation, &memoryBasicInfo, sizeof(MEMORY_BASIC_INFORMATION), NULL)))
{
goto CleanupExit;
}
if (memoryBasicInfo.Type != MEM_IMAGE || memoryBasicInfo.State != MEM_COMMIT)
{
status = STATUS_INVALID_PARAMETER;
goto CleanupExit;
}
if (!NT_SUCCESS(status = KphGetProcessMappedFileName(NtCurrentProcess(), CodeAddress,
&mappedFileName)))
{
goto CleanupExit;
}
if (!RtlEqualUnicodeString(processFileName, mappedFileName, TRUE))
{
status = STATUS_INVALID_PARAMETER;
goto CleanupExit;
}
status = KphVerifyFile(processFileName, Signature, SignatureSize);
CleanupExit:
if (mappedFileName)
ExFreePoolWithTag(mappedFileName, 'ThpK');
if (processFileName)
ExFreePool(processFileName);
ExAcquireFastMutex(&Client->StateMutex);
if (NT_SUCCESS(status))
{
Client->VerifiedProcess = PsGetCurrentProcess();
Client->VerifiedProcessId = PsGetCurrentProcessId();
Client->VerifiedRangeBase = memoryBasicInfo.BaseAddress;
Client->VerifiedRangeSize = memoryBasicInfo.RegionSize;
}
Client->VerificationStatus = status;
MemoryBarrier();
Client->VerificationSucceeded = NT_SUCCESS(status);
Client->VerificationPerformed = TRUE;
ExReleaseFastMutex(&Client->StateMutex);
}
NTSTATUS KpiVerifyClient(
__in PVOID CodeAddress,
__in_bcount(SignatureSize) PUCHAR Signature,
__in ULONG SignatureSize,
__in PKPH_CLIENT Client
)
{
PUCHAR signature;
PAGED_CODE();
__try
{
ProbeForRead(Signature, SignatureSize, 1);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return GetExceptionCode();
}
if (SignatureSize > KPH_SIGNATURE_MAX_SIZE)
return STATUS_INVALID_PARAMETER_3;
signature = ExAllocatePoolWithTag(PagedPool, SignatureSize, 'ThpK');
if (!signature)
return STATUS_INSUFFICIENT_RESOURCES;
__try
{
memcpy(signature, Signature, SignatureSize);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
ExFreePoolWithTag(signature, 'ThpK');
return GetExceptionCode();
}
KphVerifyClient(Client, CodeAddress, Signature, SignatureSize);
ExFreePoolWithTag(signature, 'ThpK');
return Client->VerificationStatus;
}
VOID KphGenerateKeysClient(
__inout PKPH_CLIENT Client
)
{
ULONGLONG interruptTime;
ULONG seed;
KPH_KEY l1Key;
KPH_KEY l2Key;
PAGED_CODE();
if (Client->KeysGenerated)
return;
interruptTime = KeQueryInterruptTime();
seed = (ULONG)(interruptTime >> 32) | (ULONG)interruptTime | PtrToUlong(Client);
l1Key = RtlRandomEx(&seed) | 0x80000000; // Make sure the key is nonzero
do
{
l2Key = RtlRandomEx(&seed) | 0x80000000;
} while (l2Key == l1Key);
ExAcquireFastMutex(&Client->StateMutex);
if (!Client->KeysGenerated)
{
Client->L1Key = l1Key;
Client->L2Key = l2Key;
MemoryBarrier();
Client->KeysGenerated = TRUE;
}
ExReleaseFastMutex(&Client->StateMutex);
}
NTSTATUS KphRetrieveKeyViaApc(
__inout PKPH_CLIENT Client,
__in KPH_KEY_LEVEL KeyLevel,
__inout PIRP Irp
)
{
PIO_APC_ROUTINE userApcRoutine;
KPH_KEY key;
PAGED_CODE();
if (!Client->VerificationSucceeded)
return STATUS_ACCESS_DENIED;
MemoryBarrier();
if (PsGetCurrentProcess() != Client->VerifiedProcess ||
PsGetCurrentProcessId() != Client->VerifiedProcessId)
{
return STATUS_ACCESS_DENIED;
}
userApcRoutine = Irp->Overlay.AsynchronousParameters.UserApcRoutine;
if (!userApcRoutine)
return STATUS_INVALID_PARAMETER;
if ((ULONG_PTR)userApcRoutine < (ULONG_PTR)Client->VerifiedRangeBase ||
(ULONG_PTR)userApcRoutine >= (ULONG_PTR)Client->VerifiedRangeBase + Client->VerifiedRangeSize)
{
return STATUS_ACCESS_DENIED;
}
KphGenerateKeysClient(Client);
switch (KeyLevel)
{
case KphKeyLevel1:
key = Client->L1Key;
break;
case KphKeyLevel2:
key = Client->L2Key;
break;
default:
return STATUS_INVALID_PARAMETER;
}
Irp->Overlay.AsynchronousParameters.UserApcContext = UlongToPtr(key);
return STATUS_SUCCESS;
}
NTSTATUS KphValidateKey(
__in KPH_KEY_LEVEL RequiredKeyLevel,
__in_opt KPH_KEY Key,
__in PKPH_CLIENT Client,
__in KPROCESSOR_MODE AccessMode
)
{
PAGED_CODE();
if (AccessMode == KernelMode)
return STATUS_SUCCESS;
if (Key && Client->VerificationSucceeded && Client->KeysGenerated)
{
MemoryBarrier();
switch (RequiredKeyLevel)
{
case KphKeyLevel1:
if (Key == Client->L1Key || Key == Client->L2Key)
return STATUS_SUCCESS;
else
KphpBackoffKey(Client);
break;
case KphKeyLevel2:
if (Key == Client->L2Key)
return STATUS_SUCCESS;
else
KphpBackoffKey(Client);
break;
default:
return STATUS_INVALID_PARAMETER;
}
}
return STATUS_ACCESS_DENIED;
}
VOID KphpBackoffKey(
__in PKPH_CLIENT Client
)
{
LARGE_INTEGER backoffTime;
PAGED_CODE();
// Serialize to make it impossible for a single client to bypass the backoff by creating
// multiple threads.
ExAcquireFastMutex(&Client->KeyBackoffMutex);
backoffTime.QuadPart = -KPH_KEY_BACKOFF_TIME;
KeDelayExecutionThread(KernelMode, FALSE, &backoffTime);
ExReleaseFastMutex(&Client->KeyBackoffMutex);
}