Windows驱动中使用数字签名验证控制设备访问权限
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,
×tampingAuthorityPolicyInfo,
&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(×tampingAuthorityPolicyInfo);
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 只能返回一个签名的摘要,因此需要应用层只有一个数字签名。