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

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

2023-12-23 17:45
10855

1.背景

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

  

2.相关代码

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

2.1 ci.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#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 函数中 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>>。

  

4. CiValidateFileObject 和 CiCheckSignedFile 函数说明

4.1 CiValidateFileObject

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

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

4.2 CiCheckSignedFile

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

  

5.链接 Ci.dll

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

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

1
2
3
4
5
LIBRARY ci.dll
EXPORTS
CiCheckSignedFile
CiFreePolicyInfo
CiValidateFileObject

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

1
lib /def:ci.def /machine:x64 /out:ci.lib

  

6.测试

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


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞7
打赏
分享
最新回复 (10)
雪    币: 1264
活跃值: (1295)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
milko 2023-12-24 12:11
2
0
比较麻烦的就是系统版本不一样,有些API不是通用的
雪    币: 634
活跃值: (617)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
风然 2023-12-24 12:25
3
0
milko 比较麻烦的就是系统版本不一样,有些API不是通用的

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

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

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

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

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