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,一经查实,立即删除!

相关文章

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;满足对隔离器…

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

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

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

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

洛谷 P9516 color C++代码

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

【力扣】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: #进行中任务…

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…

Linux下Redis安装及配置

首先下载redis安装包&#xff1a;地址 这里我使用的是7.0版本的&#xff01; 将文件上传至linux上&#xff0c;此处不再多叙述&#xff0c;不会操作的&#xff0c;建议使用ftp&#xff01; 第一步&#xff1a;解压压缩包 tar -zxvf redis-7.0.14.tar.gz第二步&#xff1a;移…

网络安全--网络环境构成,系统的安全

2. 网络攻防环境 目标 了解攻防环境构成了解入侵检测系统&#xff08;平台&#xff09;的部署位置 2.1. 环境构成 2.1.1. 环境框图 一个基本的网络攻防实验环境包括&#xff1a;靶机、攻击机、入侵检测分析系统、网络连接四部分组成。 一个基础的网络攻防实验环境需要如下…

使用 Kettle 完成数据 ETL

文章目录 使用 Kettle 完成数据 ETL数据清洗数据处理 使用 Kettle 完成数据 ETL 现在我们有一份网站的日志数据集&#xff0c;准备使用Kettle进行数据ETL。先将数据集加载到Hadoop集群中&#xff0c;然后对数据进行清洗&#xff0c;最后加载到Hive中。 在本地新建一个数据集文…

RocketMQ-RocketMQ集群实践

搭建RocketMQ可视化管理服务 下载可视化客户端源码下载 | RocketMQ 这里只提供了源码&#xff0c;并没有提供直接运行的jar包。将源码下载下来后&#xff0c;需要解压并进入对应的目录&#xff0c;使用maven进行编译。(需要提前安装maven客户端) mvn clean package -Dmaven.t…

RPC和REST对比

RPC和REST对比 参考学习 RPC 和 REST 之间有什么区别&#xff1f; 当我们对比RPC和REST时&#xff0c;其实是在对比RPC风格的API和REST风格的API&#xff0c;后者通常成为RESTful API。 远程过程调用&#xff08;RPC&#xff09;和 REST 是 API 设计中的两种架构风格。API …

「Swift」取消UITableView起始位置在状态栏下方开始

前言&#xff1a;在写页面UI时发现&#xff0c;当隐藏了NavigationBar时&#xff0c;即使UITableView是从(0,0)进行布局&#xff0c;也会一直在手机状态栏下方进行展示布局&#xff0c;而我的想法是希望UITableView可以从状态栏处就进行展示布局 当前页面展示&#xff1a; 问题…

qt-C++笔记之QStringList

qt-C笔记之QStringList —— 杭州 2023-12-03 code review! 文章目录 qt-C笔记之QStringList1.1.《Qt官方文档》第一部分翻译&#xff1a;继承自QList\<QString\>-初始化-添加字符串1.2.迭代字符串1.3.join()和split()1.4.filter()1.5.lastIndexOf()1.6.indexOf()1.7.…

【Delphi】实现彩色日志显示框(TRichEdit Helper)

目录 一、前言 二、实现方法 1. 第一步 2. 第二步 3. 第三步 三、主程序代码 四、下载 1. 可执行程序 2. 程序源代码 一、前言 在用Delphi做日常开发的时候&#xff0c;经常需要显示程序运行的日志&#xff0c;一般我们会使用TMemo&#xff0c;使用起来简单&#xff0c…

简单3D姿态基线模型网络架构与验证【SIM】

在这篇文章中&#xff0c;我们将回顾 ICCV’17 上提出的 Simple 3D Pose Baseline &#xff0c;即用于 3d 人体姿势估计的简单而有效的基线&#xff0c;也称为 SIM。 NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在…