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.rhkb.cn/news/209203.html

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

相关文章

Blender学习笔记:小车狂奔动画

文章目录 路旁小树汽车尾气移动 教程地址&#xff1a;八个案例教程带你从0到1入门blender【已完结】 小车建模 路旁小树 1 添加摄像机&#xff0c;在小车下面拉一个平面&#xff0c;覆盖到摄像机的观察视窗。复制一层平面&#xff0c;收窄变成小车两侧的路面&#xff0c;编辑…

vue使用实现录音功能js-audio-recorder

前言 最近项目中需要实现一个录音上传功能&#xff0c;用于语音评论可以上录音。 下载插件&#xff1a; npm i js-audio-recorder完整代码 <template><div style"padding: 20px;"><h3>录音上传</h3><div style"font-size:14px"…

图推理:忠实且可解释的大型语言模型推理11.29

推理&#xff1a;忠实且可解释的大型语言模型推理 摘要1 引言&#xff12; 相关工作3 准备工作4 方法4.1 图推理&#xff1a;规划-检索-推理4.2 优化框架4.3 规划模块4.4 检索推理模块 5 实验5.1 实验设置5.2 RQ1&#xff1a;KGQA 性能比较 摘要 大型语言模型&#xff08;LLM&…

python爬虫AES魔改案例:某音乐素材下载网

声明&#xff1a; 该文章为学习使用&#xff0c;严禁用于商业用途和非法用途&#xff0c;违者后果自负&#xff0c;由此产生的一切后果均与作者无关 一、找出需要加密的参数 js运行 atob(‘aHR0cHM6Ly93d3cuYWlnZWkuY29tL3NvdW5kL2NsYXNzLw’) 拿到网址&#xff0c;F12打开调…

二.运算符

运算符 1.算术运算符2.比较运算符3.逻辑运算符 1.算术运算符 算数运算符主要用于数学运算&#xff0c;其可以连接运算符前后的两个数值或表达式&#xff0c;对数值或表达式进行 - * / 和 取模%运算 1.加减法运算符 mysql> SELECT 100,100 0,100 - 0,100 50,100 50 - …

制作一个RISC-V的操作系统一-计算机系统漫游

文章目录 计算机的硬件组成两种架构程序的存储与执行程序语言的设计和进化一个mini计算机 编程语言的进化存储设备的层次结构操作系统 计算机的硬件组成 所有硬件由总线连接起来 两种架构 总线个数不同&#xff0c;Memory储存内容不同 程序的存储与执行 首先编译和链接某…

windows11 调整鼠标灵敏度方法

首先 我们打开电脑设置 或者在 此电脑/此计算机/我的电脑 右击选择属性 然后 有的电脑 左侧菜单中 直接就有 设备 然后在设备中直接就可以找到 鼠标 选项 调整光标速度即可 如果操作系统和我的一样 可以直接搜索鼠标 然后 选择 鼠标设置 然后 调整上面的鼠标指针速度即可

【Rust日报】2023-12-02 深度学习框架 Burn 发布 v0.11.0

深度学习框架 Burn 发布 v0.11.0 深度学习框架 Burn 发布 v0.11.0&#xff0c;新版本引入了自动内核融合&#xff08;Kernel Fusion&#xff09;功能&#xff0c;大大提升了访存密集型&#xff08;memory-bound&#xff09;操作的性能。同时宣布成立 Tracel AI (https://tracel…

安装错误_ImportError: cannot import name ‘XXX‘循环引用 绕晕TT(deepsolo)

这里写目录标题 error: Couldnt find a setup script in /tmp/easy_install-lfpvj6p4/scikit_image-0.22.0.tar.gzpip install -v -e . 和 python setup.py develop功能一样吗AttributeError: module ‘PIL.Image’ has no attribute ‘LINEAR’ImportError: cannot import nam…

【算法】前后缀分解题单⭐

文章目录 题单来源题目列表42. 接雨水238. 除自身以外数组的乘积2256. 最小平均差2483. 商店的最少代价代码1——前后缀数组代码2—— O ( 1 ) O(1) O(1)空间&#x1f402; 2420. 找到所有好下标2167. 移除所有载有违禁货物车厢所需的最少时间代码1——前后缀分解代码2——简洁…

3.4_1 java自制小工具 - pdf批量转图片

相关链接 目录参考文章&#xff1a;pdf转图片(apache pdfbox)参考文章&#xff1a;GUI界面-awt参考文章&#xff1a;jar包转exe(exe4j)参考文章&#xff1a;IDEA导入GIT项目参考文章&#xff1a;IDEA中使用Gitee管理代码gitee项目链接&#xff1a;pdf_2_image网盘地址&#xf…

基于CW32F030单片机的便携式多功能测试笔

一、产品背景 在日常的硬件调试工作中&#xff0c;我们最常使用的仪器仪表可能就是万用表了&#xff0c;虽然万用表号称“万用”&#xff0c;但大部分时候&#xff0c;我们需要使用到的功能无非是电压测量和通断测量。 作为调试的“得力干将”&#xff0c;万用表有时候也会存…

华为手环配置技巧

前言 华为手环作为生活健康辅助设备发挥不可忽视的作用&#xff0c;但每次更换手环后需要重新配置。华为手环不仅有健康监测、消息通知、天气推送、离线支付、公交卡、运动锻炼、等功能&#xff0c;还有倒计时、计时器、手电筒、闹钟、等小工具。下文介绍如何进行配置。 配置…

【富文本编辑器】原生JS使用WangEditor和vue上传图片前后端demo

【富文本编辑器】原生JS使用WangEditor上传图片前后端demo 第一步 HTML 第二步 初始化WangEditor与图片上传回调函数 第三步 后端返回数据体封装 第四步 后端接口上传图片&#xff0c;并返回图片地址 最近&#xff0c;我遇到了这样一个问题&#xff1a;因为我们的项目是基于…

什么是跨站脚本攻击

跨站脚本攻击 1. 定义2. 跨站脚本攻击如何工作3. 跨站脚本攻击类型4. 如何防止跨站脚本攻击 1. 定义 跨站脚本攻击&#xff08;Cross-site Scripting&#xff0c;通常称为XSS&#xff09;&#xff0c;是一种典型的Web程序漏洞利用攻击&#xff0c;在线论坛、博客、留言板等共享…

分页助手入门以及小bug,报sql语法错误

导入坐标 5版本以上的分页助手 可以不用手动指定数据库语言&#xff0c;它会自动识别 <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.3.2</version> </dependency&g…

从 Elasticsearch 到 SelectDB,观测云实现日志存储与分析的 10 倍性价比提升

作者&#xff1a;观测云 CEO 蒋烁淼 & 飞轮科技技术团队 在云计算逐渐成熟的当下&#xff0c;越来越多的企业开始将业务迁移到云端&#xff0c;传统的监控和故障排查方法已经无法满足企业的需求。在可观测理念逐渐深入人心的当下&#xff0c;人们越来越意识到通过多层次、…

[C/C++]数据结构 关于二叉树的OJ题(利用分治思想解决难题)

题目一: 单值二叉树 &#x1f6a9;⛲&#x1f31f;⚡&#x1f966;&#x1f4ac; &#x1f6a9;题目链接:力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 ⛲题目描述: 如果二叉树每个节点都具有相同的值&#xff0c;那么该二叉树就是单值二叉树。…

vivado实现分析与收敛技巧7-布局规划

关于布局规划 布局规划有助于设计满足时序要求。当设计难以始终如一满足时序要求或者从未满足时序要求时 &#xff0c; AMD 建议您执行布局规划。如果您与设计团队协作并且协作过程中一致性至关重要&#xff0c; 那么布局规划同样可以发挥作用。布局规划可通过减少平均布线延…

锁表的原因及解决办法

引言 作为开发人员&#xff0c;我们经常会和数据库打交道。 当我们对数据库进行修改操作的时候&#xff0c;例如添加字段&#xff0c;更新记录等&#xff0c;没有正确评估该表在这一时刻的使用频率&#xff0c;直接进行修改&#xff0c;致使修改操作长时间无法响应&#xff0…