首页
社区
课程
招聘
[分享]Windows驱动中校验数字签名(使用 ci.dll)
发表于: 2023-12-23 17:45 15609

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

2023-12-23 17:45
15609

  对于常规应用程序来说,校验数字签名在在应用层可以使用 WinVerifyTrust, 在驱动层使用常规的 API无法使用,自己分析数据又太麻烦。
  但在内核中 ci.dll 包装了数据签名验证相关的功能,我们可以使用该 dll 来实现我们的数字签名验证。
  详细的分析见《内核中的代码完整性:深入分析ci.dll》。下面直接上相关代码。

  

  源代码地址为 https://github.com/Ido-Moshe-Github/CiDllDemo。
  这里作了稍微的修改以及添加一些打印信息。

  

  main.cpp 中使用 PsSetCreateProcessNotifyRoutineEx 添加一个创建进程回调,回调用使用进程对象的 FileObject 和文件路径  ImageFileName来进行文件数字签名的检验。
  SignatureCheck.cpp 的 ciCheckSignedFileWrapper 函数中 digestSize 和 digestIdentifier 为验证签名的参数 ,基中 digestSize = 20 时表示签名算法为 sha1 时的长度,为32时表示算法为 sha256 时的长度; 其中 digestIdentifier  =0x8004 表示算法为 sha1的标识符,0x800c 为 sha256的标识符。
  SignatureCheck.cpp 的 ciCheckSignedFileWrapper 函数中 digestBuffer 值为签名文件的数字摘要,其内容在 validateFileUsingCiValidateFileObject 第 158 至 170 中 KDPRINT获取并打印。由于文件内容不同,签名得到的数字摘要也会不同。
  SignatureCheck.cpp 的 parsePolicyInfo 函数打印数字证书的证书链上每个证书的详细情况及摘要。其中要注意的是此摘要是数字证书的摘要,即使文件内容不同,只要签名的数字证书为同一个,其摘要的内容是一样的,这个在以后可以加以利用。
  SignatureCheck.cpp 的 validateFileUsingCiValidateFileObject 中 digestSize 和 digestBuffer 的大小必须大于等于64,否则 CiValidateFileObject 将返回“缓冲区大小太小”。
  SignatureCheck.cpp 的 validateFileUsingCiValidateFileObject 中使用 CiValidateFileObject 获取签名的摘要时要注意只能获取第一个签名的摘要,比如同时双签名了 sha1 以及 sha256 证书,获取到的证书摘要只有 sha1的,而无法获取到 sha256的,因此如果要再获取 sha356 的摘要,需要再使用只有 sha256 签名证书的文件进行分析。
  SignatureCheck.cpp 中使用的 CiValidateFileObject 以及 CiCheckSignedFile 是在 ci.dll 中导出,链接时需要一些方法,详见 <<5.链接 Ci.dll>>。

  

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

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

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

  

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

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

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

  

  加载驱动,然后运行一个有数字签名的程序。该程序添加了数字签名。
图片描述
  加载驱动后的打印消息如下:
图片描述
  去掉数字证书后如下:
图片描述
  再加载驱动调试信息如下:
图片描述
  两种签名验证的方法都不通过。

#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
);
#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
);
#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;
};
#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

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (10)
雪    币: 1507
活跃值: (1840)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
比较麻烦的就是系统版本不一样,有些API不是通用的
2023-12-24 12:11
0
雪    币: 636
活跃值: (973)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
milko 比较麻烦的就是系统版本不一样,有些API不是通用的

是的 只在 Win10 及 Win11 的版本是才有 CiValidateFileObject 这个函数,其它版本就有点麻烦了

最后于 2024-1-10 09:08 被风然编辑 ,原因:
2023-12-24 12:25
0
雪    币: 3535
活跃值: (31016)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2023-12-24 16:40
1
雪    币: 1922
活跃值: (4165)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2023-12-25 09:26
0
雪    币: 200
活跃值: (398)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
棒棒哒
2023-12-27 11:42
0
雪    币: 8188
活跃值: (2887)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
7
不错,之前调研过,有些安全产品确实也是这么搞(某60,某山),不过我司搞企业安全,不敢用这种非文档化的方案;深挖了一下这几个函数的实现,自己倒腾一套检查x509的完事
2023-12-28 10:10
2
雪    币: 183
活跃值: (2611)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
8
sha1/sha256如何获取呢?硬编码不通用呀
2023-12-28 15:32
0
雪    币: 636
活跃值: (973)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
hambaga sha1/sha256如何获取呢?硬编码不通用呀
文章中 “加载驱动后的打印消息如下” 之后的图片中第二处标红的地方就是获取的摘要,用这个摘要就行了。也就是 SignatureCheck.cpp 中 validateFileUsingCiValidateFileObject 函数第 169-180 行打印出的摘要,这里多重签名情况下,即sha1、sha256都签的情况只取得了第一个,具体是哪个我目前还不知道原理,你测试时需要哪个摘要就只签那个算法的。然后 ciCheckSignedFileWrapper函数验证的代码是sha256的, 如果要改成 sha1 的需要修改变量 digestSize和digestIdentifier值,摘要的长度也注意下 sha1 是20字节,sha256取32字节,原代码中都有注释。而且这个代码的验证摘要是取的PE文件的摘要,数字签名不变的情况下,PE文件有改动摘要也要重新算,晚点出个基于数字证书摘要方法,这种方法下只要签名的证书是同一个,不同的PE文件都能用一个摘要进行验证。
2023-12-28 18:41
1
雪    币: 36207
活跃值: (7170)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10

@风然 能不能表演下爆破TotalRecorer
该软件就算你有注册码,也是录音至1分03秒出现爆音
需要对.sys修改,但修改之后声卡就不加载了。

不要迷信老外的补丁如何牛替换了key。。。测试下就知道了。。。水货假货也不在少数。
静态逆向到头了,需要动态双机才知道走了啥流程
同时由于屌丝没有签名,所以没法娱乐下去了。
还请抽时间分析下!

最后于 2023-12-31 12:59 被ninebell编辑 ,原因:
2023-12-31 12:58
0
雪    币: 231
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
具体要怎么调用CiValidateFileObject 呢,我这边尝试写了下(C语言的驱动程序),无法调用
2024-3-25 19:08
0
游客
登录 | 注册 方可回帖
返回
//