/*
* Copyright 2021 David Xanatos, xanasoft.com
*
* Based on the processhacker's CustomSignTool, Copyright 2016 wj32
*
* This program 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.
*
* This program 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 this program. If not, see .
*/
#include
#define WIN32_NO_STATUS
typedef long NTSTATUS;
#include
#include
#include
//#include
#include
#include
#include
#include
__declspec(dllimport) NTSTATUS __stdcall
NtReadFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID Buffer,
IN ULONG Length,
IN PLARGE_INTEGER ByteOffset OPTIONAL,
IN PULONG Key OPTIONAL
);
__declspec(dllimport) NTSTATUS __stdcall
NtWriteFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID Buffer,
IN ULONG Length,
IN PLARGE_INTEGER ByteOffset OPTIONAL,
IN PULONG Key OPTIONAL
);
static UCHAR KphpTrustedPublicKey[] =
{
0x45, 0x43, 0x53, 0x31, 0x20, 0x00, 0x00, 0x00, 0x05, 0x7A, 0x12, 0x5A, 0xF8, 0x54, 0x01, 0x42,
0xDB, 0x19, 0x87, 0xFC, 0xC4, 0xE3, 0xD3, 0x8D, 0x46, 0x7B, 0x74, 0x01, 0x12, 0xFC, 0x78, 0xEB,
0xEF, 0x7F, 0xF6, 0xAF, 0x4D, 0x9A, 0x3A, 0xF6, 0x64, 0x90, 0xDB, 0xE3, 0x48, 0xAB, 0x3E, 0xA7,
0x2F, 0xC1, 0x18, 0x32, 0xBD, 0x23, 0x02, 0x9D, 0x3F, 0xF3, 0x27, 0x86, 0x71, 0x45, 0x26, 0x14,
0x14, 0xF5, 0x19, 0xAA, 0x2D, 0xEE, 0x50, 0x10
};
#define CST_SIGN_ALGORITHM BCRYPT_ECDSA_P256_ALGORITHM
#define CST_SIGN_ALGORITHM_BITS 256
#define CST_HASH_ALGORITHM BCRYPT_SHA256_ALGORITHM
#define CST_BLOB_PRIVATE BCRYPT_ECCPRIVATE_BLOB
#define CST_BLOB_PUBLIC BCRYPT_ECCPUBLIC_BLOB
#define KPH_SIGNATURE_MAX_SIZE (128 * 1024) // 128 kB
#define FILE_BUFFER_SIZE 4096
static NTSTATUS MyCreateFile(_Out_ PHANDLE FileHandle, _In_ PCWSTR FileName, _In_ ACCESS_MASK DesiredAccess,
_In_opt_ ULONG FileAttributes, _In_ ULONG ShareAccess, _In_ ULONG CreateDisposition, _In_ ULONG CreateOptions)
{
UNICODE_STRING uni;
OBJECT_ATTRIBUTES attr;
WCHAR wszBuffer[MAX_PATH];
_snwprintf(wszBuffer, MAX_PATH, L"\\??\\%s", FileName);
RtlInitUnicodeString(&uni, wszBuffer);
InitializeObjectAttributes(&attr, &uni, OBJ_CASE_INSENSITIVE, NULL, 0);
IO_STATUS_BLOCK Iosb;
return NtCreateFile(FileHandle, DesiredAccess, &attr, &Iosb, NULL, FileAttributes, ShareAccess, CreateDisposition, CreateOptions, NULL, 0);
}
NTSTATUS MyReadFile(
_In_ PWSTR FileName,
_In_ ULONG FileSizeLimit,
_Out_ PVOID* Buffer,
_Out_ PULONG FileSize
)
{
NTSTATUS status;
HANDLE fileHandle = INVALID_HANDLE_VALUE;
LARGE_INTEGER fileSize;
PVOID buffer;
IO_STATUS_BLOCK iosb;
if (!NT_SUCCESS(status = MyCreateFile(&fileHandle, FileName, FILE_GENERIC_READ, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE)))
goto CleanupExit;
if (!GetFileSizeEx(fileHandle, &fileSize) || fileSize.QuadPart > FileSizeLimit)
goto CleanupExit;
buffer = malloc((ULONG)fileSize.QuadPart + 1);
if (!NT_SUCCESS(status = NtReadFile(fileHandle, NULL, NULL, NULL, &iosb, buffer, (ULONG)fileSize.QuadPart, NULL, NULL)))
goto CleanupExit;
((char*)buffer)[fileSize.QuadPart] = 0;
*Buffer = buffer;
if(FileSize) *FileSize = (ULONG)fileSize.QuadPart;
CleanupExit:
if(fileHandle != INVALID_HANDLE_VALUE)
NtClose(fileHandle);
return status;
}
NTSTATUS MyWriteFile(
_In_ PWSTR FileName,
_In_ PVOID Buffer,
_In_ ULONG BufferSize
)
{
NTSTATUS status;
HANDLE fileHandle = INVALID_HANDLE_VALUE;
IO_STATUS_BLOCK iosb;
if (!NT_SUCCESS(status = MyCreateFile(&fileHandle, FileName, FILE_GENERIC_WRITE, FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE)))
goto CleanupExit;
if (!NT_SUCCESS(status = NtWriteFile(fileHandle, NULL, NULL, NULL, &iosb, Buffer, BufferSize, NULL, NULL)))
goto CleanupExit;
CleanupExit:
if (fileHandle != INVALID_HANDLE_VALUE)
NtClose(fileHandle);
return status;
}
typedef struct {
BCRYPT_ALG_HANDLE algHandle;
BCRYPT_HASH_HANDLE handle;
PVOID object;
} MY_HASH_OBJ;
static VOID MyFreeHash(MY_HASH_OBJ* pHashObj)
{
if (pHashObj->handle)
BCryptDestroyHash(pHashObj->handle);
if (pHashObj->object)
free(pHashObj->object);
if (pHashObj->algHandle)
BCryptCloseAlgorithmProvider(pHashObj->algHandle, 0);
}
static NTSTATUS MyInitHash(MY_HASH_OBJ* pHashObj)
{
NTSTATUS status;
ULONG hashObjectSize;
ULONG querySize;
memset(pHashObj, 0, sizeof(MY_HASH_OBJ));
if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&pHashObj->algHandle, CST_HASH_ALGORITHM, NULL, 0)))
goto CleanupExit;
if (!NT_SUCCESS(status = BCryptGetProperty(pHashObj->algHandle, BCRYPT_OBJECT_LENGTH, (PUCHAR)&hashObjectSize, sizeof(ULONG), &querySize, 0)))
goto CleanupExit;
pHashObj->object = malloc(hashObjectSize);
if (!pHashObj->object) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto CleanupExit;
}
if (!NT_SUCCESS(status = BCryptCreateHash(pHashObj->algHandle, &pHashObj->handle, (PUCHAR)pHashObj->object, hashObjectSize, NULL, 0, 0)))
goto CleanupExit;
CleanupExit:
if (!NT_SUCCESS(status))
MyFreeHash(pHashObj);
return status;
}
static NTSTATUS MyHashData(MY_HASH_OBJ* pHashObj, PVOID Data, ULONG DataSize)
{
return BCryptHashData(pHashObj->handle, (PUCHAR)Data, DataSize, 0);
}
static NTSTATUS MyFinishHash(MY_HASH_OBJ* pHashObj, PVOID* Hash, PULONG HashSize)
{
NTSTATUS status;
ULONG querySize;
if (!NT_SUCCESS(status = BCryptGetProperty(pHashObj->algHandle, BCRYPT_HASH_LENGTH, (PUCHAR)HashSize, sizeof(ULONG), &querySize, 0)))
goto CleanupExit;
*Hash = malloc(*HashSize);
if (!*Hash) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto CleanupExit;
}
if (!NT_SUCCESS(status = BCryptFinishHash(pHashObj->handle, (PUCHAR)*Hash, *HashSize, 0)))
goto CleanupExit;
return STATUS_SUCCESS;
CleanupExit:
if (*Hash) {
free(*Hash);
*Hash = NULL;
}
return status;
}
NTSTATUS MyHashFile(
_In_ PCWSTR FileName,
_Out_ PVOID* Hash,
_Out_ PULONG HashSize
)
{
NTSTATUS status;
HANDLE fileHandle = INVALID_HANDLE_VALUE;
PVOID buffer = NULL;
IO_STATUS_BLOCK iosb;
MY_HASH_OBJ hashObj;
if (!NT_SUCCESS(status = MyInitHash(&hashObj)))
goto CleanupExit;
if (!NT_SUCCESS(status = MyCreateFile(&fileHandle, FileName, FILE_GENERIC_READ, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE)))
goto CleanupExit;
buffer = malloc(FILE_BUFFER_SIZE);
if (!buffer) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto CleanupExit;
}
while (TRUE)
{
if (!NT_SUCCESS(status = NtReadFile(fileHandle, NULL, NULL, NULL, &iosb, buffer, FILE_BUFFER_SIZE, NULL, NULL)))
{
if (status == STATUS_END_OF_FILE)
break;
goto CleanupExit;
}
if (!NT_SUCCESS(status = MyHashData(&hashObj, buffer, (ULONG)iosb.Information)))
goto CleanupExit;
}
if (!NT_SUCCESS(status = MyFinishHash(&hashObj, Hash, HashSize)))
goto CleanupExit;
CleanupExit:
if(buffer)
free(buffer);
if(fileHandle != INVALID_HANDLE_VALUE)
NtClose(fileHandle);
MyFreeHash(&hashObj);
return status;
}
NTSTATUS MyHashBuffer(
_In_ PVOID pData,
_In_ SIZE_T uSize,
_Out_ PVOID* Hash,
_Out_ PULONG HashSize
)
{
NTSTATUS status;
IO_STATUS_BLOCK iosb;
MY_HASH_OBJ hashObj;
if (!NT_SUCCESS(status = MyInitHash(&hashObj)))
goto CleanupExit;
if (!NT_SUCCESS(status = MyHashData(&hashObj, pData, uSize)))
goto CleanupExit;
if (!NT_SUCCESS(status = MyFinishHash(&hashObj, Hash, HashSize)))
goto CleanupExit;
CleanupExit:
MyFreeHash(&hashObj);
return status;
}
NTSTATUS VerifyHashSignature(
PVOID Hash,
ULONG HashSize,
PVOID Signature,
ULONG SignatureSize
)
{
NTSTATUS status;
BCRYPT_ALG_HANDLE signAlgHandle = NULL;
BCRYPT_KEY_HANDLE keyHandle = NULL;
if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&signAlgHandle, CST_SIGN_ALGORITHM, NULL, 0)))
goto CleanupExit;
if (!NT_SUCCESS(status = BCryptImportKeyPair(signAlgHandle, NULL, CST_BLOB_PUBLIC, &keyHandle, KphpTrustedPublicKey, sizeof(KphpTrustedPublicKey), 0)))
goto CleanupExit;
if (!NT_SUCCESS(status = BCryptVerifySignature(keyHandle, NULL, (PUCHAR)Hash, HashSize, (PUCHAR)Signature, SignatureSize, 0)))
goto CleanupExit;
CleanupExit:
if (keyHandle != NULL)
BCryptDestroyKey(keyHandle);
if (signAlgHandle)
BCryptCloseAlgorithmProvider(signAlgHandle, 0);
return status;
}
NTSTATUS SignHash(
_In_ PVOID Hash,
_In_ ULONG HashSize,
_In_ PVOID PrivKey,
_In_ ULONG PrivKeySize,
_Out_ PVOID* Signature,
_Out_ ULONG* SignatureSize
)
{
NTSTATUS status;
BCRYPT_ALG_HANDLE signAlgHandle = NULL;
BCRYPT_KEY_HANDLE keyHandle = NULL;
// Import the trusted public key.
if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&signAlgHandle, CST_SIGN_ALGORITHM, NULL, 0)))
goto CleanupExit;
if (!NT_SUCCESS(status = BCryptImportKeyPair(signAlgHandle, NULL, CST_BLOB_PRIVATE, &keyHandle, (PUCHAR)PrivKey, PrivKeySize, 0)))
goto CleanupExit;
// Sign the hash.
if (!NT_SUCCESS(status = BCryptSignHash(keyHandle, NULL, (PUCHAR)Hash, HashSize, NULL, 0, SignatureSize, 0)))
goto CleanupExit;
*Signature = malloc(*SignatureSize);
if (!*Signature) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto CleanupExit;
}
if (!NT_SUCCESS(status = BCryptSignHash(keyHandle, NULL, (PUCHAR)Hash, HashSize, (PUCHAR)*Signature, *SignatureSize, SignatureSize, 0)))
goto CleanupExit;
CleanupExit:
if (keyHandle != NULL)
BCryptDestroyKey(keyHandle);
if (signAlgHandle)
BCryptCloseAlgorithmProvider(signAlgHandle, 0);
return status;
}
NTSTATUS VerifyFileSignature(const wchar_t* FilePath)
{
NTSTATUS status;
ULONG hashSize;
PVOID hash = NULL;
ULONG signatureSize;
PVOID signature = NULL;
WCHAR* signatureFileName = NULL;
// Read the signature.
signatureFileName = (WCHAR*)malloc((wcslen(FilePath) + 4 + 1) * sizeof(WCHAR));
if(!signatureFileName) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto CleanupExit;
}
wcscpy(signatureFileName, FilePath);
wcscat(signatureFileName, L".sig");
// Read the signature file.
if (!NT_SUCCESS(status = MyReadFile(signatureFileName, KPH_SIGNATURE_MAX_SIZE, &signature, &signatureSize)))
goto CleanupExit;
// Hash the file.
if (!NT_SUCCESS(status = MyHashFile(FilePath, &hash, &hashSize)))
goto CleanupExit;
// Verify the hash.
if (!NT_SUCCESS(status = VerifyHashSignature((PUCHAR)hash, hashSize, (PUCHAR)signature, signatureSize)))
{
goto CleanupExit;
}
CleanupExit:
if (signature)
free(signature);
if (hash)
free(hash);
if (signatureFileName)
free(signatureFileName);
return status;
}
static VOID CstFailWithStatus(_In_ const wchar_t* Message, _In_ NTSTATUS Status, _In_opt_ ULONG Win32Result)
{
wprintf(L"%s: 0x%08x %u\n", Message, Status, Win32Result);
exit(1);
}
static NTSTATUS CstExportKey(
_In_ BCRYPT_KEY_HANDLE KeyHandle,
_In_ PWSTR BlobType,
_In_ PWSTR FileName,
_In_ PWSTR Description
)
{
NTSTATUS status;
ULONG blobSize;
PVOID blob = NULL;
if (!NT_SUCCESS(status = BCryptExportKey(KeyHandle, NULL, BlobType, NULL, 0, &blobSize, 0)))
goto CleanupExit;
blob = malloc(blobSize);
if (!blob) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto CleanupExit;
}
if (!NT_SUCCESS(status = BCryptExportKey(KeyHandle, NULL, BlobType, (PUCHAR)blob, blobSize, &blobSize, 0)))
goto CleanupExit;
if (!NT_SUCCESS(status = MyWriteFile(FileName, blob, blobSize)))
goto CleanupExit;
CleanupExit:
if (blob) {
RtlSecureZeroMemory(blob, blobSize);
free(blob);
}
return status;
}
NTSTATUS CreateKeyPair(_In_ PCWSTR PrivFile, _In_ PCWSTR PubFile)
{
NTSTATUS status;
BCRYPT_ALG_HANDLE signAlgHandle;
BCRYPT_KEY_HANDLE keyHandle;
if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&signAlgHandle, CST_SIGN_ALGORITHM, NULL, 0)))
CstFailWithStatus(L"Unable to open the signing algorithm provider", status, 0);
if (!NT_SUCCESS(status = BCryptGenerateKeyPair(signAlgHandle, &keyHandle, CST_SIGN_ALGORITHM_BITS, 0)))
CstFailWithStatus(L"Unable to create the key", status, 0);
if (!NT_SUCCESS(status = BCryptFinalizeKeyPair(keyHandle, 0)))
CstFailWithStatus(L"Unable to finalize the key", status, 0);
CstExportKey(keyHandle, CST_BLOB_PRIVATE, PrivFile, L"private key");
CstExportKey(keyHandle, CST_BLOB_PUBLIC, PubFile, L"public key");
BCryptDestroyKey(keyHandle);
BCryptCloseAlgorithmProvider(signAlgHandle, 0);
return status;
}