#include "precomp.h" enum { etc_ohci1394 = 0, etc_Serenum, etc_Parport, etc_irda, etc_modem, etc_maximum }; static WCHAR* s_etcname[] = { L"\\Driver\\ohci1394", L"\\Driver\\Serenum", L"\\Driver\\Parport", L"\\Driver\\irda", L"\\Driver\\modem", NULL }; static BOOLEAN enable_etchook = FALSE; NTSTATUS Ohci139DeviceControl(PDRIVER_DISPATCH dispatch, PDEVICE_OBJECT deviceObject, PIRP irp); NTSTATUS SerenumlWrite(PDRIVER_DISPATCH dispatch, PDEVICE_OBJECT deviceObject, PIRP irp); NTSTATUS ParportWrite(PDRIVER_DISPATCH dispatch, PDEVICE_OBJECT deviceObject, PIRP irp); NTSTATUS IrdaInternalDeviceControl(PDRIVER_DISPATCH dispatch, PDEVICE_OBJECT deviceObject, PIRP irp); NTSTATUS ModemWrite(PDRIVER_DISPATCH dispatch, PDEVICE_OBJECT deviceObject, PIRP irp); NTSTATUS ohci1394HookDispatch_0(PDEVICE_OBJECT DeviceObject, PIRP Irp); NTSTATUS SerenumHookDispatch_0(PDEVICE_OBJECT DeviceObject, PIRP Irp); NTSTATUS ParportHookDispatch_0(PDEVICE_OBJECT DeviceObject, PIRP Irp); NTSTATUS IrdaHookDispatch_0(PDEVICE_OBJECT DeviceObject, PIRP Irp); NTSTATUS ModemHookDispatch_0(PDEVICE_OBJECT DeviceObject, PIRP Irp); static PDRIVER_DISPATCH s_ProxyDispatchers[etc_maximum] = { ohci1394HookDispatch_0, SerenumHookDispatch_0, ParportHookDispatch_0, IrdaHookDispatch_0, ModemHookDispatch_0 }; #define OHCI1394_COMMON_HOOK_HANDLERS \ [IRP_MJ_DEVICE_CONTROL] = { NULL, IRP_MJ_DEVICE_CONTROL, TRUE, Ohci139DeviceControl }, \ #define SERENUM_COMMON_HOOK_HANDLERS \ [IRP_MJ_WRITE] = { NULL, IRP_MJ_WRITE, TRUE, SerenumlWrite }, \ #define PARPORT_COMMON_HOOK_HANDLERS \ [IRP_MJ_WRITE] = { NULL, IRP_MJ_WRITE, TRUE, ParportWrite }, \ #define IRDA_COMMON_HOOK_HANDLERS \ [IRP_MJ_INTERNAL_DEVICE_CONTROL] = { NULL, IRP_MJ_INTERNAL_DEVICE_CONTROL, TRUE, IrdaInternalDeviceControl }, \ #define MODEM_COMMON_HOOK_HANDLERS \ [IRP_MJ_WRITE] = { NULL, IRP_MJ_WRITE, TRUE, ModemWrite }, \ static HOOK_CONTEXT g_EtcHookContexts[etc_maximum] = { { NULL, FALSE, 0, { OHCI1394_COMMON_HOOK_HANDLERS } }, { NULL, FALSE, 0, { SERENUM_COMMON_HOOK_HANDLERS } }, { NULL, FALSE, 0, { PARPORT_COMMON_HOOK_HANDLERS } }, { NULL, FALSE, 0, { IRDA_COMMON_HOOK_HANDLERS } }, { NULL, FALSE, 0, { MODEM_COMMON_HOOK_HANDLERS } }, }; NTSTATUS Ohci139DeviceControl(PDRIVER_DISPATCH dispatch, PDEVICE_OBJECT deviceObject, PIRP irp) { NTSTATUS status = STATUS_SUCCESS; PIO_STACK_LOCATION irpstack = IoGetCurrentIrpStackLocation(irp); ULONG controlCode = irpstack->Parameters.DeviceIoControl.IoControlCode; ULONG ProcessId = 0; ULONG state = 0; ULONG policyLog = 0; char szProcessName[20] = { 0, }; wchar_t processName[50] = { 0, }; wchar_t notice[50] = { 0, }; if (!g_bs1Flt.IsAttached || !enable_etchook) return dispatch(deviceObject, irp); ProcessId = HandleToULong(PsGetCurrentProcessId()); UGetProcessName(szProcessName); state = GetPolicyState(BDC_1394); policyLog = IsPolicyLog(BDC_1394); if (state == DISABLE) { KLogEx(DEBUG_TRACE_INFO, "block!!!"); if (policyLog) { RtlStringCbPrintfW(processName, sizeof(processName), L"%S", szProcessName); RtlStringCbPrintfW(notice, sizeof(notice), L"1394 Blocked control(%x)", controlCode); SetLog(NULL, NULL, LOG_POLICY, BDC_1394, state, 0, processName, notice); } irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST; irp->IoStatus.Information = 0; IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_INVALID_DEVICE_REQUEST; } status = dispatch(deviceObject, irp); return status; } NTSTATUS SerenumlWrite(PDRIVER_DISPATCH dispatch, PDEVICE_OBJECT deviceObject, PIRP irp) { NTSTATUS status = STATUS_SUCCESS; PIO_STACK_LOCATION irpstack = IoGetCurrentIrpStackLocation(irp); ULONG len = irpstack->Parameters.Write.Length; ULONG ProcessId = 0; ULONG state = 0; ULONG policyLog = 0; char szProcessName[20] = { 0, }; wchar_t processName[50] = { 0, }; wchar_t notice[50] = { 0, }; if (!g_bs1Flt.IsAttached || !enable_etchook) return dispatch(deviceObject, irp); ProcessId = HandleToULong(PsGetCurrentProcessId()); UGetProcessName(szProcessName); state = GetPolicyState(BDC_SERIAL); policyLog = IsPolicyLog(BDC_SERIAL); if (state == DISABLE) { KLogEx(DEBUG_TRACE_INFO, "block!!!"); if (policyLog) { RtlStringCbPrintfW(processName, sizeof(processName), L"%S", szProcessName); RtlStringCbPrintfW(notice, sizeof(notice), L"Serial Blocked len(%x)", len); SetLog(NULL, NULL, LOG_POLICY, BDC_SERIAL, state, 0, processName, notice); } irp->IoStatus.Status = STATUS_UNSUCCESSFUL; irp->IoStatus.Information = 0; IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_UNSUCCESSFUL; } status = dispatch(deviceObject, irp); return status; } NTSTATUS ParportWrite(PDRIVER_DISPATCH dispatch, PDEVICE_OBJECT deviceObject, PIRP irp) { NTSTATUS status = STATUS_SUCCESS; PIO_STACK_LOCATION irpstack = IoGetCurrentIrpStackLocation(irp); ULONG len = irpstack->Parameters.Write.Length; ULONG ProcessId = 0; ULONG state = 0; ULONG policyLog = 0; char szProcessName[20] = { 0, }; wchar_t processName[50] = { 0, }; wchar_t notice[50] = { 0, }; if (!g_bs1Flt.IsAttached || !enable_etchook) return dispatch(deviceObject, irp); ProcessId = HandleToULong(PsGetCurrentProcessId()); UGetProcessName(szProcessName); state = GetPolicyState(BDC_PARALLEL); policyLog = IsPolicyLog(BDC_PARALLEL); if (state == DISABLE) { KLogEx(DEBUG_TRACE_INFO, "block!!!"); if (policyLog) { RtlStringCbPrintfW(processName, sizeof(processName), L"%S", szProcessName); RtlStringCbPrintfW(notice, sizeof(notice), L"Parport Blocked len(%x)", len); SetLog(NULL, NULL, LOG_POLICY, BDC_PARALLEL, state, 0, processName, notice); } irp->IoStatus.Status = STATUS_UNSUCCESSFUL; irp->IoStatus.Information = 0; IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_UNSUCCESSFUL; } status = dispatch(deviceObject, irp); return status; } static PFILE_OBJECT gFileObject = NULL; NTSTATUS IrdaInternalDeviceControl(PDRIVER_DISPATCH dispatch, PDEVICE_OBJECT deviceObject, PIRP irp) { NTSTATUS status = STATUS_SUCCESS; PIO_STACK_LOCATION irpstack = IoGetCurrentIrpStackLocation(irp); ULONG len = irpstack->Parameters.DeviceIoControl.InputBufferLength; PFILE_OBJECT pFileObject = irpstack->FileObject; ULONG ProcessId = 0; ULONG state = 0; ULONG policyLog = 0; PCHAR pBuffer = NULL; char szProcessName[20] = { 0, }; wchar_t processName[50] = { 0, }; wchar_t notice[50] = { 0, }; if (!g_bs1Flt.IsAttached || !enable_etchook) return dispatch(deviceObject, irp); ProcessId = HandleToULong(PsGetCurrentProcessId()); UGetProcessName(szProcessName); state = GetPolicyState(BDC_IRDA); policyLog = IsPolicyLog(BDC_IRDA); if (state == DISABLE) { KLogEx(DEBUG_TRACE_INFO, "block!!!"); if (policyLog) { RtlStringCbPrintfW(processName, sizeof(processName), L"%S", szProcessName); RtlStringCbPrintfW(notice, sizeof(notice), L"Irda Blocked len(%x)", len); SetLog(NULL, NULL, LOG_POLICY, BDC_IRDA, state, 0, processName, notice); } irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST; irp->IoStatus.Information = 0; IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_INVALID_DEVICE_REQUEST; } if(!policyLog) return dispatch(deviceObject, irp); if (!irp->MdlAddress || !irp->MdlAddress->ByteCount || !irpstack->FileObject) return dispatch(deviceObject, irp); pBuffer = MmGetSystemAddressForMdl(irp->MdlAddress); if (!pBuffer) return dispatch(deviceObject, irp); __try { RtlStringCbPrintfW(processName, sizeof(processName), L"%S", szProcessName); POBEX_COMMON_HEADER pCommon = (POBEX_COMMON_HEADER)pBuffer; KLogEx(DEBUG_TRACE_INFO, "Opcode(%X)", pCommon->Opcode); switch (pCommon->Opcode) { case OBEX_OPCODE_CONNECT: { KLogEx(DEBUG_TRACE_INFO, "OBEX_OPCODE_CONNECT(%p)", pFileObject); POBEX_CONNECT_PACKET pConn = (POBEX_CONNECT_PACKET)pBuffer; USHORT hostPacketLen = 0; USHORT hostMaxPktSize = 0; if (irp->MdlAddress->ByteCount < sizeof(OBEX_CONNECT_PACKET)) { KLogEx(DEBUG_TRACE_INFO, "Data too short for OBEX Connect"); break; } hostPacketLen = SWAP_USHORT(pConn->PacketLength); hostMaxPktSize = SWAP_USHORT(pConn->MaxPacketSize); RtlStringCbPrintfW(notice, sizeof(notice), L"OBEX_OPCODE_CONNECT, hostPacketLen(%d), version(0x%x), flag(%d), hostMaxPktSize(%d)", hostPacketLen, pConn->Version, pConn->Flags, hostMaxPktSize); KLogEx(DEBUG_TRACE_INFO, "file blocked, (%S)", notice); SetLog(NULL, NULL, LOG_POLICY, BDC_IRDA, state, 0, processName, notice); gFileObject = pFileObject; } break; case OBEX_OPCODE_DISCONNECT: // Disconnect { KLogEx(DEBUG_TRACE_INFO, "OBEX_OPCODE_DISCONNECT(%p)", pFileObject); RtlStringCbPrintfW(notice, sizeof(notice), L"OBEX_OPCODE_DISCONNECT, ByteCount(%d)", irp->MdlAddress->ByteCount); KLogEx(DEBUG_TRACE_INFO, "(%S)", notice); SetLog(NULL, NULL, LOG_POLICY, BDC_IRDA, state, 0, processName, notice); gFileObject = NULL; } break; case OBEX_OPCODE_PUT: // Put case OBEX_OPCODE_PUT_FINAL: // Put Final { KLogEx(DEBUG_TRACE_INFO, "OBEX_OPCODE_PUT,OBEX_OPCODE_PUT_FINAL(%p)", pFileObject); ULONG currentOffset = 3; WCHAR szFileName[260] = { 0 }; ULONG packetLen = irp->MdlAddress->ByteCount; PUCHAR pData = (PUCHAR)pBuffer; if (gFileObject != pFileObject) break; while (currentOffset < packetLen) { UCHAR headerId = pData[currentOffset]; if (currentOffset + 3 > packetLen) break; // OBEX Çì´õ´Â »óÀ§ 2ºñÆ®¿¡ µû¶ó ÀÎÄÚµù ¹æ½ÄÀÌ ´Ù¸§ // 00xxxxxx: Unicode String (Length Æ÷ÇÔ) -> Name Çì´õ°¡ ¿©±â ¼ÓÇÔ // 01xxxxxx: Byte Sequence (Length Æ÷ÇÔ) // 10xxxxxx: 1 Byte Value (Length ÇÊµå ¾øÀ½) // 11xxxxxx: 4 Byte Value (Length ÇÊµå ¾øÀ½) if (headerId == OBEX_OPCODE_NAME) // 0x01: ÆÄÀÏ¸í ¹ß°ß! { USHORT headerLen = 0; // Length Çʵå´Â Big Endian (2 bytes) ((PUCHAR)&headerLen)[1] = pData[currentOffset + 1]; ((PUCHAR)&headerLen)[0] = pData[currentOffset + 2]; // ½ÇÁ¦ À̸§ ±æÀÌ = Çì´õ±æÀÌ - (ID 1byte + Length 2bytes) // OBEX NameÀº Null-terminated UnicodeÀÓ (¸¶Áö¸· 2¹ÙÀÌÆ® 0x00 0x00 Æ÷ÇÔ) if (headerLen > 5 && (currentOffset + headerLen <= packetLen)) { ULONG nameByteLen = headerLen - 3; // ¼ø¼ö ¹®ÀÚ¿­ ¹ÙÀÌÆ® ¼ö (Çì´õ ¿À¹öÇìµå 3¹ÙÀÌÆ® Á¦¿Ü) if (nameByteLen >= sizeof(szFileName)) nameByteLen = sizeof(szFileName) - 2; // ÆÄÀÏ¸í º¹»ç (pData + offset + 3) RtlCopyMemory(szFileName, &pData[currentOffset + 3], nameByteLen); for (ULONG k = 0; k < nameByteLen / 2; k++) szFileName[k] = SWAP_USHORT(szFileName[k]); szFileName[nameByteLen / 2] = 0; KLogEx(DEBUG_TRACE_INFO, "File Name Found: %ws", szFileName); RtlStringCbPrintfW(notice, sizeof(notice), L"File Transfer: %s, packetLen: %d", szFileName, packetLen); } break; // À̸§À» ã¾ÒÀ¸¸é ·çÇÁ Á¾·á (ÃÖÀûÈ­) } // ´ÙÀ½ Çì´õ·Î À̵¿Çϱâ À§ÇÑ ¿ÀÇÁ¼Â °è»ê if ((headerId & 0xC0) == 0x00 || (headerId & 0xC0) == 0x40) { // Length Çʵ尡 Àִ ŸÀÔ (String, Sequence) USHORT chunkLen = 0; ((PUCHAR)&chunkLen)[1] = pData[currentOffset + 1]; ((PUCHAR)&chunkLen)[0] = pData[currentOffset + 2]; if (chunkLen == 0) break; // ¹«ÇÑ·çÇÁ ¹æÁö currentOffset += chunkLen; } else if ((headerId & 0xC0) == 0x80) { // 1 Byte Value (ÃÑ 2¹ÙÀÌÆ®: ID + Value) currentOffset += 2; } else if ((headerId & 0xC0) == 0xC0) { // 4 Byte Value (ÃÑ 5¹ÙÀÌÆ®: ID + Value 4bytes) currentOffset += 5; } } if (notice[0] == 0) RtlStringCbPrintfW(notice, sizeof(notice), L"File Transfer: unknown, packetLen: %d", packetLen); SetLog(NULL, NULL, LOG_POLICY, BDC_IRDA, state, 0, processName, notice); } break; case OBEX_OPCODE_OK: KLogEx(DEBUG_TRACE_INFO, "OBEX_OPCODE_OK"); break; case OBEX_OPCODE_CONTINUE: KLogEx(DEBUG_TRACE_INFO, "OBEX_OPCODE_CONTINUE"); break; case OBEX_OPCODE_BODY: KLogEx(DEBUG_TRACE_INFO, "OBEX_OPCODE_BODY"); break; case OBEX_OPCODE_END_BODY: KLogEx(DEBUG_TRACE_INFO, "OBEX_OPCODE_END_BODY"); break; case OBEX_OPCODE_VERSION: KLogEx(DEBUG_TRACE_INFO, "OBEX_OPCODE_VERSION"); break; case OBEX_OPCODE_CONN_FLAGS: KLogEx(DEBUG_TRACE_INFO, "OBEX_OPCODE_CONN_FLAGS"); break; case OBEX_OPCODE_NAME: KLogEx(DEBUG_TRACE_INFO, "OBEX_OPCODE_NAME"); break; case OBEX_OPCODE_LENGTH: KLogEx(DEBUG_TRACE_INFO, "OBEX_OPCODE_LENGTH"); break; default: KLogEx(DEBUG_TRACE_INFO, "default OBEX_OPCODE"); break; } } __except (EXCEPTION_EXECUTE_HANDLER) { NTSTATUS exceptStatus = GetExceptionCode(); KLogEx(DEBUG_TRACE_ERROR, "Exception accessing buffer: 0x%X", exceptStatus); } status = dispatch(deviceObject, irp); return status; } NTSTATUS ModemWrite(PDRIVER_DISPATCH dispatch, PDEVICE_OBJECT deviceObject, PIRP irp) { NTSTATUS status = STATUS_SUCCESS; PIO_STACK_LOCATION irpstack = IoGetCurrentIrpStackLocation(irp); ULONG len = irpstack->Parameters.Write.Length; ULONG ProcessId = 0; ULONG state = 0; ULONG policyLog = 0; char szProcessName[20] = { 0, }; wchar_t processName[50] = { 0, }; wchar_t notice[50] = { 0, }; if (!g_bs1Flt.IsAttached || !enable_etchook) return dispatch(deviceObject, irp); ProcessId = HandleToULong(PsGetCurrentProcessId()); UGetProcessName(szProcessName); state = GetPolicyState(BDC_MODEM); policyLog = IsPolicyLog(BDC_MODEM); if (state == DISABLE) { KLogEx(DEBUG_TRACE_INFO, "block!!!"); if (policyLog) { RtlStringCbPrintfW(processName, sizeof(processName), L"%S", szProcessName); RtlStringCbPrintfW(notice, sizeof(notice), L"Modem Blocked len(%x)", len); SetLog(NULL, NULL, LOG_POLICY, BDC_MODEM, state, 0, processName, notice); } irp->IoStatus.Status = STATUS_UNSUCCESSFUL; irp->IoStatus.Information = 0; IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_UNSUCCESSFUL; } status = dispatch(deviceObject, irp); return status; } NTSTATUS EtcHookDispatch_Common(ULONG ContextIndex, PDEVICE_OBJECT deviceObject, PIRP irp) { NTSTATUS NtStatus = STATUS_SUCCESS; PIO_STACK_LOCATION irpstack = IoGetCurrentIrpStackLocation(irp); ULONG id = irpstack->MajorFunction; PHOOK_CONTEXT pCtx = NULL; if (ContextIndex >= etc_maximum) { return STATUS_UNSUCCESSFUL; } InterlockedIncrement((volatile LONG*)&g_EtcHookContexts[ContextIndex].IrpEnterCount); pCtx = &g_EtcHookContexts[ContextIndex]; if (pCtx->IsHooked && pCtx->HookHandlers[id].IsHook && pCtx->HookHandlers[id].Work) { NtStatus = pCtx->HookHandlers[id].Work(pCtx->HookHandlers[id].pOrgHandler, deviceObject, irp); } else { NtStatus = STATUS_UNSUCCESSFUL; } InterlockedDecrement((volatile LONG*)&g_EtcHookContexts[ContextIndex].IrpEnterCount); return NtStatus; } NTSTATUS ohci1394HookDispatch_0(PDEVICE_OBJECT deviceObject, PIRP irp) { return EtcHookDispatch_Common(etc_ohci1394, deviceObject, irp); } NTSTATUS SerenumHookDispatch_0(PDEVICE_OBJECT deviceObject, PIRP irp) { return EtcHookDispatch_Common(etc_Serenum, deviceObject, irp); } NTSTATUS ParportHookDispatch_0(PDEVICE_OBJECT deviceObject, PIRP irp) { return EtcHookDispatch_Common(etc_Parport, deviceObject, irp); } NTSTATUS IrdaHookDispatch_0(PDEVICE_OBJECT deviceObject, PIRP irp) { return EtcHookDispatch_Common(etc_irda, deviceObject, irp); } NTSTATUS ModemHookDispatch_0(PDEVICE_OBJECT deviceObject, PIRP irp) { return EtcHookDispatch_Common(etc_modem, deviceObject, irp); } NTSTATUS EtcIrpHookInit() { WCHAR* name = NULL; ULONG i, mi; PDRIVER_OBJECT obj = NULL; PHOOK_CONTEXT hook = NULL; for (i = 0; i < etc_maximum; i++) { hook = &g_EtcHookContexts[i]; if (hook->IsHooked) continue; name = s_etcname[i]; if (name == NULL) continue; KLogEx(DEBUG_TRACE_INFO, "Target driver(%S)\n", name); obj = SearchDriverObject(name); if (!obj) { KLogEx(DEBUG_TRACE_ERROR, "Not found object (%S)\n", name); continue; } hook->DriverObject = obj; for (mi = 0; mi < IRP_MJ_MAXIMUM_FUNCTION + 1; mi++) { if (!hook->HookHandlers[mi].IsHook) continue; if (obj->MajorFunction[mi] == s_ProxyDispatchers[i]) continue; hook->HookHandlers[mi].pOrgHandler = obj->MajorFunction[mi]; if (hook->HookHandlers[mi].pOrgHandler == NULL) continue; InterlockedExchangePointer((PVOID)&obj->MajorFunction[mi], (PVOID)s_ProxyDispatchers[i]); KLogEx(DEBUG_TRACE_INFO, "[Hook] %S MJ:%d Org:%p New:%p\n", name, mi, hook->HookHandlers[mi].pOrgHandler, s_ProxyDispatchers[i]); } hook->IsHooked = TRUE; enable_etchook = TRUE; } KLogEx(DEBUG_TRACE_INFO, "complete\n"); return STATUS_SUCCESS; } NTSTATUS EtcIrpHookCleanup() { ULONG i, mi; LARGE_INTEGER WaitTime; PHOOK_CONTEXT hook = NULL; PDRIVER_DISPATCH pCurrentHandler = NULL; enable_etchook = FALSE; KLogEx(DEBUG_TRACE_INFO, "Started...\n"); for (i = 0; i < etc_maximum; i++) { hook = &g_EtcHookContexts[i]; if (!hook->IsHooked || !hook->DriverObject) continue; KLogEx(DEBUG_TRACE_INFO, "Unhooking Driver Index: %d\n", i); for (mi = 0; mi <= IRP_MJ_MAXIMUM_FUNCTION; mi++) { if (!hook->HookHandlers[mi].IsHook) continue; if (hook->HookHandlers[mi].pOrgHandler == NULL) continue; pCurrentHandler = (PDRIVER_DISPATCH)InterlockedExchangePointer( (PVOID)&hook->DriverObject->MajorFunction[mi], (PVOID)hook->HookHandlers[mi].pOrgHandler ); hook->HookHandlers[mi].pOrgHandler = NULL; } WaitTime.QuadPart = -50 * 1000 * 10; // 50ms ´ÜÀ§·Î ´ë±â while (hook->IrpEnterCount > 0) { KLogEx(DEBUG_TRACE_INFO, "Waiting for active IRPs... Count: %d\n", hook->IrpEnterCount); KeDelayExecutionThread(KernelMode, FALSE, &WaitTime); } // »óÅ ÃʱâÈ­ hook->IsHooked = FALSE; hook->DriverObject = NULL; //RtlZeroMemory(pCtx->HookHandlers, sizeof(pCtx->HookHandlers)); } WaitTime.QuadPart = -500 * 1000 * 10; KeDelayExecutionThread(KernelMode, FALSE, &WaitTime); KLogEx(DEBUG_TRACE_INFO, "Complete.\n"); return STATUS_SUCCESS; }