1.背景
对于常规应用程序来说,在应用层可以使用 WinVerifyTrust, 在驱动层使用常规的 API无法使用,自己分析数据又太麻烦。
但在内核中 ci.dll 包装了数据签名验证相关的功能,我们可以使用该 dll 来实现我们的数字签名验证。
详细的分析见《内核中的代码完整性:深入分析ci.dll》。下面直接上相关代码。
2.相关代码
原代码地址为 https://github.com/Ido-Moshe-Github/CiDllDemo。这里作了稍微的修改以及添加一些打印信息。
2.1 ci.h
#pragma once
#include <wdm.h>
#include <minwindef.h>
#if DBG
#define KDPRINT(projectName, format, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,\
projectName "::【" __FUNCTION__ "】" ##format, \
##__VA_ARGS__ )
#else
#define KDPRINT(format, ...)
#endif
/**
* This struct was copied from <wintrust.h> and encapsulates a signature used in verifying executable files.
*/
typedef struct _WIN_CERTIFICATE {
DWORD dwLength; // Specifies the length, in bytes, of the signature
WORD wRevision; // Specifies the certificate revision
WORD wCertificateType; // Specifies the type of certificate
BYTE bCertificate[ANYSIZE_ARRAY]; // An array of certificates
} WIN_CERTIFICATE, * LPWIN_CERTIFICATE;
/**
* Describes the location (address) and size of a ASN.1 blob within a buffer.
*
* @note The data itself is not contained in the struct.
*/
typedef struct _Asn1BlobPtr
{
int size; // size of the ASN.1 blob
PVOID ptrToData; // where the ASN.1 blob starts
} Asn1BlobPtr, * pAsn1BlobPtr;
/**
* Describes the location (address) and size of a certificate subject/issuer name, within a buffer.
*
* @note The data itself (name) is not contained in the struct.
*
* @note the reason for separating these fields into their own struct was to match the padding we
* observed in CertChainMember struct after the second 'short' field - once you enclose it
* into a struct, on x64 bit machines there will be a padding of 4 bytes at the end of the struct,
* because the largest member of the struct is of size 8 and it dictates the alignment of the struct.
*/
typedef struct _CertificatePartyName
{
PVOID pointerToName;
short nameLen;
short unknown;
} CertificatePartyName, * pCertificatePartyName;
/**
* Contains various data about a specific certificate in the chain and also points to the actual certificate.
*
* @note the digest described in this struct is the digest that was used to create the certificate - not for
* signing the file.
*
* @note The size reserved for digest is 64 byte regardless of the digest type, in order to accomodate SHA2/3's
* max size of 512bit. The memory is not zeroed, so we must take the actual digestSize into account when
* reading it.
*/
typedef struct _CertChainMember
{
int digestIdetifier; // e.g. 0x800c for SHA256
int digestSize; // e.g. 0x20 for SHA256
BYTE digestBuffer[64]; // contains the digest itself, where the digest size is dictated by digestSize
CertificatePartyName subjectName; // pointer to the subject name
CertificatePartyName issuerName; // pointer to the issuer name
Asn1BlobPtr certificate; // ptr to actual cert in ASN.1 - including the public key
} CertChainMember, * pCertChainMember;
/**
* Describes the format of certChainInfo buffer member of PolicyInfo struct. This header maps the types,
* locations, and quantities of the data which is contained in the buffer.
*
* @note when using this struct make sure to check its size first (bufferSize) because it's not guaranteed
* that all the fields below will exist.
*/
typedef struct _CertChainInfoHeader
{
// The size of the dynamically allocated buffer
int bufferSize;
// points to the start of a series of Asn1Blobs which contain the public keys of the certificates in the chain
pAsn1BlobPtr ptrToPublicKeys;
int numberOfPublicKeys;
// points to the start of a series of Asn1Blobs which contain the EKUs
pAsn1BlobPtr ptrToEkus;
int numberOfEkus;
// points to the start of a series of CertChainMembers
pCertChainMember ptrToCertChainMembers;
int numberOfCertChainMembers;
int unknown;
// ASN.1 blob of authenticated attributes - spcSpOpusInfo, contentType, etc.
Asn1BlobPtr variousAuthenticodeAttributes;
} CertChainInfoHeader, * pCertChainInfoHeader;
/**
* Contains information regarding the certificates that were used for signing/timestamping
*
* @note you must check structSize before accessing the other members, since some members were added later.
*
* @note all structs members, including the length, are populated by ci functions - no need
* to fill them in adavnce.
*/
typedef struct _PolicyInfo
{
int structSize;
NTSTATUS verificationStatus;
int flags;
pCertChainInfoHeader certChainInfo; // if not null - contains info about certificate chain
FILETIME revocationTime; // when was the certificate revoked (if applicable)
FILETIME notBeforeTime; // the certificate is not valid before this time
FILETIME notAfterTime; // the certificate is not valid before this time
} PolicyInfo, *pPolicyInfo;
/**
* Given a file digest and signature of a file, verify the signature and provide information regarding
* the certificates that was used for signing (the entire certificate chain)
*
* @note the function allocates a buffer from the paged pool --> can be used only where IRQL < DISPATCH_LEVEL
*
* @param digestBuffer - buffer containing the digest
*
* @param digestSize - size of the digest, e.g. 0x20 for SHA256, 0x14 for SHA1
*
* @param digestIdentifier - digest algorithm identifier, e.g. 0x800c for SHA256, 0x8004 for SHA1
*
* @param winCert - pointer to the start of the security directory
*
* @param sizeOfSecurityDirectory - size the security directory
*
* @param policyInfoForSigner[out] - PolicyInfo containing information about the signer certificate chain
*
* @param signingTime[out] - when the file was signed (FILETIME format)
*
* @param policyInfoForTimestampingAuthority[out] - PolicyInfo containing information about the timestamping
* authority (TSA) certificate chain
*
* @return 0 if the file digest in the signature matches the given digest and the signer cetificate is verified.
* Various error values otherwise, for example:
* STATUS_INVALID_IMAGE_HASH - the digest does not match the digest in the signature
* STATUS_IMAGE_CERT_REVOKED - the certificate used for signing the file is revoked
* STATUS_IMAGE_CERT_EXPIRED - the certificate used for signing the file has expired
*/
extern "C" __declspec(dllimport) NTSTATUS _stdcall CiCheckSignedFile(
const PVOID digestBuffer,
int digestSize,
int digestIdentifier,
const LPWIN_CERTIFICATE winCert,
int sizeOfSecurityDirectory,
PolicyInfo* policyInfoForSigner,
LARGE_INTEGER* signingTime,
PolicyInfo* policyInfoForTimestampingAuthority);
/**
* Resets a PolicyInfo struct - frees the dynamically allocated buffer in PolicyInfo (certChainInfo) if not null.
* Zeros the entire PolicyInfo struct.
*
* @param policyInfo - the struct to reset.
*
* @return the struct which was reset.
*/
extern "C" __declspec(dllimport) PVOID _stdcall CiFreePolicyInfo(PolicyInfo* policyInfo);
/**
* Given a file object, verify the signature and provide information regarding
* the certificates that was used for signing (the entire certificate chain)
*
* @note the function allocates memory from the paged pool --> can be used only where IRQL < DISPATCH_LEVEL
*
* @param fileObject[in] - fileObject of the PE in question
*
* @param a2[in] - unknown, needs to be reversed. 0 is a valid value.
*
* @param a3[in] - unknown, needs to be reversed. 0 is a valid value.
*
* @param policyInfoForSigner[out] - PolicyInfo containing information about the signer certificate chain
*
* @param signingTime[out] - when the file was signed
*
* @param policyInfoForTimestampingAuthority[out] - PolicyInfo containing information about the timestamping
* authority (TSA) certificate chain
*
* @param digestBuffer[out] - buffer to be filled with the digest, must be at least 64 bytes
*
* @param digestSize[inout] - size of the digest. Must be at leat 64 and will be changed by the function to
* reflect the actual digest length.
*
* @param digestIdentifier[out] - digest algorithm identifier, e.g. 0x800c for SHA256, 0x8004 for SHA1
*
* @return 0 if the file digest in the signature matches the given digest and the signer cetificate is verified.
* Various error values otherwise, for example:
* STATUS_INVALID_IMAGE_HASH - the digest does not match the digest in the signature
* STATUS_IMAGE_CERT_REVOKED - the certificate used for signing the file is revoked
* STATUS_IMAGE_CERT_EXPIRED - the certificate used for signing the file has expired
*/
extern "C" __declspec(dllimport) NTSTATUS _stdcall CiValidateFileObject(
struct _FILE_OBJECT* fileObject,
int a2,
int a3,
PolicyInfo* policyInfoForSigner,
PolicyInfo* policyInfoForTimestampingAuthority,
LARGE_INTEGER* signingTime,
BYTE* digestBuffer,
int* digestSize,
int* digestIdentifier
);
2.2 RAIIUtils.h
#pragma once
#include <ntddk.h>
#include <wdm.h>
#include "ci.h"
/**
* create a file handle for read.
* release handle when exiting the current context.
*/
class FileReadHandleGuard
{
public:
FileReadHandleGuard(PCUNICODE_STRING imageFileName) : _handle(nullptr), _isValid(false)
{
IO_STATUS_BLOCK ioStatusBlock = { 0 };
OBJECT_ATTRIBUTES objAttr = { 0 };
InitializeObjectAttributes(
&objAttr,
const_cast<PUNICODE_STRING>(imageFileName),
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
nullptr,
nullptr);
const NTSTATUS openFileRet = ZwOpenFile(
&_handle,
SYNCHRONIZE | FILE_READ_DATA, // ACCESS_MASK, we use SYNCHRONIZE because we might need to wait on the handle in order to wait for the file to be read
&objAttr,
&ioStatusBlock,
FILE_SHARE_READ,
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT // FILE_SYNCHRONOUS_IO_NONALERT so that zwReadfile will pend for us until reading is done
);
if (!NT_SUCCESS(openFileRet))
{
KDPRINT("【CiDemoDriver】", "failed to open file - openFileRet = %d\n", openFileRet);
return;
}
if (ioStatusBlock.Status != STATUS_SUCCESS || _handle == nullptr)
{
KDPRINT("【CiDemoDriver】", "ioStatusBlock.Status != STATUS_SUCCESS, or _handle is null\n");
return;
}
_isValid = true;
}
~FileReadHandleGuard()
{
if (_handle != nullptr)
{
ZwClose(_handle);
}
}
HANDLE& get() { return _handle; }
bool isValid() const { return _isValid; }
private:
HANDLE _handle;
bool _isValid;
};
/**
* create a section handle.
* release handle when exiting the current context.
*/
class SectionHandleGuard
{
public:
SectionHandleGuard(HANDLE& fileHandle) : _handle(nullptr), _isValid(false)
{
OBJECT_ATTRIBUTES objectAttributes = { 0 };
InitializeObjectAttributes(
&objectAttributes,
nullptr,
OBJ_KERNEL_HANDLE, // to make sure user mode cannot access this handle
nullptr,
nullptr);
const NTSTATUS createSectionRet = ZwCreateSection(
&_handle,
SECTION_MAP_READ,
&objectAttributes,
nullptr, // maximum size - use the file size, in order to map the entire file
PAGE_READONLY,
SEC_COMMIT, // map as commit and not as SEC_IMAGE, because SEC_IMAGE will not map things which are not needed for the PE - such as resources and certificates
fileHandle
);
if (!NT_SUCCESS(createSectionRet))
{
KDPRINT("【CiDemoDriver】", "failed to create section - ZwCreateSection returned %x\n", createSectionRet);
return;
}
_isValid = true;
}
~SectionHandleGuard()
{
if (_handle != nullptr)
{
ZwClose(_handle);
}
}
HANDLE& get() { return _handle; }
bool isValid() const { return _isValid; }
private:
HANDLE _handle;
bool _isValid;
};
/**
* retrieve a section object from a section handle.
* release object reference when exiting the current context.
*/
class SectionObjectGuard
{
public:
SectionObjectGuard(HANDLE& sectionHandle) : _object(nullptr), _isValid(false)
{
const NTSTATUS ret = ObReferenceObjectByHandle(
sectionHandle,
SECTION_MAP_READ,
nullptr,
KernelMode,
&_object,
nullptr
);
if (!NT_SUCCESS(ret))
{
KDPRINT("【CiDemoDriver】", "ObReferenceObjectByHandle failed - returned %x\n", ret);
return;
}
_isValid = true;
}
~SectionObjectGuard()
{
if (_object != nullptr)
{
ObfDereferenceObject(_object);
}
}
PVOID& get() { return _object; }
bool isValid() const { return _isValid; }
private:
PVOID _object;
bool _isValid;
};
/**
* create a view of file.
* unmap the view when exiting the current context.
*/
class SectionViewGuard
{
public:
SectionViewGuard(PVOID sectionObject) : _baseAddrOfView(nullptr), _viewSize(0), _isValid(false)
{
const NTSTATUS ret = MmMapViewInSystemSpace(
sectionObject,
&_baseAddrOfView,
&_viewSize
);
if (!NT_SUCCESS(ret))
{
KDPRINT("【CiDemoDriver】", "MmMapViewInSystemSpace failed - returned %x\n", ret);
return;
}
_isValid = true;
}
~SectionViewGuard()
{
if (_baseAddrOfView != nullptr)
{
MmUnmapViewInSystemSpace(_baseAddrOfView);
}
}
PVOID getViewBaseAddress() const { return _baseAddrOfView; }
SIZE_T getViewSize() const { return _viewSize; }
bool isValid() const { return _isValid; }
private:
PVOID _baseAddrOfView;
SIZE_T _viewSize;
bool _isValid;
};
/**
* create a PoicyInfo struct.
* Release the memory used by the struct when exiting the current context.
*/
class PolicyInfoGuard
{
public:
PolicyInfoGuard() : _policyInfo{} {}
~PolicyInfoGuard()
{
// CiFreePolicyInfo checks internally if there's memory to free
CiFreePolicyInfo(&_policyInfo);
}
PolicyInfo& get() { return _policyInfo; }
private:
PolicyInfo _policyInfo;
};
2.3 SignatureCheck.cpp
#include "RAIIUtils.h"
#include "SignatureCheck.h"
#include "ci.h"
#define SHA1_IDENTIFIER 0x8004
#define SHA256_IDENTIFIER 0x800C
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4
extern "C" PVOID RtlImageDirectoryEntryToData(PVOID BaseAddress, BOOLEAN MappedAsImage, USHORT Directory, PULONG Size);
bool inRange(const BYTE* rangeStartAddr, const BYTE* rangeEndAddr, const BYTE* addrToCheck);
void parsePolicyInfo(const pPolicyInfo policyInfo);
bool ciCheckSignedFileWrapper(const LPWIN_CERTIFICATE win_cert, ULONG sizeOfSecurityDirectory);
void validateFileUsingCiCheckSignedFile(PCUNICODE_STRING imageFileName)
{
KDPRINT("【CiDemoDriver】", "Validating file using CiCheckSignedFile...\n");
FileReadHandleGuard fileHandleGuard(imageFileName);
if (!fileHandleGuard.isValid()) return;
// create section for the file
SectionHandleGuard sectionHandleGuard(fileHandleGuard.get());
if (!sectionHandleGuard.isValid()) return;
// get section object from section handle
SectionObjectGuard sectionObjectGuard(sectionHandleGuard.get());
if (!sectionObjectGuard.isValid()) return;
// map a view of the section
SectionViewGuard viewGuard(sectionObjectGuard.get());
if (!viewGuard.isValid()) return;
// fetch the security directory
PVOID securityDirectoryEntry = nullptr;
ULONG securityDirectoryEntrySize = 0;
securityDirectoryEntry = RtlImageDirectoryEntryToData(
viewGuard.getViewBaseAddress(),
TRUE, // we tell RtlImageDirectoryEntryToData it's mapped as image because then it will treat the RVA as offset from the beginning of the view, which is what we want. See https://doxygen.reactos.org/dc/d30/dll_2win32_2dbghelp_2compat_8c_source.html#l00102
IMAGE_DIRECTORY_ENTRY_SECURITY,
&securityDirectoryEntrySize
);
if (securityDirectoryEntry == nullptr)
{
KDPRINT("【CiDemoDriver】", "no security directory\n");
return;
}
KDPRINT("【CiDemoDriver】", "securityDirectoryEntry found at: %p, size: %x\n",
securityDirectoryEntry, securityDirectoryEntrySize);
// Make sure the security directory is contained in the file view
const BYTE* endOfFileAddr = static_cast<BYTE*>(viewGuard.getViewBaseAddress()) + viewGuard.getViewSize();
const BYTE* endOfSecurityDir = static_cast<BYTE*>(securityDirectoryEntry) + securityDirectoryEntrySize;
if (endOfSecurityDir > endOfFileAddr || securityDirectoryEntry < viewGuard.getViewBaseAddress())
{
KDPRINT("【CiDemoDriver】", "security directory is not contained in file view!\n");
return;
}
// technically, there can be several WIN_CERTIFICATE in a file. This not common, and, for simplicity,
// we'll assume there's only one
LPWIN_CERTIFICATE winCert = static_cast<LPWIN_CERTIFICATE>(securityDirectoryEntry);
KDPRINT("【CiDemoDriver】", "WIN_CERTIFICATE at: %p, revision = %x, type = %x, length = %xd, bCertificate = %p\n",
securityDirectoryEntry, winCert->wRevision, winCert->wCertificateType, winCert->dwLength, static_cast<PVOID>(winCert->bCertificate));
ciCheckSignedFileWrapper(winCert, securityDirectoryEntrySize);
}
bool ciCheckSignedFileWrapper(const LPWIN_CERTIFICATE win_cert, ULONG sizeOfSecurityDirectory)
{
// prepare the parameters required for calling CiCheckSignedFile
PolicyInfoGuard signerPolicyInfo;
PolicyInfoGuard timestampingAuthorityPolicyInfo;
LARGE_INTEGER signingTime = {};
//const int digestSize = 20; // sha1 len, 0x14
const int digestSize = 32; // sha256 len, 0x20
//const int digestIdentifier = 0x8004; // sha1
const int digestIdentifier = 0x800C; // sha256
const BYTE digestBuffer[] = //
{ 0x5f, 0x6d, 0xa4, 0x95, 0x78, 0xa4, 0x39, 0x4b, 0xb4, 0x0f,
0xf6, 0x9b, 0xaa, 0x2a, 0xd7, 0x02, 0xda, 0x7d, 0x3d, 0xbe,
0xb8, 0x12, 0xb8, 0xc7, 0x24, 0xcd, 0xe3, 0x68, 0x89, 0x65,
0x86, 0x00 };
// CiCheckSignedFile() allocates memory from the paged pool, so make sure we're at IRQL < 2,
// where access to paged memory is allowed
NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
const NTSTATUS status = CiCheckSignedFile(
(PVOID)digestBuffer,
digestSize,
digestIdentifier,
win_cert,
(int)sizeOfSecurityDirectory,
&signerPolicyInfo.get(),
&signingTime,
×tampingAuthorityPolicyInfo.get());
KDPRINT("【CiDemoDriver】", "CiCheckSignedFile returned 0x%08X\n", status);
if (NT_SUCCESS(status))
{
parsePolicyInfo(&signerPolicyInfo.get());
return true;
}
return false;
}
UCHAR HexToChar(UCHAR temp)
{
UCHAR dst;
if (temp == ' ')
{
// do nothing
dst = temp;
}
else if (temp < 10) {
dst = temp + '0';
}
else {
dst = temp - 10 + 'A';
}
return dst;
}
void validateFileUsingCiValidateFileObject(PFILE_OBJECT fileObject)
{
KDPRINT("【CiDemoDriver】", "Validating file using CiValidateFileObject...\n");
NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
PolicyInfoGuard signerPolicyInfo;
PolicyInfoGuard timestampingAuthorityPolicyInfo;
LARGE_INTEGER signingTime = {};
int digestSize = 64; //大小必须为64,否则返回缓冲区大小太小
int digestIdentifier = 0;
BYTE digestBuffer[64] = {}; //大小必须为64,否则返回缓冲区大小太小
const NTSTATUS status = CiValidateFileObject(
fileObject,
0,
0,
&signerPolicyInfo.get(),
×tampingAuthorityPolicyInfo.get(),
&signingTime,
digestBuffer,
&digestSize,
&digestIdentifier
);
KDPRINT("【CiDemoDriver】", "CiValidateFileObject returned 0x%08X\n", status);
if (NT_SUCCESS(status))
{
CHAR digestTempBuffer[98] = { 0 };
for (int i = 0; i <= 31; i++)
{
digestTempBuffer[3 * i] = digestBuffer[i] >> 4;
digestTempBuffer[3 * i + 1] = digestBuffer[i] & 0xf;
digestTempBuffer[3 * i + 2] = ' ';
}
for (int i = 0; i < 96; i++)
{
digestTempBuffer[i] = HexToChar(digestTempBuffer[i]);
}
KDPRINT("【CiDemoDriver】", "Signer certificate:\n digest algorithm - 0x%x\n digest size - %d\r\n digest - %s\n",
digestIdentifier, digestSize, digestTempBuffer);
parsePolicyInfo(&signerPolicyInfo.get());
return;
}
}
void parsePolicyInfo(const pPolicyInfo policyInfo)
{
if (policyInfo == nullptr)
{
KDPRINT("【CiDemoDriver】", "parsePolicyInfo - paramter is null\n");
return;
}
if (policyInfo->structSize == 0)
{
KDPRINT("【CiDemoDriver】", "policy info is empty\n");
return;
}
if (policyInfo->certChainInfo == nullptr)
{
KDPRINT("【CiDemoDriver】", "certChainInfo is null\n");
return;
}
const pCertChainInfoHeader chainInfoHeader = policyInfo->certChainInfo;
const BYTE* startOfCertChainInfo = (BYTE*)(chainInfoHeader);
const BYTE* endOfCertChainInfo = (BYTE*)(policyInfo->certChainInfo) + chainInfoHeader->bufferSize;
DWORD dwChainCount = policyInfo->certChainInfo->numberOfCertChainMembers;
for (DWORD dwChainIndex = 0; dwChainIndex < dwChainCount; dwChainIndex++)
{
if (!inRange(startOfCertChainInfo, endOfCertChainInfo, (BYTE*)(chainInfoHeader->ptrToCertChainMembers + dwChainIndex)))
{
KDPRINT("【CiDemoDriver】", "chain members out of range\n");
continue;
}
// need to make sure we have enough room to accomodate the chain member struct
if (!inRange(startOfCertChainInfo, endOfCertChainInfo, (BYTE*)(chainInfoHeader->ptrToCertChainMembers + dwChainIndex) + sizeof(CertChainMember)))
{
KDPRINT("【CiDemoDriver】", "chain member out of range\n");
continue;
}
// we are interested in the first certificate in the chain - the signer itself
pCertChainMember signerChainMember = chainInfoHeader->ptrToCertChainMembers + dwChainIndex;
UTF8_STRING utf8SubjectName = { 0 };
utf8SubjectName.MaximumLength = utf8SubjectName.Length = signerChainMember->subjectName.nameLen;
utf8SubjectName.Buffer = static_cast<char*>(signerChainMember->subjectName.pointerToName);
UNICODE_STRING usSubjectName = { 0 };
RtlUTF8StringToUnicodeString(&usSubjectName, &utf8SubjectName, true);
UTF8_STRING utf8IssuerName = { 0 };
utf8IssuerName.MaximumLength = utf8IssuerName.Length = signerChainMember->issuerName.nameLen;
utf8IssuerName.Buffer = static_cast<char*>(signerChainMember->issuerName.pointerToName);
UNICODE_STRING usIssuerName = { 0 };
RtlUTF8StringToUnicodeString(&usIssuerName, &utf8IssuerName, true);
//KDPRINT("【CiDemoDriver】", "Signer certificate:\n digest algorithm - 0x%x\n size - %d\n subject - %.*s\n issuer - %.*s\n",
KDPRINT("【CiDemoDriver】", "Signer certificate[%d]:\n digest algorithm - 0x%x\n size - %d\n subject - %wZ\n issuer - %wZ\n",
dwChainIndex + 1,
signerChainMember->digestIdetifier, \
signerChainMember->certificate.size, \
/* signerChainMember->subjectName.nameLen,
static_cast<char*>(signerChainMember->subjectName.pointerToName),*/
& usSubjectName,
/*signerChainMember->issuerName.nameLen,
static_cast<char*>(signerChainMember->issuerName.pointerToName)*/
&usIssuerName);
CHAR digestTempBuffer[98] = { 0 };
for (int i = 0; i <= 31; i++)
{
digestTempBuffer[3 * i] = signerChainMember->digestBuffer[i] >> 4;
digestTempBuffer[3 * i + 1] = signerChainMember->digestBuffer[i] & 0xf;
digestTempBuffer[3 * i + 2] = ' ';
}
for (int i = 0; i < 96; i++)
{
digestTempBuffer[i] = HexToChar(digestTempBuffer[i]);
}
KDPRINT("【CiDemoDriver】", "Signer certificate:\n digest algorithm - 0x%x\n digest size - %d\r\n digest - %s\n",
signerChainMember->digestIdetifier, signerChainMember->digestSize, digestTempBuffer);
RtlFreeUnicodeString(&usSubjectName);
RtlFreeUnicodeString(&usIssuerName);
}
}
bool inRange(const BYTE* rangeStartAddr, const BYTE* rangeEndAddr, const BYTE* addrToCheck)
{
if (addrToCheck > rangeEndAddr || addrToCheck < rangeStartAddr)
{
return false;
}
return true;
}
2.4 main.cpp
#include <ntddk.h> // PsSetCreateProcessNotifyRoutineEx
#include <wdm.h>
#include "SignatureCheck.h"
#include "ci.h"
DRIVER_UNLOAD MyDriverUnload;
void registerProcessCallback();
void unregisterProcessCallback();
void ProcessCreateProcessNotifyRoutineEx(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo);
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);
DriverObject->DriverUnload = MyDriverUnload;
KDPRINT("【CiDemoDriver】", "CiDemoDriver load\n");
registerProcessCallback();
return STATUS_SUCCESS;
}
VOID MyDriverUnload(_In_ struct _DRIVER_OBJECT* DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
KDPRINT("【CiDemoDriver】", "CiDemoDriver unload\n");
unregisterProcessCallback();
}
void registerProcessCallback()
{
const NTSTATUS registerCallbackStatus = PsSetCreateProcessNotifyRoutineEx(ProcessCreateProcessNotifyRoutineEx, FALSE);
if (!NT_SUCCESS(registerCallbackStatus))
{
KDPRINT("【CiDemoDriver】", "failed to register callback with status %d\n", registerCallbackStatus);
}
else
{
KDPRINT("【CiDemoDriver】", "successfully registered callback\n");
}
}
void unregisterProcessCallback()
{
const NTSTATUS registerCallbackStatus = PsSetCreateProcessNotifyRoutineEx(ProcessCreateProcessNotifyRoutineEx, TRUE);
if (!NT_SUCCESS(registerCallbackStatus))
{
KDPRINT("【CiDemoDriver】", "failed to unregister callback\n");
}
else
{
KDPRINT("【CiDemoDriver】", "successfully unregistered callback\n");
}
}
void ProcessCreateProcessNotifyRoutineEx(
PEPROCESS Process,
HANDLE ProcessId,
PPS_CREATE_NOTIFY_INFO CreateInfo
)
{
UNREFERENCED_PARAMETER(Process);
UNREFERENCED_PARAMETER(ProcessId);
if (CreateInfo == nullptr) return; //process died
if (CreateInfo->FileObject == nullptr) return;
if (nullptr == CreateInfo->ImageFileName) return;
KDPRINT("【CiDemoDriver】", "New process - image name: %wZ\n", CreateInfo->ImageFileName);
validateFileUsingCiValidateFileObject(CreateInfo->FileObject);
validateFileUsingCiCheckSignedFile(CreateInfo->ImageFileName);
}
3. 相关逻辑分析及注意事项
- main.cpp 中使用 PsSetCreateProcessNotifyRoutineEx 添加一个创建进程回调,回调用使用进程对象的 FileObject 和文件路径 ImageFileName来进行文件数字签名的检验。
- SignatureCheck.cpp 的 ciCheckSignedFileWrapper 函数第 79 至 82 行为验证签名的参数 ,基中 digestSize = 20 时表示签名算法为 sha1 时的长度,为32时表示算法为 sha256 时的长度; 其中 digestIdentifier =0x8004 表示算法为 sha1的标识符,0x800c 为 sha256的标识符。
- SignatureCheck.cpp 的 ciCheckSignedFileWrapper 函数第 83 至 88 行为签名文件的数字摘要,其内容在 validateFileUsingCiValidateFileObject 第 158 至 170 中获取并打印。由于文件内容不同,签名得到的数字摘要也会不同。
- SignatureCheck.cpp 的 parsePolicyInfo 函数打印数字证书的证书链上每个证书的详细情况及摘要。其中要注意的是此摘要是数字证书的摘要,即使文件内容不同,只要签名的数字证书为同一个,其摘要的内容是一样的,这个在以后可以加以利用。
- SignatureCheck.cpp 的 validateFileUsingCiValidateFileObject 第139 行和 第 141 行的大小必须大于等于64,否则 CiValidateFileObject 将返回“缓冲区大小太小”。
- SignatureCheck.cpp 的 validateFileUsingCiValidateFileObject 中使用 CiValidateFileObject 获取签名的摘要时要注意只能获取第一个签名的摘要,比如同时双签名了 sha1 以及 sha256 证书,获取到的证书摘要只有 sha1的,而无法获取到 sha256的,因此如果要再获取 sha356 的摘要,需要再使用只有 sha256 签名证书的文件进行分析。
- SignatureCheck.cpp 中使用的 CiValidateFileObject 以及 CiCheckSignedFile 是在 ci.dll 中导出,链接时需要一些方法,详见 <<4.链接 Ci.dll>>。
4.链接 Ci.dll
《内核中的代码完整性:深入分析ci.dll》文中也指出了相关方法,即使用 lib 工具来创建一个.lib文件。
先创建一个.def文件,内容如下:
LIBRARY ci.dll
EXPORTS
CiCheckSignedFile
CiFreePolicyInfo
CiValidateFileObject
然后使用 lib 工具执行命令:
lib /def:ci.def /machine:x64 /out:ci.lib
5.测试
加载驱动,然后运行一个有数字签名的程序。该程序添加了数字签名。
加载驱动后的打印消息如下:
去掉数字签名后如下:
再加载驱动调试信息如下:
两种签名验证的方法都不通过。