Merge pull request #2 from vyvojar/master
[WindowsD.git] / driver.c
blob73703ff73b435b421c681ba6cf3a27684b0d769c
1 #include <ntifs.h>
2 #include <ntimage.h>
3 #define _WIND_DRIVER
4 #include "defs.h"
5 #include "wind.h"
6 #include "regint.h"
8 static wind_config_t cfg = {(void*)(-(LONG)sizeof(cfg))};
9 static KMUTEX ioctl_mutex;
13 // What follows is the meat of the whole DSE bypass.
15 // We temporarily flip the ci_Options to not validate, load driver, flip
16 // ci_options back.
18 static void ci_restore()
20 DBG("current ci_Options=%08x\n", *((ULONG*)cfg.ci_opt));
21 cfg.ci_opt[0] = cfg.ci_guess;
22 DBG("now restored ci_Options=%08x\n", *((ULONG*)cfg.ci_opt));
25 static NTSTATUS driver_sideload(PUNICODE_STRING svc)
27 NTSTATUS status;
29 // Clear ci_Options. Daaaaanger zone.
30 cfg.ci_opt[0] = 0;
32 // Now go fetch.
33 status = ZwLoadDriver(svc);
35 // Restore ci_Options.
36 ci_restore();
38 return status;
41 // The rest is just boring driver boilerplate...
42 static NTSTATUS NTAPI dev_open(IN PDEVICE_OBJECT dev, IN PIRP irp)
44 irp->IoStatus.Status = STATUS_SUCCESS;
45 irp->IoStatus.Information = 0;
46 IoCompleteRequest(irp, IO_NO_INCREMENT);
47 return STATUS_SUCCESS;
50 // Restore/remove notify of one potential CM_KEY_BODY.
51 static int notify_unlock(int lock, CM_KEY_BODY *tkb, CM_KEY_BODY *kb)
53 int ret = 0;
54 CM_NOTIFY_BLOCK *nb;
55 DBG("unlock %d %p %p\n",lock,tkb,kb);
57 if (!tkb)
58 return 0;
59 if (tkb->KeyControlBlock != kb->KeyControlBlock)
60 return 0;
61 nb = tkb->NotifyBlock;
62 while (nb) {
63 union {
64 struct {
65 ULONG low:8;
66 ULONG high:8;
67 ULONG rest:14;
69 ULONG n:30;
70 } f;
71 if (nb->KeyControlBlock != kb->KeyControlBlock)
72 goto skipentry;
74 f.n = nb->Filter;
75 DBG("process NB @ %p Filter=%x high=%x low=%x\n",
76 nb, nb->Filter, f.high, f.low);
77 if (!lock && f.low && !f.high) {
78 f.high = f.low;
79 f.low = 0;
80 DBG("unlock: changing filter from %x to %x", nb->Filter, f.n);
81 nb->Filter = f.n;
82 ret++;
84 if (lock && f.high && !f.low) {
85 f.low = f.high;
86 f.high = 0;
87 DBG("re-lock: changing filter from %x to %x", nb->Filter, f.n);
88 nb->Filter = f.n;
89 ret++;
91 skipentry:
92 if (!nb->HiveList.Flink)
93 break;
94 nb = CONTAINING_RECORD(nb->HiveList.Flink,
95 CM_NOTIFY_BLOCK, HiveList);
97 return ret;
100 static NTSTATUS open_key(HANDLE *h, PUNICODE_STRING name)
102 OBJECT_ATTRIBUTES attr = {
103 .Length = sizeof(attr),
104 .Attributes = OBJ_KERNEL_HANDLE|OBJ_CASE_INSENSITIVE,
105 .ObjectName = name
107 return ZwOpenKey(h, KEY_READ, &attr);
110 // Stop/reenable notifications on registry key.
111 static NTSTATUS reg_set_notify(PUNICODE_STRING name, int lock)
113 #define KLOCK_FLAGS (CM_KCB_NO_DELAY_CLOSE|CM_KCB_READ_ONLY_KEY)
114 #define KUNLOCK_MARKER (1<<15)
115 #define NSPAM 6
116 HANDLE harr[NSPAM];
117 CM_KEY_BODY *kbs[NSPAM], *kb;
118 NTSTATUS st;
119 CM_KEY_CONTROL_BLOCK *cb;
120 LIST_ENTRY *kl;
121 void **scan;
122 int i;
123 struct {
124 LIST_ENTRY KeyBodyListHead;
125 CM_KEY_BODY *KeyBodyArray[4];
126 } *cbptr = NULL;
128 // Spam handles to ensure we'll appear in cbptr->KeyBodyListHead.
129 for (i = 0; i < NSPAM; i++) {
130 st = open_key(&harr[i], name);
131 if (!NT_SUCCESS(st))
132 goto out_unspam_zwclose;
134 for (i = 0; i < NSPAM; i++) {
135 st = ObReferenceObjectByHandle(harr[i], KEY_WRITE,
136 *CmKeyObjectType, 0, (void*)&kbs[i], NULL);
137 if (!NT_SUCCESS(st))
138 goto out_unspam_deref;
141 kb = kbs[NSPAM-1];
142 cb = kb->KeyControlBlock;
143 st = STATUS_KEY_DELETED;
144 if (!cb || cb->Delete)
145 goto out_unspam;
147 scan = (void*)cb;
148 st = STATUS_INTERNAL_ERROR;
149 DBG("kb=%p cb=%p, scanning...\n", kb, cb);
150 // Find ourselves in the CM_KEY_CONTROL_BLOCK structure.
151 for (i = 0; i < 512; i++) {
152 DBG("scan near %p = %p %p\n", &scan[i], scan[i], &kb->KeyBodyList)
153 if (scan[i] == &kb->KeyBodyList) {
154 cbptr = (void*)(scan+i-1);
155 break;
158 if (!cbptr) {
159 DBG("cbptr not found\n");
160 goto out_unspam;
163 DBG("cbptr @ %p, offset %p\n", cbptr, ((ULONG_PTR)(((void*)cbptr)-((void*)cb))));
165 // Now process array area.
166 for (i = 0; i < 4; i++)
167 if (notify_unlock(lock, cbptr->KeyBodyArray[i], kb))
168 st = STATUS_SUCCESS;
170 // And list area too.
171 kl = cbptr->KeyBodyListHead.Flink;
172 DBG("kl=%p\n");
173 while (kl && (kl != &cbptr->KeyBodyListHead)) {
174 CM_KEY_BODY *tkb = CONTAINING_RECORD(kl, CM_KEY_BODY, KeyBodyList);
175 if (notify_unlock(lock, tkb, kb))
176 st = STATUS_SUCCESS;
177 kl = kl->Flink;
180 out_unspam:;
181 i = NSPAM;
182 out_unspam_deref:
183 for (int j = 0; j < i; j++)
184 ObDereferenceObject(kbs[j]);
185 i = NSPAM;
186 out_unspam_zwclose:
187 for (int j = 0; j < i; j++)
188 ZwClose(harr[j]);
189 return st;
192 // Apply/remove hard lock.
193 static NTSTATUS reg_set_lock(PUNICODE_STRING name, int lock)
195 HANDLE h;
196 NTSTATUS st;
197 CM_KEY_CONTROL_BLOCK *cb;
198 CM_KEY_BODY *kb;
200 st = open_key(&h, name);
201 if (!NT_SUCCESS(st))
202 return st;
203 st = ObReferenceObjectByHandle(h, KEY_WRITE,
204 *CmKeyObjectType, 0, (void*)&kb, NULL);
206 if (!NT_SUCCESS(st)) {
207 ZwClose(h);
208 return st;
210 cb = kb->KeyControlBlock;
211 st = STATUS_KEY_DELETED;
212 if (!cb || cb->Delete)
213 goto out;
215 DBG("lock=%d, kb=%p, cb=%p, t=%x refc=%u flags=%02x nb=%p\n",
216 lock, kb, cb, kb->Type, cb->RefCount, cb->ExtFlags, kb->NotifyBlock);
218 st = STATUS_SUCCESS;
219 if (lock) {
220 cb->ExtFlags |= KLOCK_FLAGS;
221 } else {
222 cb->ExtFlags &= ~KLOCK_FLAGS;
224 out:;
225 ObDereferenceObject(kb);
226 ZwClose(h);
227 return st;
230 static NTSTATUS regs_do(NTSTATUS (*fn)(PUNICODE_STRING,int), PUNICODE_STRING names,
231 int lock)
233 WCHAR *p = names->Buffer;
234 NTSTATUS status = STATUS_SUCCESS;
236 if (!p)
237 return status;
239 while (*p) {
240 WCHAR *next;
241 UNICODE_STRING split;
242 NTSTATUS item_status;
244 next = wcschr(p, L';');
245 if (next)
246 *next = 0;
248 RtlInitUnicodeString(&split, p);
249 item_status = fn(&split, lock);
250 if (NT_SUCCESS(status) && !NT_SUCCESS(item_status))
251 status = item_status;
252 if (!next)
253 break;
254 p = next+1;
256 return status;
259 static NTSTATUS change_prot(wind_prot_t *req)
261 int getonly;
262 void *proc;
263 NTSTATUS status;
264 if ((getonly = (req->pid < 0)))
265 req->pid = -req->pid;
266 status = PsLookupProcessByProcessId((HANDLE)(req->pid), (PEPROCESS*)&proc);
267 if (!NT_SUCCESS(status))
268 return status;
269 if (cfg.protbit < 0) {
270 WIND_PS_PROTECTION save, *prot = proc + cfg.protofs - 2;
271 memcpy(&save, prot, sizeof(save));
272 if (!getonly)
273 memcpy(prot, &req->prot, sizeof(req->prot));
274 memcpy(&req->prot, &save, sizeof(save));
275 } else {
276 ULONG prev, *prot = proc + cfg.protofs;
277 prev = *prot;
278 if (!getonly)
279 *prot = (prev & (~(1<<cfg.protbit)))
280 | ((!!req->prot.Level) << cfg.protbit);
281 memset(&req->prot, 0, sizeof(req->prot));
282 req->prot.Level = (prev>>cfg.protbit)&1;
284 ObDereferenceObject(proc);
285 return status;
288 static NTSTATUS regcb_set(int enable)
290 static LIST_ENTRY saved_list;
291 static int cleared = 0;
292 if (!cfg.cblist)
293 return STATUS_NOT_SUPPORTED;
294 if (cleared ^ enable)
295 return STATUS_DEVICE_BUSY;
296 if (!enable) {
297 saved_list = *cfg.cblist;
298 InitializeListHead(cfg.cblist);
299 cleared = 1;
300 } else {
301 *cfg.cblist = saved_list;
302 cleared = 0;
304 return STATUS_SUCCESS;
307 // Helper scratch space for parser.
308 typedef struct {
309 wind_pol_ent *ents[WIND_POL_MAX];
310 UCHAR scratch[65536], *p;
311 int nent;
312 } parse_t;
314 // Walk through our custom policies, and patch em up into the system one.
315 static NTSTATUS NTAPI parse_policy(WCHAR *name, ULONG typ, void *data, ULONG len,
316 void *pparse, void *unused)
318 parse_t *parse = pparse;
319 wind_pol_ent *e;
320 int i, nlen;
321 if (!name)
322 return STATUS_SUCCESS;
323 nlen = wcslen(name)*2;
324 DBG("Inside parser, %S, typ=%d, nent=%d %p %p %p\n",name,typ,parse->nent,parse,unused,data);
325 // Sane type.
326 if ((typ != REG_SZ) && (typ != REG_BINARY) && (typ != REG_DWORD))
327 return STATUS_SUCCESS;
328 // Find entry of given name
329 for (i = 0; i < parse->nent; i++) {
330 if (parse->ents[i]->name_sz == nlen
331 && (RtlCompareMemory(parse->ents[i]->name, name, nlen)==nlen)) {
332 DBG("found at index %d\n",i);
333 break;
336 // Allocate scratch space
337 e = (void*)parse->p;
338 parse->p += sizeof(*e) + len + nlen;
339 if (parse->p > (parse->scratch + sizeof(parse->scratch)))
340 return STATUS_SUCCESS;
341 // If name not found, allocate new entry.
342 if (i == parse->nent) {
343 e->flags = 0;
344 if (parse->nent == WIND_POL_MAX)
345 return STATUS_SUCCESS;
346 parse->nent++;
347 } else {
348 // Otherwise we'll overwrite previous entry, preserve flags.
349 e->flags = parse->ents[i]->flags;
351 // Fill in entry. Note that padding (as well as final size)
352 // is done via wind_pol_pack().
353 e->name_sz = nlen;
354 e->type = typ;
355 e->data_sz = len;
356 e->pad0 = 0;
357 memcpy(e->name, name, nlen);
358 memcpy(e->name + nlen, data, len);
359 parse->ents[i] = e;
360 return STATUS_SUCCESS;
363 // System policy has changed, apply our custom rules.
364 static NTAPI void pol_arm_notify(HANDLE key)
366 static WORK_QUEUE_ITEM it;
367 static IO_STATUS_BLOCK io;
368 static struct {
369 KEY_VALUE_PARTIAL_INFORMATION v;
370 UCHAR buf[65536];
371 } vb;
372 static parse_t parse;
373 static UCHAR buf[65536];
374 ULONG got = sizeof(vb);
376 // Grab current view of policy and parse its entries.
377 parse.nent = -1;
378 if (NT_SUCCESS(ZwQueryValueKey(key, &RTL_STRING(L""PRODUCT_POLICY),
379 KeyValuePartialInformation, &vb, sizeof(vb), &got)))
380 parse.nent = wind_pol_unpack(vb.v.Data, parse.ents);
381 if (parse.nent >= 0) {
382 RTL_QUERY_REGISTRY_TABLE qt[2] = { {
383 .QueryRoutine = parse_policy,
384 .Name = L""CUSTOM_POLICY,
385 .Flags = RTL_QUERY_REGISTRY_SUBKEY|RTL_QUERY_REGISTRY_NOEXPAND,
386 },{} };
387 // Now filter it through our own "policy".
388 parse.p = parse.scratch;
389 if (NT_SUCCESS(RtlQueryRegistryValues(0, POLICY_PATH, qt, &parse, NULL))) {
390 // If ok, pack it again
391 int len = wind_pol_pack(buf, parse.ents, parse.nent);
392 // And update cache.
393 if (cfg.pExUpdateLicenseData)
394 cfg.pExUpdateLicenseData(len, buf);
395 else if (cfg.pExUpdateLicenseData2)
396 cfg.pExUpdateLicenseData2(len, buf);
397 ZwSetValueKey(key, &RTL_STRING(L""PRODUCT_POLICY), 0, REG_BINARY, buf, len);
400 DBG("Re-arming notification\n");
401 memset(&it, 0, sizeof(it));
402 it.WorkerRoutine = pol_arm_notify;
403 it.Parameter = key;
404 ZwNotifyChangeKey(key, NULL, (void*)&it, (void*)1,
405 &io, 5, TRUE, NULL, 0, TRUE);
408 static NTSTATUS NTAPI dev_control(IN PDEVICE_OBJECT dev, IN PIRP irp)
410 PIO_STACK_LOCATION io_stack;
411 ULONG code;
412 NTSTATUS status = STATUS_INVALID_PARAMETER;
413 UNICODE_STRING us;
414 void *buf;
415 int len,onoff;
417 KeWaitForMutexObject(&ioctl_mutex, UserRequest, KernelMode, FALSE, NULL);
419 io_stack = IoGetCurrentIrpStackLocation(irp);
420 if (!io_stack)
421 goto out;
423 buf = irp->AssociatedIrp.SystemBuffer;
424 len = io_stack->Parameters.DeviceIoControl.InputBufferLength;
425 code = io_stack->Parameters.DeviceIoControl.IoControlCode;
427 irp->IoStatus.Information = 0;
429 if (!SeSinglePrivilegeCheck(LUID_SeLoadDriverPrivilege, irp->RequestorMode)) {
430 status = STATUS_PRIVILEGE_NOT_HELD;
431 goto out;
434 status = STATUS_INVALID_BUFFER_SIZE;
435 DBG("code=%08x\n",(unsigned)code);
437 // codes 0x90x and 0x81x need buffer.
438 if ((code & ((0x110)<<2)) && (!buf))
439 goto out;
441 // 0x10 marks string argument.
442 if (code & (0x10 << 2)) {
443 // must be at least 2 long, must be even, must terminate with 0
444 if ((len < 2) || (len&1) || (*((WCHAR*)(buf+len-2))!=0))
445 goto out;
447 us.Buffer = (void*)buf;
448 us.Length = len-2;
449 us.MaximumLength = len;
452 onoff = (code>>2)&1;
453 switch (code) {
454 case WIND_IOCTL_INSMOD:
455 status = driver_sideload(&us);
456 break;
457 case WIND_IOCTL_REGLOCKON:
458 case WIND_IOCTL_REGLOCKOFF:
459 status = regs_do(reg_set_lock, &us, onoff);
460 break;
461 case WIND_IOCTL_REGNON:
462 case WIND_IOCTL_REGNOFF:
463 status = regs_do(reg_set_notify, &us, onoff);
464 break;
465 case WIND_IOCTL_PROT:
466 if (len != sizeof(wind_prot_t))
467 goto out;
468 status = change_prot(buf);
469 irp->IoStatus.Information = len;
470 break;
471 case WIND_IOCTL_REGCBON:
472 case WIND_IOCTL_REGCBOFF:
473 status = regcb_set(onoff);
474 break;
476 out:;
477 KeReleaseMutex(&ioctl_mutex, 0);
478 irp->IoStatus.Status = status;
479 IoCompleteRequest(irp, IO_NO_INCREMENT);
480 return status;
483 static VOID NTAPI dev_unload(IN PDRIVER_OBJECT self)
485 DBG("unloading!\n");
486 // Restore callbacks.
487 regcb_set(1);
488 // Nuke our own notify, so that kernel does not call junk.
489 reg_set_notify(&RTL_STRING(POLICY_PATH), 0);
490 IoDeleteDevice(self->DeviceObject);
493 static int k_brute()
495 UCHAR *p = MmGetSystemRoutineAddress(&RTL_STRING(L"MmMapViewInSessionSpace"));
496 DBG("marker at %p\n", p);
497 if (!p) return 0;
498 for (int i = 0; i < 256*1024; i++, p--) {
499 #ifdef _WIN64
500 if (EQUALS(p + 14,"\x48\x81\xec\xa0\x04\x00\x00") && p[0] == 0x48)
501 #else
502 if (EQUALS(p,"\x68\x28\x04\x00\x00\x68"))
503 #endif
505 DBG("ExUpdateLicenseData guessed at %p\n", p);
506 cfg.pExUpdateLicenseData2 = (void*)p;
507 return 1;
510 DBG("Even the brute guess failed.\n");
511 return 0;
514 static int k_analyze()
516 int i;
517 UCHAR *p = (void*)MmGetSystemRoutineAddress(&RTL_STRING(L"PsGetProcessProtection"));
518 cfg.protbit = -1;
519 cfg.protofs = 0;
520 if (!p) {
521 cfg.protbit = 11;
522 p = (void*)MmGetSystemRoutineAddress(&RTL_STRING(L"PsIsProtectedProcess"));
523 // mov
524 for (i = 0; i < 64; i++, p++)
525 // mov eax, [anything + OFFSET]; shr eax, 11
526 if (RtlCompareMemory(p+2, "\x00\x00\xc1\xe8\x0b",5)==5)
527 goto protfound;
528 } else {
529 // mov al, [anything+OFFSET]
530 for (i =0 ; i < 64; i++, p++)
531 if ((p[-2] == 0x8a) && (!p[2] && !p[3]))
532 goto protfound;
534 DBG("failed to find protbit\n");
535 return 0;
536 protfound:;
537 cfg.protofs = *((ULONG*)p);
538 DBG("prot done");
540 p = (void*)MmGetSystemRoutineAddress(&RTL_STRING(L"CmUnRegisterCallback"));
541 if (!p) goto nocb;
542 for (i = 0; i < 512; i++, p++) {
543 #ifdef _WIN64
544 // lea rcx, cblist; call ..; mov rdi, rax
545 if (p[-3] == 0x48 && p[-2] == 0x8d && p[-1] == 0x0d &&
546 p[4] == 0xe8 && p[9] == 0x48 && p[10] == 0x8b && p[11] == 0xf8) {
547 cfg.cblist = (void*)((p + 4) + *((LONG*)p));
548 break;
551 #else
552 // mov edi, offset cblist; mov eax, edi; call
553 if ((p[-1] == 0xbf && p[4] == 0x8b && p[5] == 0xc7 && p[6] == 0xe8) ||
554 (p[-1] == 0xbe && p[4] == 0x53 && p[5] == 0x8d && p[6] == 0x55)) {
555 cfg.cblist = *((void**)p);
556 break;
558 #endif
560 nocb:;
561 DBG("CallbackListHead @ %p", cfg.cblist);
562 cfg.pExUpdateLicenseData = MmGetSystemRoutineAddress(&RTL_STRING(L"ExUpdateLicenseData"));
563 if (cfg.pExUpdateLicenseData)
564 return 1;
566 return k_brute();
570 NTSTATUS NTAPI ENTRY(driver_entry)(IN PDRIVER_OBJECT self, IN PUNICODE_STRING reg)
572 PDEVICE_OBJECT dev;
573 NTSTATUS status;
574 UNICODE_STRING regs[4]={{0}};
575 RTL_QUERY_REGISTRY_TABLE tab[] = {{
576 .Flags = RTL_QUERY_REGISTRY_DIRECT
577 |RTL_QUERY_REGISTRY_TYPECHECK
578 |RTL_QUERY_REGISTRY_REQUIRED
579 #ifdef NDEBUG
580 |RTL_QUERY_REGISTRY_DELETE
581 #endif
583 .Name = L"cfg",
584 .EntryContext = &cfg,
585 .DefaultType = (REG_BINARY<<RTL_QUERY_REGISTRY_TYPECHECK_SHIFT)
586 |REG_NONE
588 .Flags = RTL_QUERY_REGISTRY_DIRECT
589 |RTL_QUERY_REGISTRY_TYPECHECK,
590 .DefaultType = (REG_SZ<<RTL_QUERY_REGISTRY_TYPECHECK_SHIFT),
591 .Name = L"RD",
592 .EntryContext = regs,
594 .Flags = RTL_QUERY_REGISTRY_DIRECT
595 |RTL_QUERY_REGISTRY_TYPECHECK,
596 .DefaultType = (REG_SZ<<RTL_QUERY_REGISTRY_TYPECHECK_SHIFT),
597 .Name = L"RE",
598 .EntryContext = regs+1,
600 .Flags = RTL_QUERY_REGISTRY_DIRECT
601 |RTL_QUERY_REGISTRY_TYPECHECK,
602 .DefaultType = (REG_SZ<<RTL_QUERY_REGISTRY_TYPECHECK_SHIFT),
603 .Name = L"ND",
604 .EntryContext = regs+2,
606 .Flags = RTL_QUERY_REGISTRY_DIRECT
607 |RTL_QUERY_REGISTRY_TYPECHECK,
608 .DefaultType = (REG_SZ<<RTL_QUERY_REGISTRY_TYPECHECK_SHIFT),
609 .Name = L"NE",
610 .EntryContext = regs+3,
612 {}};
614 status = RtlQueryRegistryValues(0, reg->Buffer, tab, NULL, NULL);
615 if (!NT_SUCCESS(status)) {
616 DBG("registry read failed=%x\n",(unsigned)status);
617 return status;
619 self->DriverUnload = dev_unload;
620 self->MajorFunction[IRP_MJ_CREATE] = dev_open;
621 self->MajorFunction[IRP_MJ_DEVICE_CONTROL] = dev_control;
623 status = IoCreateDevice(self, 0, &RTL_STRING(L"\\Device\\" WIND_DEVNAME),
624 FILE_DEVICE_UNKNOWN, 0, 0, &dev);
626 if (!NT_SUCCESS(status)) {
627 DBG("failed to create device=%08x\n",(unsigned)status);
628 return status;
631 if (cfg.ci_orig)
632 cfg.ci_guess = *cfg.ci_orig;
633 ci_restore();
635 KeInitializeMutex(&ioctl_mutex, 0);
636 KeWaitForMutexObject(&ioctl_mutex, UserRequest, KernelMode, FALSE, NULL);
638 dev->Flags |= METHOD_BUFFERED;
639 dev->Flags &= ~DO_DEVICE_INITIALIZING;
641 if (cfg.bootreg) {
642 regs_do(reg_set_lock, regs, 0);
643 regs_do(reg_set_lock, regs+1, 1);
644 regs_do(reg_set_notify, regs+2, 0);
645 regs_do(reg_set_notify, regs+3, 1);
648 if (k_analyze()) {
649 HANDLE kh;
650 reg_set_notify(&RTL_STRING(POLICY_PATH), 0);
651 if (NT_SUCCESS(open_key(&kh, &RTL_STRING(POLICY_PATH))))
652 pol_arm_notify(kh);
655 DBG("initialized driver with:\n"
656 " .ci_opt = %p\n"
657 " .ci_orig = %p\n"
658 " .ci_guess = %02x\n"
659 " .protofs = %x\n"
660 " .protbit = %d\n", cfg.ci_opt, cfg.ci_orig, cfg.ci_guess,
661 cfg.protofs, cfg.protbit);
663 KeReleaseMutex(&ioctl_mutex, 0);
664 DBG("loaded driver\n");
665 return status;