Windows驱动中校验数字签名(使用 ci.dll)

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 signatureWORD  wRevision;                        // Specifies the certificate revisionWORD  wCertificateType;                 // Specifies the type of certificateBYTE  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 blobPVOID 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 SHA256int digestSize;                     // e.g. 0x20 for SHA256BYTE digestBuffer[64];              // contains the digest itself, where the digest size is dictated by digestSizeCertificatePartyName subjectName;   // pointer to the subject nameCertificatePartyName issuerName;    // pointer to the issuer nameAsn1BlobPtr 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 bufferint bufferSize;// points to the start of a series of Asn1Blobs which contain the public keys of the certificates in the chainpAsn1BlobPtr ptrToPublicKeys;int numberOfPublicKeys;// points to the start of a series of Asn1Blobs which contain the EKUspAsn1BlobPtr ptrToEkus;int numberOfEkus;// points to the start of a series of CertChainMemberspCertChainMember 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 chainFILETIME revocationTime;            // when was the certificate revoked (if applicable)FILETIME notBeforeTime;             // the certificate is not valid before this timeFILETIME 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 handlenullptr,nullptr);const NTSTATUS createSectionRet = ZwCreateSection(&_handle,SECTION_MAP_READ,&objectAttributes,nullptr, // maximum size - use the file size, in order to map the entire filePAGE_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 certificatesfileHandle);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 freeCiFreePolicyInfo(&_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  4extern "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 fileSectionHandleGuard sectionHandleGuard(fileHandleGuard.get());if (!sectionHandleGuard.isValid()) return;// get section object from section handleSectionObjectGuard sectionObjectGuard(sectionHandleGuard.get());if (!sectionObjectGuard.isValid()) return;// map a view of the sectionSectionViewGuard viewGuard(sectionObjectGuard.get());if (!viewGuard.isValid()) return;// fetch the security directoryPVOID 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#l00102IMAGE_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 viewconst 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 oneLPWIN_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 CiCheckSignedFilePolicyInfoGuard signerPolicyInfo;PolicyInfoGuard timestampingAuthorityPolicyInfo;LARGE_INTEGER signingTime = {};//const int digestSize = 20; // sha1 len, 0x14const  int digestSize = 32; // sha256 len, 0x20//const int digestIdentifier = 0x8004; // sha1const int digestIdentifier = 0x800C; // sha256const 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 allowedNT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);const NTSTATUS status = CiCheckSignedFile((PVOID)digestBuffer,digestSize,digestIdentifier,win_cert,(int)sizeOfSecurityDirectory,&signerPolicyInfo.get(),&signingTime,&timestampingAuthorityPolicyInfo.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(),&timestampingAuthorityPolicyInfo.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 structif (!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 itselfpCertChainMember 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 diedif (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. CiValidateFileObject 和 CiCheckSignedFile 函数说明

4.1 CiValidateFileObject 

  用来验证文件的数字签名是否正确,它验证了整个证书链,如果文件在签名后进行了修改,此验证不通过。

  它验证完的同时会返回一个证书链的相关信息,我们可以判断证书链中的一些数字证书的摘要来确定是否为相应的签名。

4.2 CiCheckSignedFile 

  用来检查相应的摘要签名,通过传入PE文件的 IMAGE_DIRECTORY_ENTRY_SECURITY 表,以及给定的摘要数据来进行判断签名是否有效。此摘要是整个文件的数字签名摘要,可以通过 CiValidateFileObject 来获取,并不是证书的摘要。

5.链接 Ci.dll

  《内核中的代码完整性:深入分析ci.dll》文中也指出了相关方法,即使用 lib 工具来创建一个.lib文件。

  先创建一个.def文件,内容如下:

LIBRARY ci.dll
EXPORTS
CiCheckSignedFile
CiFreePolicyInfo
CiValidateFileObject

  然后使用 lib 工具执行命令:

lib /def:ci.def /machine:x64 /out:ci.lib

6.测试

  加载驱动,然后运行一个有数字签名的程序。该程序添加了数字签名。

  加载驱动后的打印消息如下:

   去掉数字签名后如下:

  再加载驱动调试信息如下:

  两种签名验证的方法都不通过。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/194587.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu20.24 安装ecCodes,包括 tar.gz 和 python(笔记)

这里写目录标题 动机为此找了解决方案。废话不多说&#xff0c;如下&#xff1a;1. 下载 ecCodes 的源文件&#xff0c;网址如下&#xff1a; https://confluence.ecmwf.int/display/ECC/Releases2. 解压包:3. 创建 ecCodes 的安装目录:4. 通过 cmake 安装 :5. 编译, 测试&…

Siemens-NXUG二次开发-新建与保存prt文件[Python UF][20231204]

Siemens-NXUG二次开发-新建与保存prt文件[Python UF][20231204] 1.python uf函数1.1 NXOpen.UF.Part.New1.2 NXOpen.UF.Part.Save1.3 NXOpen.UF.Ui.OpenListingWindow1.4 NXOpen.UF.Ui.IsListingWindowOpen1.5 NXOpen.UF.Ui.WriteListingWindow1.6 NXOpen.UF.Ui.SaveListingWin…

电气间隙和爬电距离的算法

电气间隙和爬电距离 一、定义 1、电气间隙&#xff1a;不同电位的两个导电部件间最短的空间直线距离。 2、爬电距离&#xff1a;不同电位的两个导电部件之间沿绝缘材料表面的最短距离。 3、隔离距离&#xff08;机械式开关电器一个极的&#xff09;&#xff1a;满足对隔离器…

【重点】【双指针】42. 接雨水

题目 重点题目&#xff0c;深刻理解&#xff01;&#xff01;&#xff01; 解法1&#xff1a;备忘录 O(N) O(N) class Solution {public int trap(int[] height) {int n height.length, res 0;int[] lMax new int[n];int[] rMax new int[n];lMax[0] height[0];rMax[n …

华为手环关闭智能适时测量

问题 使用华为手环并使用华为创新研究APP后&#xff0c;会自动打开智能适时测量开关&#xff0c;此开关开启后&#xff0c;手环会在睡眠时间自动测量血氧&#xff0c;增加手环功耗从而影响续航&#xff0c;用户可根据自身需求决定是否开启&#xff0c;下文介绍如何找到此开关。…

Stable Diffusion 系列教程 - 1 基础准备(针对新手)

使用SD有两种方式&#xff1a; 本地&#xff1a; 显卡要求&#xff1a;硬件环境推荐NVIDIA的具有8G显存的独立显卡&#xff0c;这个显存勉勉强强能摸到门槛。再往下的4G可能面临各种炸显存、炼丹失败、无法生成图片等各种问题。对于8G显存&#xff0c;1.0模型就不行&#xff0…

Google Guava 反射工具使用详解

文章目录 反射类操作方法操作字段操作获取注解 反射 在 Guava 中&#xff0c;反射&#xff08;Reflection&#xff09;模块提供了一些用于简化反射操作的工具类和方法。通过 Guava 的反射模块&#xff0c;您可以方便地进行类、方法、字段的操作、获取注解信息等。下面详细介绍…

洛谷 P9516 color C++代码

目录 前言 思路点拨 AC代码 结尾 前言 今天我们来做洛谷上的一道题目。 网址&#xff1a;color - 洛谷 题目&#xff1a; 思路点拨 这题就是if-else判断输入的五个数据和不就OK了&#xff1f; 在这里我的估值是183&#xff08;截止2023.12.3&#xff09;&#xff0c;热…

Java 中 IO 流分为几种?

Java 中 IO 流分为几种&#xff1f; 在Java中&#xff0c;I/O&#xff08;输入/输出&#xff09;流用于处理输入和输出操作。I/O 流分为两大类&#xff1a;字节流和字符流&#xff0c;每个类别又分为输入流和输出流。 字节流&#xff08;Byte Streams&#xff09;&#xff1a…

【力扣】56. 合并区间

【力扣】56. 合并区间 文章目录 【力扣】56. 合并区间1. 题目介绍2. 解法2.1 方法一&#xff1a;标志位2.2 方法二&#xff1a;排序 遍历 3. Danger参考 1. 题目介绍 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合…

Linux系统配置深度学习环境之cudnn安装

前言 一个针对深度学习应用优化的 GPU 加速库。它提供了高性能、高可靠性的加速算法&#xff0c;旨在加速深度神经网络模型的训练和推理过程。 cuDNN 提供了一系列优化的基本算法和函数&#xff0c;包括卷积、池化、规范化、激活函数等&#xff0c;以及针对深度学习任务的高级功…

移动平均滤波的原理和C代码

移动平均滤波是一种简单有效的平滑信号的方法&#xff0c;它通过计算一系列数据点的平均值来减小信号中的波动。基本的移动平均滤波方法有两种&#xff1a;简单移动平均&#xff08;SMA&#xff09;和指数加权移动平均&#xff08;EWMA&#xff09;。 简单移动平均滤波&#xf…

Go读取yaml文件,struct返回,json输出

程序模块中&#xff0c;缺少不了一些配置定义&#xff0c;这时定义yaml是个很好的选择 先定义yaml文件内容&#xff0c;文件名为&#xff1a;task_status_config.yaml confs:#阅读类任务&#xff0c;即提醒任务read:name: readawait: #待开始任务status_id: 0ing: #进行中任务…

基于直流微电网的光伏并网功率转换装置设计与运行仿真

摘要 微电网是目前国内外应用较为广泛的一种绿色可再生能源&#xff0c;近几年我国微电网产业的发展十分迅速。然后&#xff0c;越来越多的微电网系统建立并网&#xff0c;微电网产生的电能受外界因素影响较大&#xff0c;具有一定的随机性和波动性&#xff0c;给并网后的电力系…

高通410随身wifi编译安装Asterisk(chan_mobile)搭建内网voip系统

前几天写了教程在ubuntu上搭建Asterisk,成功在内网打通了SIP通话。 ubuntu是18.04,安装的虚拟机系统;最近手头的斐讯的N1,Armbian系统,以及高通410的Debian系统,就是ubuntu内核,所以理论上应该也可以安装Asterisk;在这之前在openwrt上研究了一周多,Asterisk是跑起来了…

MATLAB算法实战应用案例精讲-【图像处理】人脸识别(补充篇)

目录 人脸识别业务流程 1、通用流程概述 2、ArcFace接入集成及示例 2.1 SDK的获取 2.2 SDK包结构

leetcode (力扣) 154. 寻找旋转排序数组中的最小值 I+II (二分法)

文章目录 题目描述思路分析完整代码 题目描述 已知一个长度为 n 的数组&#xff0c;预先按照升序排列&#xff0c;经由 1 到 n 次 旋转 后&#xff0c;得到输入数组。例如&#xff0c;原数组 nums [0,1,2,4,5,6,7] 在变化后可能得到&#xff1a; 若旋转 4 次&#xff0c;则可以…

股票主力建仓

只有拥有足够的筹码&#xff0c;主力才能在实盘中控制股价的涨跌&#xff0c;所以&#xff0c;在确定目标股之后&#xff0c;主力想要进行控盘&#xff0c;首先要做的第一件事就是建仓。那么&#xff0c;主力会在什么时候建仓&#xff1f;建仓的时候会采用什么方法&#xff1f;…

53.redis分布式缓存

目录 一、单机安装Redis。 二、Redis主从集群。 2.1.集群结构 2.2.准备实例和配置 2.3.启动 2.4.开启主从关系 2.5.测试 三、搭建哨兵集群。 3.1.集群结构 3.2.准备实例和配置 3.3.启动 3.4.测试 四、搭建分片集群。 4.1.集群结构 4.2.准备实例和配置 4.3.启动…

监控之Spring Boot Admin

目录 一、Spring Boot Admin 简介 官方网址 简介 二、Spring Boot Admin的使用 启动SBA server 微服务端配置SBA server地址 查看监控信息 一、Spring Boot Admin 简介 官方网址 GitHup网址&#xff1a;GitHub - codecentric/spring-boot-admin: Admin UI for administ…