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

news2024/12/24 3:00:07

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,
                &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 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. 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.coloradmin.cn/o/1289313.html

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

相关文章

2023年度亚太客户中心产业发展论坛——鸿联九五荣获亚太区卓越客服大赛客户运营管理类铂金大奖

11月27-28日&#xff0c; 2023年度亚太客户中心产业发展论坛暨亚太区卓越客服大赛在马来西亚吉隆坡举行。来自中国、澳大利亚、马来西亚、新加坡、中国香港、印度尼西亚和泰国等多个国家及地区的优秀企业代表齐聚吉隆坡。 论坛首日活动以“Experience Excellence, Meet the Cha…

JavaScript代码压缩的功效与作用

JavaScript代码压缩可实现3大功能&#xff1a;减小体积、优化逻辑、提升执行效率 专业的JavaScript代码压缩&#xff0c;绝不仅仅是去除回车使代码挤到一行&#xff0c;而是用真正的技术减小代码体积、提升代码性能。 下面&#xff0c;以JShaman的JavaScript代码压缩功能为例…

辐射校正、辐射定标、大气校正关系

文章目录 前言一、基本概念二、辐射校正三、辐射定标四、大气校正 1.是否需要大气校正2.大气校正模型 五、参考链接 前言 完整的辐射校正包括遥感器校准、大气校正、太阳高度和地形校正。本文主要介绍辐射校正基本概念及区分辐射校正、辐射定标、大气校正。 一、基本概念 DN&a…

Java 将word转为PDF的三种方式和处理在服务器上下载后乱码的格式

我这边是因为业务需要将之前导出的word文档转换为PDF文件&#xff0c;然后页面预览下载这样的情况。之前导出word文档又不是我做的&#xff0c;所以为了不影响业务&#xff0c;只是将最后在输出流时转换成了PDF&#xff0c;当时本地调用没什么问题&#xff0c;一切正常&#xf…

挂耳式蓝牙耳机性价比排行榜吗,排名靠前的几款耳机推荐

当涉及挂耳式蓝牙耳机的选择时&#xff0c;消费者常常陷入选择困境&#xff0c;面对市场上琳琅满目的产品&#xff0c;很难找到性价比兼具的理想之选&#xff0c;为了帮助大家在众多选择中快速定位高性价比的耳机&#xff0c;我们精心整理了一份挂耳式蓝牙耳机性价比排行榜&…

javaWebssh图书系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

系统前景 图书有很多&#xff0c;老的图书书的管理靠纸介质&#xff0c;浪费人力和物力&#xff0c;给图书管理者带来极大的资源浪费。随着计算机信息化的普及&#xff0c;对图书的管理带来本质的改变&#xff0c;图书的销售情况以及&#xff0c;图书管理&#xff0c;以及年终对…

搜不到你想找的资料?那是你还没有掌握这些搜索技巧

文章目录 Google搜索命令sitefiletypeintitleinauthor:define:related:OR:- (减号):.. (两个点):weather:stocks:movie:link: 示例site:filetype:intitle:inauthor:define:related:OR:- (减号):.. (两个点):*** (星号):**cache:info:weather:stocks:movie:link: 补充 Google搜索…

解析操作系统是如何启动起来的?

操作系统被称为“第一个程序”&#xff0c;the first programme&#xff0c;原因很简单&#xff0c;只有当操作系统启动起来后才能运行我们编写的程序&#xff0c;那么你有没有想过这个问题&#xff1a;操作系统是怎样启动起来的呢&#xff1f;实际上这个过程就像发射火箭一样有…

解决vue ssr服务端渲染运行时报错:net::ERR_PROXY_CONNECTION_FAILED

现象&#xff1a; 从代码里找了半天也没有找到问题&#xff0c;但是由于ssr服务端渲染配置本身非常复杂&#xff0c;步骤又繁琐&#xff0c; 而且报错又很多&#xff0c;不知道哪里出了问题。 感觉是header或者cookie丢失造成的&#xff0c;因为据说ssr本身有这样的缺陷&…

NHN和SuiFrens合作的限量版配饰正式发售

SuiFrens那些可爱、富有想象力的生物&#xff0c;即将迎来全新装扮&#xff0c;而你也可以一同享受。今天&#xff0c;限量版NHN x SuiFrens Pebble City幸运配饰系列正式推出&#xff0c;同时还有机会在SuiFrens商店免费领取独家限量版NHN帽子。 NHN x SuiFens Pebble City配饰…

17、pytest自动使用fixture

官方实例 # content of test_autouse_fixture.py import pytestpytest.fixture def first_entry():return "a"pytest.fixture def order():return []pytest.fixture(autouseTrue) def append_first(order, first_entry):return order.append(first_entry)def test_s…

CVE初探之漏洞反弹Shell(CVE-2019-6250)

概述 ZMQ(Zero Message Queue)是一种基于消息队列得多线程网络库&#xff0c;C编写&#xff0c;可以使得Socket编程更加简单高效。 该编号为CVE-2019-6250的远程执行漏洞&#xff0c;主要出现在ZMQ的核心引擎libzmq(4.2.x以及4.3.1之后的4.3.x)定义的ZMTP v2.0协议中。 这一…

调试文心大模型或chatgpt的function Calling函数应用场景

沉默了一段时间&#xff0c;最近都在研究AI大模型的产品落地应用&#xff0c;我觉得这个function calling出来后&#xff0c;对目前辅助办公有革命性的改变&#xff0c;可以它来做什么呢&#xff1f;我们先来调试看看&#xff0c;chatgpt和文心大模型的ERNIE Bot支持这个&#…

电力智慧运维系统

电力智慧运维系统是以提高用户侧电力运行安全&#xff0c;降低运维成本为目标&#xff1b;采用智能化运维管理工具—“电易云”&#xff0c;帮助企业建立电力运维体系全方位的信息化、数字化平台&#xff0c;实现设备运行的数字化在线监控与线下维护处理的有机融合&#xff0c;…

在线学习平台-项目搭建

ER图: 外面的圆圈是外键(外键建在多的一边) 平台搭建: 参考这篇博客:若依的基本使用 测试: 先随便写一个测试的控制层看看项目是否搭建成功 调用验证码接口拿到uuid和token 更改token有效时长 之和的请求都需要带上token,测试时可以把token设置长一些 继承插件 需要什么…

C语言能判断一个变量是int还是float吗?

C语言能判断一个变量是int还是float吗&#xff1f; 在开始前我有一些资料&#xff0c;是我根据自己从业十年经验&#xff0c;熬夜搞了几个通宵&#xff0c;精心整理了一份「C语言从专业入门到高级教程工具包」&#xff0c;点个关注&#xff0c;全部无偿共享给大家&#xff01;&…

HXDSP2441-HXD-PRO(CPU)在线仿真器

HXD-PRO&#xff08;CPU&#xff09;在线仿真器 CPU下载器具体使用方法参考《HXD-PRO在线仿真器使用手册》&#xff0c;需要注意的是&#xff0c;CPU下载器在PLL初始化后才可以正常连接。

体育场找座位 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题目描述 在一个大型体育场内举办了一场大型活动&#xff0c;由于疫情防控的需要&#xff0c;要求每位观众的必须间隔至少一个空位才允许落座。现在给出一排观众座位分布图&#xff0c;座位中存在已落座的观众&…

阅读笔记——《Hopper: Interpretative Fuzzing for Libraries》

【参考文献】Zhan Q, Fang R, Bindu R, et al. Removing RLHF Protections in GPT-4 via Fine-Tuning[J]. arXiv preprint arXiv:2311.05553, 2023.【注】本文仅为作者个人学习笔记&#xff0c;如有冒犯&#xff0c;请联系作者删除。 目录 摘要 一、介绍 二、背景 1、库的模…

代码随想录算法训练营第五十七天 | 647. 回文子串,516.最长回文子序列,动态规划总结篇

目录 647. 回文子串 516.最长回文子序列 动态规划总结篇 647. 回文子串 题目链接&#xff1a;647. 回文子串 &#xff08;1&#xff09;dp[ i ][ j ] 表示从 i 到 j 的字符串是否为回文子串&#xff1b; &#xff08;2&#xff09;若 s[ i ] s[ j ] 若 j - i < 1 dp…