Windows驱动中使用数字签名验证控制设备访问权限

news2025/1/19 23:16:41

1. 背景

  在一般的驱动开发时,创建了符号链接后在应用层就可以访问打开我们的设备并进行通讯。

  但我们有时候不希望非自己的进程访问我们的设备并进行交互,虽然可以使用 IoCreateDeviceSecure 来创建有安全描述符的设备,但大数的用户账户为了方便都是管理员,因此该方法不太完整。

2. 一般方法

  一般情况下进行限制的方法是允许打开设备,但在打开设备后做一些校验,如果不通过,之后的其它请求都拒绝。

  例如 Dell 在一个漏洞驱动 pcdsrvc_x64.pkms 中处理如下(IDA逆向代码):

__int64 __fastcall DeviceIoControl(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
        _IO_STACK_LOCATION* pIosp; // r9
        unsigned int nBytesReturn; // edx
        ULONG nIoControlCode; // ecx
        unsigned int ntStatus; // ebx
        DWORD* dwInitailizeCode; // rax
 
        pIosp = pIrp->Tail.Overlay.CurrentStackLocation;
        nBytesReturn = 0;
        nIoControlCode = pIosp->Parameters.DeviceIoControl.IoControlCode;
        if (nIoControlCode != 0x222004 && !bInitialized)
        {
                ntStatus = 0xC0000022;
                pIrp->IoStatus.Information = 0i64;
        LABEL_18:
                LOBYTE(nBytesReturn) = 0;
                goto LABEL_36;
        }
        if (nIoControlCode - 0x222000 <= 0x7C)
        {
                ntStatus = 0;
                if (nIoControlCode == 0x222000)
                {
                        if (pIosp->Parameters.DeviceIoControl.OutputBufferLength >= 4)
                        {
                                *(_DWORD*)pIrp->AssociatedIrp.SystemBuffer = 0x6020300;
                        LABEL_16:
                                nBytesReturn = 4;
                                goto LABEL_17;
                        }
                }
                else
                {
                        if (nIoControlCode != 0x222004)
                        {
                                ntStatus = 0xC0000010;
                        LABEL_17:
                                pIrp->IoStatus.Information = nBytesReturn;
                                goto LABEL_18;
                        }
                        if (pIosp->Parameters.DeviceIoControl.InputBufferLength >= 4
                                && pIosp->Parameters.DeviceIoControl.OutputBufferLength >= 4)// 初始化驱动
                        {
                                dwInitailizeCode = (DWORD*)pIrp->AssociatedIrp.SystemBuffer;
                                if (*dwInitailizeCode == 0xA1B2C3D4)
                                {
                                        bInitialized = 1;
                                        *dwInitailizeCode = 0;
                                }
                                else
                                {
                                        *dwInitailizeCode = 1;
                                }
                                goto LABEL_16;
                        }
                }
                ntStatus = 0xC000000D;
                goto LABEL_17;
        }
    ......
}

  按照原逻辑是在打开使用后发送一个 0x222004 的请求码,并传入 0xA1B2C3D4,至此以后就相当于初始化了,之后的请求都可以正常进行;如果没有这个初始化的请求,之后的请求都是拒绝的。

  虽然这个方法可行,但稍微懂点逆向的相当于可以看到源代码逻辑,这样只要稍作处理这个方法就失效了。

3. 使用验证数字签名

  使用该方法可以在设备打开的逻辑中验证操作进程的数字签名,如果为允许的签名就返回成功,打开设备也就成功;否则返回失败也就打开失败。

  驱动中验证PE的数字签名见《Windows驱动中校验数字签名(使用 ci.dll)》。里边有两种方法,一种是 CiValidateFileObject,另一种是 CiCheckSignedFile。

  其中 CiCheckSignedFile 更为严格,需要指定签名算法和摘要,此摘要是 PE 文件签名后的摘要,因此随时 PE 文件的不同摘要也不同,这里并不太方便。

  因此本文用的是 CiValidateFileObject,首先它可以验证签名是否有效,即签名后文件进行了篡改后验证是不通过的;假的数字签名证书也不能通过验证。再者它可以返回证书链以及每个证书的摘要,我们可在在这里判断证书的摘要,如果是我们指定的证书则通过验证,即使是用的同一证书签名不同的文件,也可以一次代码,多次使用。

4. 代码

4.1 驱动代码:

4.1.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
);

4.1.2 SignatureCheck.h

#pragma once

#include <wdm.h>
#include <minwindef.h>

#define SHA1_IDENTIFIER 0x8004
#define SHA1_DEGIST_LENGTH  20
#define SHA256_IDENTIFIER 0x800C
#define SHA256_DEGIST_LENGTH  32
#define IMAGE_DIRECTORY_ENTRY_SECURITY  4
#define PRINT_DIGEST true

BOOLEAN ValidateFileUsingCiValidateFileObject(PFILE_OBJECT fileObject, const BYTE crtDigest[], UINT nLength);

4.1.3 SignatureCheck.cpp

#include "SignatureCheck.h"
#include "ci.h"



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


BOOLEAN ValidateFileUsingCiValidateFileObject(PFILE_OBJECT fileObject , const BYTE crtDigest[], UINT nLength)
{

        KDPRINT("【IrpCertCheck】", "Validating file using CiValidateFileObject...\n");
        NT_ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);

        PolicyInfo signerPolicyInfo = {0};
        PolicyInfo timestampingAuthorityPolicyInfo = {0};
        LARGE_INTEGER signingTime = {0};
        int digestSize = 64;  //大小必须为64,否则返回缓冲区大小太小
        int digestIdentifier = 0;
        BYTE digestBuffer[64] = {0}; //大小必须为64,否则返回缓冲区大小太小
        BOOLEAN bValidate = false;
        const NTSTATUS status = CiValidateFileObject(
                fileObject,
                0,
                0,
                &signerPolicyInfo,
                &timestampingAuthorityPolicyInfo,
                &signingTime,
                digestBuffer,
                &digestSize,
                &digestIdentifier
        );
       
        KDPRINT("【IrpCertCheck】", "CiValidateFileObject returned 0x%08X\n", status);
        if (NT_SUCCESS(status))
        {
#if PRINT_DIGEST 
                CHAR digestTempBuffer[98] = { 0 };
                for (int i = 0; i < 32; i++)
                {
                        digestTempBuffer[3 * i] = signerPolicyInfo.certChainInfo->ptrToCertChainMembers->digestBuffer[i] >> 4;
                        digestTempBuffer[3 * i + 1] = signerPolicyInfo.certChainInfo->ptrToCertChainMembers->digestBuffer[i] & 0xf;
                        digestTempBuffer[3 * i + 2] = ' ';
                }
                for (int i = 0; i < 96; i++)
                {
                        digestTempBuffer[i] = HexToChar(digestTempBuffer[i]);
                }
                KDPRINT("【IrpCertCheck】", "Signer certificate:\n digest algorithm - 0x%x\n digest size - %d\r\n digest - %s\n",
                        digestIdentifier, digestSize, digestTempBuffer);
#endif
                if (RtlCompareMemory(signerPolicyInfo.certChainInfo->ptrToCertChainMembers->digestBuffer, crtDigest, nLength) == nLength)
                {
                        bValidate =  true;
                }
        }
        CiFreePolicyInfo(&signerPolicyInfo);
        CiFreePolicyInfo(&timestampingAuthorityPolicyInfo);
        return bValidate;
}

4.1.4 main.cpp

#include <ntddk.h> // PsSetCreateProcessNotifyRoutineEx
#include <wdm.h>
#include <wdmsec.h>

#include "SignatureCheck.h"
#include "ci.h"
#pragma comment(lib, "Wdmsec.lib")

#define IRP_CERT_CHECK_DEVICE_NAME      (L"\\Device\\IrpCertCheck")
#define IRP_CERT_CHECK_DEVICE_SYMBOLIC_NAME      (L"\\??\\IrpCertCheck")
#define CTL_CODE_TEST  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x1000, METHOD_BUFFERED, FILE_ANY_ACCESS)

EXTERN_C
NTKERNELAPI
NTSTATUS
PsReferenceProcessFilePointer(
        IN PEPROCESS Process,
        OUT PVOID* pFilePointer
);
EXTERN_C
NTKERNELAPI
NTSTATUS
IoQueryFileDosDeviceName(
        _In_  PFILE_OBJECT FileObject,
        _Out_ POBJECT_NAME_INFORMATION* ObjectNameInformation
);
DRIVER_UNLOAD Unload;
PDEVICE_OBJECT g_pDeviceObject = NULL;

const static BYTE Cert256Digest[SHA256_DEGIST_LENGTH] = {
        0xD6, 0x43, 0x45, 0x88, 0x65, 0x45, 0x27, 0x40, 0x44, 0xDD,
        0x87, 0xD8, 0xCF, 0x67, 0x4B, 0x34, 0x78, 0x42, 0xE5, 0xA3,
        0x70, 0x35, 0x05, 0xB8, 0x90, 0x15, 0xD4, 0xA4, 0xB7, 0x7F,
        0xBA, 0x31 };

NTSTATUS
DeviceIoControl(
        IN PDEVICE_OBJECT pDeviceObject,
        IN PIRP pIrp
)
{
        UNREFERENCED_PARAMETER(pDeviceObject);
        NTSTATUS ntStatus = STATUS_INVALID_PARAMETER;
        PIO_STACK_LOCATION pIosp = IoGetCurrentIrpStackLocation(pIrp);
        if (pIosp->Parameters.DeviceIoControl.IoControlCode == CTL_CODE_TEST)
        {
                KDPRINT("【IrpCertCheck】", "This Is Test Control Code\n");
                ntStatus = STATUS_SUCCESS;
        }
        pIrp->IoStatus.Information = 0;
        pIrp->IoStatus.Status = ntStatus;
        IoCompleteRequest(pIrp, IO_NO_INCREMENT);
        return ntStatus;
}


NTSTATUS CommonDispatchRoutine(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
        UNREFERENCED_PARAMETER(pDeviceObject);
        NTSTATUS ntStatus = STATUS_NOT_IMPLEMENTED;
        pIrp->IoStatus.Information = 0;
        pIrp->IoStatus.Status = ntStatus;
        IoCompleteRequest(pIrp, IO_NO_INCREMENT);
        return ntStatus;
}

NTSTATUS
Create(
        IN PDEVICE_OBJECT pDeviceObject,
        IN PIRP pIrp
)
{
        UNREFERENCED_PARAMETER(pDeviceObject);
        BOOLEAN bAllow = false;
        NTSTATUS ntStatus = STATUS_ACCESS_DENIED;
        PEPROCESS pCurrentProcess = PsGetCurrentProcess();
        PFILE_OBJECT pFileObject = NULL;
        ntStatus = PsReferenceProcessFilePointer(pCurrentProcess, (PVOID*)&pFileObject);
        if (NT_SUCCESS(ntStatus))
        {
                POBJECT_NAME_INFORMATION name =NULL;
                ntStatus = IoQueryFileDosDeviceName(pFileObject, &name);
                if (NT_SUCCESS(ntStatus))
                {
                        KDPRINT("【IrpCertCheck】", "Operation Process Name:%wZ\n", &name->Name);
                        HANDLE hFile = NULL;
                        WCHAR szBuffer[MAX_PATH] = { 0 };
                        UNICODE_STRING usFileDosPath = { 0 };
                        usFileDosPath.MaximumLength = MAX_PATH * 2;
                        usFileDosPath.Buffer = szBuffer;
                        RtlAppendUnicodeToString(&usFileDosPath, L"\\??\\");
                        RtlAppendUnicodeStringToString(&usFileDosPath, &name->Name);

                        OBJECT_ATTRIBUTES oba = { 0 };
                        InitializeObjectAttributes(&oba, &usFileDosPath, OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE, NULL, NULL);
                        IO_STATUS_BLOCK iosb = { 0 };
                        ntStatus = ZwOpenFile(&hFile, GENERIC_READ, &oba, &iosb, FILE_SHARE_READ, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT);
                        if (NT_SUCCESS(ntStatus))
                        {
                                PFILE_OBJECT pFileRef = NULL;
                                ntStatus = ObReferenceObjectByHandle(hFile, GENERIC_READ, *IoFileObjectType, KernelMode, (PVOID*)&pFileRef, NULL);
                                if (NT_SUCCESS(ntStatus))
                                {

                                        //第一个参数的 PFILE_OBJECT 不能直接使用 PsReferenceProcessFilePointer返回的,可能
                                        //因为映射的方式不同,需要重新打开对应的文件取得 PFILE_OBJECT
                                        if (ValidateFileUsingCiValidateFileObject(pFileRef, Cert256Digest, SHA256_DEGIST_LENGTH))
                                        { 
                                                bAllow = true;
                                                KDPRINT("【IrpCertCheck】", "ValidateFileUsingCiValidateFileObject Pass\n");
                                        }
                                        else
                                        {
                                                KDPRINT("【IrpCertCheck】", "ValidateFileUsingCiValidateFileObject Failed\n");
                                        }
                                        ObDereferenceObject(pFileRef);
                                }
                                ZwClose(hFile);
                        }
                }
                ObDereferenceObject(pFileObject);
        }
        else
        {
                KDPRINT("【IrpCertCheck】", "PsReferenceProcessFilePointer failed, code:0x%08x\n", ntStatus);
        }
        if (bAllow)
        {
                ntStatus = STATUS_SUCCESS;
        }
        else
        {
                ntStatus = STATUS_ACCESS_DENIED;
        }
        pIrp->IoStatus.Information = 0;
        pIrp->IoStatus.Status = ntStatus;
        IoCompleteRequest(pIrp, IO_NO_INCREMENT);
        return ntStatus;
}

extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
        UNREFERENCED_PARAMETER(DriverObject);
        UNREFERENCED_PARAMETER(RegistryPath);
        DriverObject->DriverUnload = Unload;
        KDPRINT("【IrpCertCheck】", "IrpCertCheck Enter...\n");
        UNICODE_STRING usDeviceName = RTL_CONSTANT_STRING(IRP_CERT_CHECK_DEVICE_NAME);
        UNICODE_STRING usSddl = RTL_CONSTANT_STRING(L"D:P(A;;GA;;;WD)");
        //NTSTATUS ntStatus = IoCreateDeviceSecure(
        //        DriverObject,
        //        0,
        //        &usDeviceName,
        //        FILE_DEVICE_UNKNOWN,
        //        FILE_DEVICE_SECURE_OPEN,
        //        false,
        //        &usSddl,
        //        NULL,
        //        &g_pDeviceObject);
        NTSTATUS ntStatus = IoCreateDevice(
                DriverObject,
                0,
                &usDeviceName,
                FILE_DEVICE_UNKNOWN,
                FILE_DEVICE_SECURE_OPEN,
                false,
                &g_pDeviceObject);
        if (NT_SUCCESS(ntStatus))
        {
                UNICODE_STRING usSymbolic = RTL_CONSTANT_STRING(IRP_CERT_CHECK_DEVICE_SYMBOLIC_NAME);
                ntStatus = IoCreateSymbolicLink(&usSymbolic, &usDeviceName);
                if (NT_SUCCESS(ntStatus))
                {
                        for (int i = 0 ; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
                        {
                                DriverObject->MajorFunction[i] = CommonDispatchRoutine;
                        }
                        DriverObject->MajorFunction[IRP_MJ_CREATE] = Create;
                        DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceIoControl;
                }
                else
                {
                        KDPRINT("【IrpCertCheck】", "IoCreateSymbolicLink failed, code:0x%08x\n", ntStatus);
                }
        }
        else
        {
                KDPRINT("【IrpCertCheck】", "IoCreateDevice failed, code:0x%08x\n", ntStatus);
        }

        return ntStatus;
}

VOID Unload(_In_ struct _DRIVER_OBJECT* DriverObject)
{
        UNREFERENCED_PARAMETER(DriverObject);
        KDPRINT("【IrpCertCheck】", "IrpCertCheck Enter..\n");
        UNICODE_STRING usSymbolic = RTL_CONSTANT_STRING(IRP_CERT_CHECK_DEVICE_SYMBOLIC_NAME);
        IoDeleteSymbolicLink(&usSymbolic);
        if (g_pDeviceObject)
        {
                IoDeleteDevice(g_pDeviceObject);
                g_pDeviceObject = NULL;
        }
}

4.2 应用层代码

#include <iostream>
#include <windows.h>
#include <iomanip>
#define CTL_CODE_TEST  CTL_CODE(FILE_DEVICE_UNKNOWN, 0x1000, METHOD_BUFFERED, FILE_ANY_ACCESS)
int main()
{
        HANDLE hDevice = CreateFile(L"\\\\.\\IrpCertCheck", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        if (hDevice == INVALID_HANDLE_VALUE)
        {
                std::string strInfo = "打开设备失败, 错误码:0x";
                std::cout << strInfo << std::setbase(16) << std::setfill('0') <<std::setw(8) << GetLastError() << std::endl;
        }
        else
        {
                DWORD nByteRetuen = 0;
                bool bOK = DeviceIoControl(hDevice, CTL_CODE_TEST, nullptr, 0, NULL, 0, &nByteRetuen, NULL);
                if (bOK)
                {
                        std::string strInfo = "发送控制码成功";
                        std::cout << strInfo <<std::endl;
                }
                else
                {
                        std::string strInfo = "发送控制码失败, 错误码:0x";
                        std::cout << strInfo << std::setbase(16) << std::setfill('0') << std::setw(8) << GetLastError() << std::endl;
                }
        }
}

5. 代码逻辑及注意事项

  • 驱动中 IRP_MJ_CREATE 对应的 Create 函数调用 ValidateFileUsingCiValidateFileObject 来判断数字签名是否是允许的,不允许情况下直接返回失败,对应的应用层打开设备也就失败。
  • 驱动中 IRP_MJ_CREATE 对应的 Create 函数调用 ValidateFileUsingCiValidateFileObject 的第一个参数 fileObject 不能直接使用 PsReferenceProcessFilePointer 返回的 FILE_OBJECT,原因可能是返回的 FILE_OBJECT 对应的文件映射方式不同,需要再次使用 ZwOpenFile 返回的文件对象。
  • SignatureCheck.cpp 中的 ValidateFileUsingCiValidateFileObject 函数第 52 行至 66 行打印了验证返回的数字证书摘要,此摘要为证书的摘要而非 PE 文件的摘要,而此证书为整个证书链最顶层的证书。这个打印逻辑可以用来先获取证书摘要 ,再通过 第67 至 70 行的判断来决定证书是否通过验证。
  • main.cpp 中的 Cert256Digest 的数据即是上一条方法中获取的证书摘要。
  • 应用层逻辑为打开设备成功后送一个控制请求。

6. 实验效果

  使用代码中 Cert256Digest 摘要对应的数字证书签名应用层程序,如下:

  加载驱动后运行应用层程序,效果如下:

  可以看到验证通过,应用层控制码也发送成功,main.cpp 中的 Cert256Digest 数据即为图中红框所标部分。

  之后换掉用层程序的数字签名(SHA256 改为 SHA1)后验证如下:

  可以发现数字证书虽然验证成功,但证书不是我们指定证书,最终还是返回失败,应用层打开设备直接返回拒绝访问,目的达到。

7.注意事项

  •   CiValidateFileObject 由 ci.dll 导出,编译驱动时需要链接 ci.dll, 具体方法见《Windows驱动中校验数字签名(使用 ci.dll)》。
  •   在多重数字签名的情况下,CiValidateFileObject 只能返回一个签名的摘要,因此需要应用层只有一个数字签名。

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

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

相关文章

【理解ARM架构】中断处理 | CPU模式

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《理解ARM架构》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f35c;中断&#x1f368;GPIO中断代码实现 &#x1f35c;CPU&#x1f368;CONTROL…

设计模式-结构型模式之装饰者设计模式

文章目录 六、装饰者模式 六、装饰者模式 装饰者模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其结构。它是作为现有的类的一个包装。 装饰类和被装饰类可以独立发展&#xff0c;不会相互耦合&#xff0c;装饰者模…

各大期刊网址

AAAL: http://dblp.uni-trier.de/db/conf/aaai/ CVPR: http://dblp.uni-trier.de/db/conf/cvpr/ NeurlPS:http://dblp.uni-trier.de/db/conf/nips/ ICCV: http://dblp.uni-trier.de/db/conf/iccv/ IJCAL: http://dblp.uni-trier.de/db/conf/ijcal/ 并非原创引…

Linux(12):磁盘配额(Quota)与进阶文件系统管理

磁盘配额&#xff08;Quota&#xff09;的应用与实作 Quota 的一般用途&#xff1a; 针对 www server &#xff0c;例如:每个人的网页空间的容量限制&#xff1b; 针对 mail server&#xff0c;例如:每个人的邮件空间限制。 针对 file server&#xff0c;例如:每个人最大的可用…

【开源】基于Vue+SpringBoot的康复中心管理系统

项目编号&#xff1a; S 056 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S056&#xff0c;文末获取源码。} 项目编号&#xff1a;S056&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 普通用户模块2.2 护工模块2.3 管理员…

详解Spring对Mybatis等持久化框架的整合

&#x1f609;&#x1f609; 学习交流群&#xff1a; ✅✅1&#xff1a;这是孙哥suns给大家的福利&#xff01; ✨✨2&#xff1a;我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 &#x1f96d;&#x1f96d;3&#xff1a;QQ群&#xff1a;583783…

最大单词数算法分析

题目描述&#xff1a; 算法一&#xff1a; 代码实现&#xff1a; # include<stdio.h> # include<string.h>int main(){//char text[100]"leet code";//char brokenLetters[26]"lt";char text[100]"hello world";char brokenLetters…

28、DS18B20温度传感器

DS18B20介绍 DS18B20是一种常见的数字温度传感器&#xff0c;其控制命令和数据都是以数字信号的方式输入输出&#xff0c;相比较于模拟温度传感器&#xff0c;具有功能强大、硬件简单、易扩展、抗干扰性强等特点 测温范围&#xff1a;-55C 到 125C 通信接口&#xff1a;1-Wire…

常见的线程安全问题及解决

1. 什么是线程安全 线程安全指的是当多个线程同时访问一个共享的资源时&#xff0c;不会出现不确定的结果。这意味着无论并发线程的调度顺序如何&#xff0c;程序都能够按照设计的预期来运行&#xff0c;而不会产生竞态条件&#xff08;race condition&#xff09;或其他并发问…

【数据库原理】函数依赖、三范式、视图、事务、数据库设计(概念、逻辑、物理结构设计,实施)、数据流图、数据字典、存储过程、触发器、备份与还原【我统统拿下!】

函数依赖 函数依赖是关系数据库中的重要概念&#xff0c;用于描述关系中属性之间的依赖关系。 在关系数据库中&#xff0c;如果关系 R 中的某个属性或属性组的值能够唯一确定关系中其他属性的值&#xff0c;那么我们就说这个属性或属性组对其他属性具有函数依赖关系。 举个例…

pytest系列——allure之在测试用例添加标题(@allure.title())

前言 通过使用装饰器allure.title可以为测试用例自定义一个更具有阅读性的易读的标题。 allure.title的三种使用方式&#xff1a; 直接使用allure.title为测试用例自定义标题&#xff1b;allure.title支持通过占位符的方式传递参数&#xff0c;可以实现测试用例标题参数化&a…

SpringBootCache缓存——j2cache

文章目录 缓存供应商变更&#xff1a;j2cache 缓存供应商变更&#xff1a;j2cache <!-- https://mvnrepository.com/artifact/net.oschina.j2cache/j2cache-core --><dependency><groupId>net.oschina.j2cache</groupId><artifactId>j2cache-cor…

【数组和函数实战: 斗地主游戏】

目录 1. 玩法说明 2. 分析和设计 3. 代码实现 4. 游戏演示1. 玩法说明 一副54张牌,3最小,两个王最大,其实是2,和上面一样从大到小排列 2. 分析和设计 2.1 分析和设计 常量和变量设计 一副牌有54张,有牌的数值和花色,可以分别用两个数组来存储,card为卡牌表示的数值,color为…

WebGL笔记:矩阵平移的数学原理和实现

矩阵平移的数学原理 让向量OA位移 x方向&#xff0c;txy方向&#xff0c;tyz方向&#xff0c;tz 最终得到向量OB 矩阵平移的应用 再比如我要让顶点的x移动0.1&#xff0c;y移动0.2&#xff0c;z移动0.3 1 &#xff09;顶点着色器核心代码 <script id"vertexShader&…

echarts实现全国及各省市地图

echarts实现全国及各省市地图&#xff08;内附地图json文件&#xff09; 去阿里云就可以获取&#xff1a;[阿里云地理]&#xff1a;http://datav.aliyun.com/portal/school/atlas/area_selector#&lat31.769817845138945&lng104.29901249999999&zoom4(http://datav…

6.7 Windows驱动开发:内核枚举LoadImage映像回调

在笔者之前的文章《内核特征码搜索函数封装》中我们封装实现了特征码定位功能&#xff0c;本章将继续使用该功能&#xff0c;本次我们需要枚举内核LoadImage映像回调&#xff0c;在Win64环境下我们可以设置一个LoadImage映像加载通告回调&#xff0c;当有新驱动或者DLL被加载时…

HTML块元素和行内元素

HTML块元素和行内元素 1.分类2.块元素3.行内元素 1.分类 在HTML中&#xff0c;根据元素的表现形式&#xff0c;一般可以分为两类&#xff1a; 块元素&#xff08;block&#xff09;行内元素&#xff08;inline&#xff09; 2.块元素 在HTML中&#xff0c;块元素在浏览器显示…

制作一个RISC-V的操作系统二-RISC-V ISA介绍

文章目录 ISA的基本介绍啥是ISA为什么要设计ISACISCvsRISCISA的宽度知名ISA介绍 RISC-V历史和特点RISC-V发展RISC-V ISA 命名规范模块化的ISA通用寄存器Hart特权级别Control and Status Register&#xff08;CSR&#xff09;内存管理与保护异常和中断 ISA的基本介绍 啥是ISA …

单显卡插槽安装英伟达Tesla P4 AI加速卡

Tesla P4是专业AI显卡&#xff0c;只有70瓦功耗&#xff0c;可以作为AI入门使用。 安装时碰到的几个问题&#xff1a; 首先因为单显卡插槽&#xff0c;就需要先安装好机器&#xff0c;然后ssh登录进行相关配置。安装的时候来回插拔了好多次&#xff01; 其次就是安装驱动时&a…

【java毕业设计源码】基于SSM框架的在线智能题库管理系统设计与实现

该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程等学习内容。 目录 一、项目介绍&#xff1a; 二、文档学习资料&#xff1a; 三、模块截图&#xff1a; 四、开发技术与运行环境&#xff1a; 五、代码展示&#xff1a; 六、数据库表截图&#xff1a…