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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
|
/*++
Copyright (c) Microsoft Corporation. All rights reserved.
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
PURPOSE.
Module Name:
capture.c
Abstract:
This file contains
1. Documentation for what parameters need to be captured
2. A simple sample showing how to capture the parameters for
SetValueKey and DeleteValueKey operations.
3. Helper routines for capturing buffers and UNICODE_STRINGs.
Environment:
Kernel mode only
--*/
#include "regfltr.h"
/*++
Probing and Capturing Parameters
This section defines how registry filtering drivers should handle input
parameters.
I. Probed Parameters
All members in related registry structures (e.g. REG_CREATE_KEY_INFORMATION,
REG_SAVE_KEY_INFORMATION) and all structures and buffers pointed to by
their buffers are already probed by registry. Parameters are probed only
when ExGetPreviousMode indicates that the previous mode was user mode. If
the previous mode is kernel mode (i.e. KernelMode. is returned by
ExGetPreviousMode), all parameters are considered valid and no probing is
done in this case.
II. Captured parameters
Most but not all members in related registry structures are captured.
If the previous mode is kernel mode, all parameters are considered valid
and no capturing is done in this case. If the previous mode is user mode,
all parameters are probed but not necessarily captured. Since this impacts
how registry filtering drivers should handle input parameters, we outline
below exactly which parameters are captured. It is important to note that
some members currently probed but not captured by the OS may be captured
in the future. On the other hand registry filtering drivers can rely on
the fact that parameters currently captured will be captured in all future
OS releases.
* Note: As of Windows 8, all structures except output buffers are fully
captured.
1. Fully captured structures:
Note: A fully captured structure is a captured structure where all
structures and buffers pointed to by its members are also fully
captured.
For example: the UNICODE_STRING structure pointed to by the
CompleteName member in the fully captured REG_CREATE_KEY_INFORMATION
structure is also captured (including the Buffer member of the
UNICODE_STRING).
List of fully captured structures (in Windows 7):
REG_CREATE_KEY_INFORMATION (** incorrect)
REG_CREATE_KEY_INFORMATION_V1 (** incorrect)
REG_DELETE_KEY_INFORMATION
REG_FLUSH_KEY_INFORMATION
REG_KEY_HANDLE_CLOSE_INFORMATION
REG_OPEN_KEY_INFORMATION
REG_OPEN_KEY_INFORMATION_V1
REG_QUERY_KEY_SECURITY_INFORMATION (** added)
REG_REPLACE_KEY_INFORMATION
REG_RESTORE_KEY_INFORMATION
REG_SAVE_KEY_INFORMATION
REG_SET_KEY_SECURITY_INFORMATION.
REG_UNLOAD_KEY_INFORMATION
** There were incorrect entries in this list that are now
corrected. REG_CREATE_KEY_INFORMATION and
REG_CREATE_KEY_INFORMATION_V1 are not fully captured and should
be on the partially captured structures.
REG_QUERY_KEY_SECURITY_INFORMATION has now been added to the list
of fully captured structures.
2. Partially captured structures
Note: This section has been modified for clarity in the Windows 8
release of the WDK. However this information only applies to Windows 7
since in Windows 8 all input buffers and structures are captured.
This section defines which members are captured in the partially
captured structures.
Notes:
A. CallContext, ObjectContext members are not consumed by registry
and are not treated as probed or captured by this document.
B. Integer types (e.g. enum, int, �) are considered captured in the
structure and are not explicitly listed below.
C. The Object member is always captured and it's not explicitly
listed below.
D. Output buffers are probed but not captured
List of partially captured structures and the members that are NOT
captured:
REG_CREATE_KEY_INFORMATION: Class->Buffer
REG_CREATE_KEY_INFORMATION_V1: Class->Buffer
REG_DELETE_VALUE_KEY_INFORMATION: ValueName->Buffer
REG_LOAD_KEY_INFORMATION: KeyName->Buffer
REG_QUERY_VALUE_KEY_INFORMATION: ValueName->Buffer
REG_QUERY_MULTIPLE_VALUE_KEY_INFORMATION: BufferLength
REG_RENAME_KEY_INFORMATION: NewName->Buffer
REG_SET_VALUE_KEY_INFORMATION: Data
All other REG_Xxx_INFORMATION structures do not have fields that
require capturing other than those mentioned in notes B and C.
Example:
REG_ENUMERATE_KEY_INFORMATION:
Object: captured, see (C) above
Index: captured, see (B) above
KeyInformationClass : captured, see (B) above
KeyInformation: only probed, see (D) above
Length: captured, see (B) above
ResultLength: only probed, see (D) above
CallContext, ObjectContext: unknown, see (A) above
Reserved: currently undefined
List of these structures:
REG_CALLBACK_CONTEXT_CLEANUP_INFORMATION
REG_DELETE_KEY_INFORMATION
REG_ENUMERATE_KEY_INFORMATION
REG_KEY_HANDLE_CLOSE_INFORMATION
REG_QUERY_KEY_INFORMATION
REG_QUERY_KEY_SECURITY_INFORMATION
REG_REPLACE_KEY_INFORMATION
REG_RESTORE_KEY_INFORMATION
REG_SAVE_KEY_INFORMATION
REG_SET_INFORMATION_KEY_INFORMATION
REG_SET_KEY_SECURITY_INFORMATION
REG_UNLOAD_KEY_INFORMATION
III. Handling Registry Filtering Parameters
Registry filtering drivers must handle input parameters correctly. If the
previous mode is user mode and the driver needs to use a parameter, it must
depending on the scenario either wrap every access with a try-except
construct or capture the parameter. If the driver wants to call a Zw
registry API or any other kernel mode Zw API, it must ensure that all the
arguments passed to the call are captured because these APIs will not
probe or capture their inputs if the call originated from kernel mode.
There is no need for drivers to probe any of the parameters as the registry
has already probed them. If the driver uses the OS probe APIs to probe a
parameter that has already been captured by the registry, it will throw an
exception.
Note: Special handling should be provided for NULL buffers. In some cases
such buffer might be considered valid even for kernel mode.
--*/
NTSTATUS
CallbackCapture(
_In_ PCALLBACK_CONTEXT CallbackCtx,
_In_ REG_NOTIFY_CLASS NotifyClass,
_Inout_ PVOID Argument2
)
/*++
Routine Description:
This helper callback routine shows how to capture a buffer and a
unicode string with the name of a value. The bulk of the work is down
in the helper capture routines: CaptureBuffer and CaptureUnicodeString.
In the pre-notification phase, we bypass the set value and delete value
operations and complete them manually by calling ZwSetValueKey and
ZwDeleteValueKey.
Arguments:
CallbackContext - The value that the driver passed to the Context parameter
of CmRegisterCallbackEx when it registers this callback routine.
NotifyClass - A REG_NOTIFY_CLASS typed value that identifies the type of
registry operation that is being performed and whether the callback
is being called in the pre or post phase of processing.
Argument2 - A pointer to a structure that contains information specific
to the type of the registry operation. The structure type depends
on the REG_NOTIFY_CLASS value of Argument1.
Return Value:
NTSTATUS
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
PREG_SET_VALUE_KEY_INFORMATION PreSetValueInfo;
PREG_DELETE_VALUE_KEY_INFORMATION PreDeleteValueInfo;
HANDLE RootKey = NULL;
PVOID LocalData = NULL;
PVOID Data = NULL;
UNICODE_STRING LocalValueName = {0};
PUNICODE_STRING ValueName = NULL;
KPROCESSOR_MODE Mode = KernelMode;
UNREFERENCED_PARAMETER(CallbackCtx);
switch(NotifyClass) {
case RegNtPreSetValueKey:
PreSetValueInfo = (PREG_SET_VALUE_KEY_INFORMATION) Argument2;
//
// REG_SET_VALUE_KEY_INFORMATION is a partially captured structure.
// The value name is captured but the data is not. Since we are
// passing the data to a zw* method, we need to capture it.
//
// *Note: as of win8, the data buffer is captured as well
// by the registry.
//
Mode = ExGetPreviousMode();
if (!g_IsWin8OrGreater && (Mode == UserMode)) {
Status = CaptureBuffer(&LocalData,
PreSetValueInfo->Data,
PreSetValueInfo->DataSize,
REGFLTR_CAPTURE_POOL_TAG);
if (!NT_SUCCESS(Status)) {
break;
}
Data = LocalData;
} else {
Data = PreSetValueInfo->Data;
}
//
// Get a handle to the root key the value is being created under.
// This is in PreInfo->Object.
//
Status = ObOpenObjectByPointer(PreSetValueInfo->Object,
OBJ_KERNEL_HANDLE,
NULL,
KEY_ALL_ACCESS,
NULL,
KernelMode,
&RootKey);
if (!NT_SUCCESS (Status)) {
ErrorPrint("ObObjectByPointer failed. Status 0x%x", Status);
break;
}
//
// Set the value.
//
Status = ZwSetValueKey(RootKey,
PreSetValueInfo->ValueName,
0,
PreSetValueInfo->Type,
Data,
PreSetValueInfo->DataSize);
if(!NT_SUCCESS(Status)) {
ErrorPrint("ZwSetValue in CallbackModify failed. Status 0x%x",
Status);
ZwClose(RootKey);
break;
}
//
// Finally return STATUS_CALLBACK_BYPASS to tell the registry
// not to proceed with the original registry operation and to return
// STATUS_SUCCESS to the caller.
//
InfoPrint("\tCallback: Set value %wZ bypassed.", PreSetValueInfo->ValueName);
Status = STATUS_CALLBACK_BYPASS;
ZwClose(RootKey);
break;
case RegNtPreDeleteValueKey:
PreDeleteValueInfo = (PREG_DELETE_VALUE_KEY_INFORMATION) Argument2;
//
// REG_DELETE_VALUE_KEY_INFORMATION is a partially captured
// structure. The value name's buffer is not captured. Since we are
// passing the name to a zw* method, we need to capture it.
//
// *Note: as of Win8, the data buffer is captured already
// by the registry.
//
Mode = ExGetPreviousMode();
if (!g_IsWin8OrGreater && (Mode == UserMode)) {
Status = CaptureUnicodeString(&LocalValueName,
PreDeleteValueInfo->ValueName,
REGFLTR_CAPTURE_POOL_TAG);
if (!NT_SUCCESS(Status)) {
break;
}
ValueName = &LocalValueName;
} else {
ValueName = PreDeleteValueInfo->ValueName;
}
//
// Get a handle to the root key the value is being created under.
// This is in PreInfo->Object.
//
Status = ObOpenObjectByPointer(PreDeleteValueInfo->Object,
OBJ_KERNEL_HANDLE,
NULL,
KEY_ALL_ACCESS,
NULL,
KernelMode,
&RootKey);
if (!NT_SUCCESS (Status)) {
ErrorPrint("ObObjectByPointer failed. Status 0x%x", Status);
break;
}
//
// Set the value.
//
Status = ZwDeleteValueKey(RootKey,
ValueName);
if(!NT_SUCCESS(Status)) {
ErrorPrint("ZwDeleteValue failed. Status 0x%x",
Status);
ZwClose(RootKey);
break;
}
//
// Finally return STATUS_CALLBACK_BYPASS to tell the registry
// not to proceed with the original registry operation and to return
// STATUS_SUCCESS to the caller.
//
InfoPrint("\tCallback: Delete value %S bypassed.", ValueName->Buffer);
Status = STATUS_CALLBACK_BYPASS;
ZwClose(RootKey);
break;
default:
//
// Do nothing for other notifications
//
break;
}
//
// Free buffers used for capturing user mode values.
//
if (LocalData != NULL){
FreeCapturedBuffer(LocalData, REGFLTR_CAPTURE_POOL_TAG);
}
if (LocalValueName.Buffer != NULL) {
FreeCapturedUnicodeString(&LocalValueName, REGFLTR_CAPTURE_POOL_TAG);
}
return Status;
}
NTSTATUS
CaptureBuffer(
_Outptr_result_maybenull_ PVOID *CapturedBuffer,
_In_reads_bytes_(Length) PVOID Buffer,
_In_ SIZE_T Length,
_In_ ULONG PoolTag
)
/*++
Routine Description:
Captures a buffer using allocations with the specified pool tag. Captured
buffer should be freed using FreeCapturedBuffer.
Arguments:
CapturedBuffer - pointer to a variable that receives the location of the
captured buffer.
Buffer - the buffer to capture
Length - Length of Buffer
PoolTag - pool tag
Return Value:
NTSTATUS
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
PVOID TempBuffer = NULL;
NT_ASSERT(CapturedBuffer != NULL);
if (Length == 0) {
*CapturedBuffer = NULL;
return Status;
}
TempBuffer = (PCALLBACK_CONTEXT) ExAllocatePoolZero(
PagedPool,
Length,
PoolTag);
//
// It's a good practice to keep the contents of a try-except block to
// the bare minimum. By keeping the pool allocation call outside of the
// try-except block we don't mask possible pool corruptions.
//
if (TempBuffer != NULL) {
try {
RtlCopyMemory(TempBuffer, Buffer, Length);
} except (ExceptionFilter(GetExceptionInformation())) {
ErrorPrint("Capturing buffer failed with exception");
ExFreePoolWithTag(TempBuffer, PoolTag);
TempBuffer = NULL;
Status = GetExceptionCode();
}
} else {
ErrorPrint("Capturing buffer failed wtih insufficient resources");
Status = STATUS_INSUFFICIENT_RESOURCES;
}
*CapturedBuffer = TempBuffer;
return Status;
}
VOID
FreeCapturedBuffer(
_In_ PVOID CapturedBuffer,
_In_ ULONG PoolTag
)
/*++
Routine Description:
Frees a captured buffer.
Arguments:
CapturedBuffer - captured buffer
PoolTag - pool tag
--*/
{
if (CapturedBuffer != NULL) {
ExFreePoolWithTag(CapturedBuffer, PoolTag);
}
}
NTSTATUS
CaptureUnicodeString(
_Inout_ UNICODE_STRING *DestString,
_In_ PCUNICODE_STRING SourceString,
_In_ ULONG PoolTag
)
/*++
Routine Description:
Captures a unicode string. The buffer is captured based on SourceString's
Length field with the addition of sizeof(WCHAR) bytes for a NULL to
signal the end of the string.
Use FreeCapturedUnicodeString to free the captured string.
Arguments:
DestString - Pointer to the unicode string that will receive the
captured buffer.
SourceString - Pointer tot he unicode string to be captured.
PoolTag - pool tag
Return Value:
NTSTATUS
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
if (SourceString->Length == 0) {
DestString->Length = 0;
DestString->Buffer = NULL;
DestString->MaximumLength = 0;
return Status;
}
//
// Only SourceString->Length should be checked. The registry does not
// validate SourceString->MaximumLength.
//
// An additional sizeof(WCHAR) bytes are added to the buffer size since
// SourceString->Length does not include the NULL at the end of the string.
//
DestString->Length = SourceString->Length;
DestString->MaximumLength = SourceString->Length + sizeof(WCHAR);
DestString->Buffer = (PWSTR) ExAllocatePoolZero(
PagedPool,
DestString->MaximumLength,
PoolTag);
if (DestString->Buffer != NULL) {
//
// It's a good practice to keep the contents of a try-except block to
// the bare minimum. By keeping the pool allocation call outside of the
// try-except block we don't mask possible pool corruptions.
//
try {
RtlCopyMemory(DestString->Buffer,
SourceString->Buffer,
SourceString->Length);
} except (ExceptionFilter(GetExceptionInformation())) {
ErrorPrint("Capturing Unicode String failed with exception");
ExFreePoolWithTag(DestString->Buffer, PoolTag);
DestString->Buffer = NULL;
Status = GetExceptionCode();
}
} else {
ErrorPrint("Capturing Unicode String failed wtih insufficient resources");
Status = STATUS_INSUFFICIENT_RESOURCES;
}
if (DestString->Buffer == NULL) {
DestString->Length = 0;
DestString->MaximumLength = 0;
}
return Status;
}
VOID
FreeCapturedUnicodeString(
_In_ UNICODE_STRING *String,
_In_ ULONG PoolTag
)
/*++
Routine Description:
Frees a captured buffer.
Arguments:
CapturedBuffer - captured buffer
PoolTag - pool tag
--*/
{
if (String->Length != 0) {
String->Length = 0;
String->MaximumLength = 0;
FreeCapturedBuffer(String->Buffer, PoolTag);
String->Buffer = NULL;
}
}
|