/* * @implemented */ DWORD WINAPI SignalObjectAndWait(IN HANDLE hObjectToSignal, IN HANDLE hObjectToWaitOn, IN DWORD dwMilliseconds, IN BOOL bAlertable) { PLARGE_INTEGER TimePtr; LARGE_INTEGER Time; NTSTATUS Status; RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME ActCtx; /* APCs must execute with the default activation context */ if (bAlertable) { /* Setup the frame */ RtlZeroMemory(&ActCtx, sizeof(ActCtx)); ActCtx.Size = sizeof(ActCtx); ActCtx.Format = RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME_FORMAT_WHISTLER; RtlActivateActivationContextUnsafeFast(&ActCtx, NULL); } /* Get real handle */ hObjectToWaitOn = TranslateStdHandle(hObjectToWaitOn); /* Check for console handle */ if ((IsConsoleHandle(hObjectToWaitOn)) && (VerifyConsoleIoHandle(hObjectToWaitOn))) { /* Get the real wait handle */ hObjectToWaitOn = GetConsoleInputWaitHandle(); } /* Convert the timeout */ TimePtr = BaseFormatTimeOut(&Time, dwMilliseconds); /* Start wait loop */ do { /* Do the wait */ Status = NtSignalAndWaitForSingleObject(hObjectToSignal, hObjectToWaitOn, (BOOLEAN)bAlertable, TimePtr); if (!NT_SUCCESS(Status)) { /* The wait failed */ BaseSetLastNTError(Status); Status = WAIT_FAILED; } } while ((Status == STATUS_ALERTED) && (bAlertable)); /* Cleanup the activation context */ if (bAlertable) RtlDeactivateActivationContextUnsafeFast(&ActCtx); /* Return wait status */ return Status; }
/* * @implemented */ BOOL WINAPI GetQueuedCompletionStatus(IN HANDLE CompletionHandle, IN LPDWORD lpNumberOfBytesTransferred, OUT PULONG_PTR lpCompletionKey, OUT LPOVERLAPPED *lpOverlapped, IN DWORD dwMilliseconds) { NTSTATUS Status; IO_STATUS_BLOCK IoStatus; ULONG_PTR CompletionKey; LARGE_INTEGER Time; PLARGE_INTEGER TimePtr; /* Convert the timeout and then call the native API */ TimePtr = BaseFormatTimeOut(&Time, dwMilliseconds); Status = NtRemoveIoCompletion(CompletionHandle, (PVOID*)&CompletionKey, (PVOID*)lpOverlapped, &IoStatus, TimePtr); if (!(NT_SUCCESS(Status)) || (Status == STATUS_TIMEOUT)) { /* Clear out the overlapped output */ *lpOverlapped = NULL; /* Check what kind of error we got */ if (Status == STATUS_TIMEOUT) { /* Timeout error is set directly since there's no conversion */ SetLastError(WAIT_TIMEOUT); } else { /* Any other error gets converted */ BaseSetLastNTError(Status); } /* This is a failure case */ return FALSE; } /* Write back the output parameters */ *lpCompletionKey = CompletionKey; *lpNumberOfBytesTransferred = IoStatus.Information; /* Check for error */ if (!NT_SUCCESS(IoStatus.Status)) { /* Convert and fail */ BaseSetLastNTError(IoStatus.Status); return FALSE; } /* Return success */ return TRUE; }
DWORD WINAPI SignalObjectAndWait( HANDLE hObjectToSignal, HANDLE hObjectToWaitOn, DWORD dwMilliseconds, BOOL bAlertable ) { NTSTATUS Status; LARGE_INTEGER TimeOut; PLARGE_INTEGER pTimeOut; PPEB Peb; Peb = NtCurrentPeb(); switch( (DWORD)hObjectToWaitOn ) { case STD_INPUT_HANDLE: hObjectToWaitOn = Peb->ProcessParameters->StandardInput; break; case STD_OUTPUT_HANDLE: hObjectToWaitOn = Peb->ProcessParameters->StandardOutput; break; case STD_ERROR_HANDLE: hObjectToWaitOn = Peb->ProcessParameters->StandardError; break; } if (CONSOLE_HANDLE(hObjectToWaitOn) && VerifyConsoleIoHandle(hObjectToWaitOn)) { hObjectToWaitOn = GetConsoleInputWaitHandle(); } pTimeOut = BaseFormatTimeOut(&TimeOut,dwMilliseconds); rewait: Status = NtSignalAndWaitForSingleObject( hObjectToSignal, hObjectToWaitOn, (BOOLEAN)bAlertable, pTimeOut ); if ( !NT_SUCCESS(Status) ) { BaseSetLastNTError(Status); Status = (NTSTATUS)0xffffffff; } else { if ( bAlertable && Status == STATUS_ALERTED ) { goto rewait; } } return (DWORD)Status; }
/* * @implemented */ DWORD WINAPI SleepEx(IN DWORD dwMilliseconds, IN BOOL bAlertable) { LARGE_INTEGER Time; PLARGE_INTEGER TimePtr; NTSTATUS errCode; RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME ActCtx; /* APCs must execute with the default activation context */ if (bAlertable) { /* Setup the frame */ RtlZeroMemory(&ActCtx, sizeof(ActCtx)); ActCtx.Size = sizeof(ActCtx); ActCtx.Format = RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME_FORMAT_WHISTLER; RtlActivateActivationContextUnsafeFast(&ActCtx, NULL); } /* Convert the timeout */ TimePtr = BaseFormatTimeOut(&Time, dwMilliseconds); if (!TimePtr) { /* Turn an infinite wait into a really long wait */ Time.LowPart = 0; Time.HighPart = 0x80000000; TimePtr = &Time; } /* Loop the delay while APCs are alerting us */ do { /* Do the delay */ errCode = NtDelayExecution((BOOLEAN)bAlertable, TimePtr); } while ((bAlertable) && (errCode == STATUS_ALERTED)); /* Cleanup the activation context */ if (bAlertable) RtlDeactivateActivationContextUnsafeFast(&ActCtx); /* Return the correct code */ return (errCode == STATUS_USER_APC) ? WAIT_IO_COMPLETION : 0; }
/* * @implemented */ DWORD WINAPI WaitForMultipleObjectsEx(IN DWORD nCount, IN CONST HANDLE *lpHandles, IN BOOL bWaitAll, IN DWORD dwMilliseconds, IN BOOL bAlertable) { PLARGE_INTEGER TimePtr; LARGE_INTEGER Time; PHANDLE HandleBuffer; HANDLE Handle[8]; DWORD i; NTSTATUS Status; RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME ActCtx; /* APCs must execute with the default activation context */ if (bAlertable) { /* Setup the frame */ RtlZeroMemory(&ActCtx, sizeof(ActCtx)); ActCtx.Size = sizeof(ActCtx); ActCtx.Format = RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME_FORMAT_WHISTLER; RtlActivateActivationContextUnsafeFast(&ActCtx, NULL); } /* Check if we have more handles then we locally optimize */ if (nCount > 8) { /* Allocate a buffer for them */ HandleBuffer = RtlAllocateHeap(RtlGetProcessHeap(), 0, nCount * sizeof(HANDLE)); if (!HandleBuffer) { /* No buffer, fail the wait */ SetLastError(ERROR_NOT_ENOUGH_MEMORY); if (bAlertable) RtlDeactivateActivationContextUnsafeFast(&ActCtx); return WAIT_FAILED; } } else { /* Otherwise, use our local buffer */ HandleBuffer = Handle; } /* Copy the handles into our buffer and loop them all */ RtlCopyMemory(HandleBuffer, (LPVOID)lpHandles, nCount * sizeof(HANDLE)); for (i = 0; i < nCount; i++) { /* Check what kind of handle this is */ HandleBuffer[i] = TranslateStdHandle(HandleBuffer[i]); /* Check for console handle */ if ((IsConsoleHandle(HandleBuffer[i])) && (VerifyConsoleIoHandle(HandleBuffer[i]))) { /* Get the real wait handle */ HandleBuffer[i] = GetConsoleInputWaitHandle(); } } /* Convert the timeout */ TimePtr = BaseFormatTimeOut(&Time, dwMilliseconds); /* Start wait loop */ do { /* Do the wait */ Status = NtWaitForMultipleObjects(nCount, HandleBuffer, bWaitAll ? WaitAll : WaitAny, (BOOLEAN)bAlertable, TimePtr); if (!NT_SUCCESS(Status)) { /* Wait failed */ BaseSetLastNTError(Status); Status = WAIT_FAILED; } } while ((Status == STATUS_ALERTED) && (bAlertable)); /* Check if we didn't use our local buffer */ if (HandleBuffer != Handle) { /* Free the allocated one */ RtlFreeHeap(RtlGetProcessHeap(), 0, HandleBuffer); } /* Cleanup the activation context */ if (bAlertable) RtlDeactivateActivationContextUnsafeFast(&ActCtx); /* Return wait status */ return Status; }
DWORD APIENTRY SleepEx( DWORD dwMilliseconds, BOOL bAlertable ) /*++ Routine Description: The execution of the current thread can be delayed for a specified interval of time with the SleepEx function. The SleepEx function causes the current thread to enter a waiting state until the specified interval of time has passed. If the bAlertable parameter is FALSE, the only way the SleepEx returns is when the specified time interval has passed. If the bAlertable parameter is TRUE, then the SleepEx can return due to the expiration of the time interval (return value of 0), or because an I/O completion callback terminated the SleepEx early (return value of WAIT_IO_COMPLETION). Arguments: dwMilliseconds - A time-out value that specifies the relative time, in milliseconds, over which the wait is to be completed. A timeout value of 0 specified that the wait is to timeout immediately. A timeout value of -1 specifies an infinite timeout period. bAlertable - Supplies a flag that controls whether or not the SleepEx may terminate early due to an I/O completion callback. A value of TRUE allows this API to complete early due to an I/O completion callback. A value of FALSE will not allow I/O completion callbacks to terminate this call early. Return Value: 0 - The SleepEx terminated due to expiration of the time interval. WAIT_IO_COMPLETION - The SleepEx terminated due to one or more I/O completion callbacks. --*/ { LARGE_INTEGER TimeOut; PLARGE_INTEGER pTimeOut; NTSTATUS Status; pTimeOut = BaseFormatTimeOut(&TimeOut,dwMilliseconds); if (pTimeOut == NULL) { // // If Sleep( -1 ) then delay for the longest possible integer // relative to now. // TimeOut.LowPart = 0x0; TimeOut.HighPart = 0x80000000; pTimeOut = &TimeOut; } rewait: Status = NtDelayExecution( (BOOLEAN)bAlertable, pTimeOut ); if ( bAlertable && Status == STATUS_ALERTED ) { goto rewait; } return Status == STATUS_USER_APC ? WAIT_IO_COMPLETION : 0; }
DWORD APIENTRY WaitForMultipleObjectsEx( DWORD nCount, CONST HANDLE *lpHandles, BOOL bWaitAll, DWORD dwMilliseconds, BOOL bAlertable ) /*++ Routine Description: A wait operation on multiple waitable objects (up to MAXIMUM_WAIT_OBJECTS) is accomplished with the WaitForMultipleObjects function. This API can be used to wait on any of the specified objects to enter the signaled state, or all of the objects to enter the signaled state. If the bAlertable parameter is FALSE, the only way the wait terminates is because the specified timeout period expires, or because the specified objects entered the signaled state. If the bAlertable parameter is TRUE, then the wait can return due to any one of the above wait termination conditions, or because an I/O completion callback terminated the wait early (return value of WAIT_IO_COMPLETION). Arguments: nCount - A count of the number of objects that are to be waited on. lpHandles - An array of object handles. Each handle must have SYNCHRONIZE access to the associated object. bWaitAll - A flag that supplies the wait type. A value of TRUE indicates a "wait all". A value of false indicates a "wait any". dwMilliseconds - A time-out value that specifies the relative time, in milliseconds, over which the wait is to be completed. A timeout value of 0 specified that the wait is to timeout immediately. This allows an application to test an object to determine if it is in the signaled state. A timeout value of 0xffffffff specifies an infinite timeout period. bAlertable - Supplies a flag that controls whether or not the wait may terminate early due to an I/O completion callback. A value of TRUE allows this API to complete early due to an I/O completion callback. A value of FALSE will not allow I/O completion callbacks to terminate this call early. Return Value: WAIT_TIME_OUT - indicates that the wait was terminated due to the TimeOut conditions. 0 to MAXIMUM_WAIT_OBJECTS-1, indicates, in the case of wait for any object, the object number which satisfied the wait. In the case of wait for all objects, the value only indicates that the wait was completed successfully. 0xffffffff - The wait terminated due to an error. GetLastError may be used to get additional error information. WAIT_ABANDONED_0 to (WAIT_ABANDONED_0)+(MAXIMUM_WAIT_OBJECTS - 1), indicates, in the case of wait for any object, the object number which satisfied the event, and that the object which satisfied the event was abandoned. In the case of wait for all objects, the value indicates that the wait was completed successfully and at least one of the objects was abandoned. WAIT_IO_COMPLETION - The wait terminated due to one or more I/O completion callbacks. --*/ { NTSTATUS Status; LARGE_INTEGER TimeOut; PLARGE_INTEGER pTimeOut; DWORD i; LPHANDLE HandleArray; HANDLE Handles[ 8 ]; PPEB Peb; if (nCount > 8) { HandleArray = (LPHANDLE) RtlAllocateHeap(RtlProcessHeap(), MAKE_TAG( TMP_TAG ), nCount*sizeof(HANDLE)); if (HandleArray == NULL) { BaseSetLastNTError(STATUS_NO_MEMORY); return 0xffffffff; } } else { HandleArray = Handles; } RtlCopyMemory(HandleArray,(LPVOID)lpHandles,nCount*sizeof(HANDLE)); Peb = NtCurrentPeb(); for (i=0; i<nCount; i++) { switch( (DWORD)HandleArray[i] ) { case STD_INPUT_HANDLE: HandleArray[i] = Peb->ProcessParameters->StandardInput; break; case STD_OUTPUT_HANDLE: HandleArray[i] = Peb->ProcessParameters->StandardOutput; break; case STD_ERROR_HANDLE: HandleArray[i] = Peb->ProcessParameters->StandardError; break; } if (CONSOLE_HANDLE(HandleArray[i]) && VerifyConsoleIoHandle(HandleArray[i])) { HandleArray[i] = GetConsoleInputWaitHandle(); } } pTimeOut = BaseFormatTimeOut(&TimeOut,dwMilliseconds); rewait: Status = NtWaitForMultipleObjects( (CHAR)nCount, HandleArray, bWaitAll ? WaitAll : WaitAny, (BOOLEAN)bAlertable, pTimeOut ); if ( !NT_SUCCESS(Status) ) { BaseSetLastNTError(Status); Status = (NTSTATUS)0xffffffff; } else { if ( bAlertable && Status == STATUS_ALERTED ) { goto rewait; } } if (HandleArray != Handles) { RtlFreeHeap(RtlProcessHeap(), 0, HandleArray); } return (DWORD)Status; }
DWORD APIENTRY WaitForSingleObjectEx( HANDLE hHandle, DWORD dwMilliseconds, BOOL bAlertable ) /*++ Routine Description: A wait operation on a waitable object is accomplished with the WaitForSingleObjectEx function. Waiting on an object checks the current state of the object. If the current state of the object allows continued execution, any adjustments to the object state are made (for example, decrementing the semaphore count for a semaphore object) and the thread continues execution. If the current state of the object does not allow continued execution, the thread is placed into the wait state pending the change of the object's state or time-out. If the bAlertable parameter is FALSE, the only way the wait terminates is because the specified timeout period expires, or because the specified object entered the signaled state. If the bAlertable parameter is TRUE, then the wait can return due to any one of the above wait termination conditions, or because an I/O completion callback terminated the wait early (return value of WAIT_IO_COMPLETION). Arguments: hHandle - An open handle to a waitable object. The handle must have SYNCHRONIZE access to the object. dwMilliseconds - A time-out value that specifies the relative time, in milliseconds, over which the wait is to be completed. A timeout value of 0 specified that the wait is to timeout immediately. This allows an application to test an object to determine if it is in the signaled state. A timeout value of 0xffffffff specifies an infinite timeout period. bAlertable - Supplies a flag that controls whether or not the wait may terminate early due to an I/O completion callback. A value of TRUE allows this API to complete early due to an I/O completion callback. A value of FALSE will not allow I/O completion callbacks to terminate this call early. Return Value: WAIT_TIME_OUT - Indicates that the wait was terminated due to the TimeOut conditions. 0 - indicates the specified object attained a Signaled state thus completing the wait. 0xffffffff - The wait terminated due to an error. GetLastError may be used to get additional error information. WAIT_ABANDONED - indicates the specified object attained a Signaled state but was abandoned. WAIT_IO_COMPLETION - The wait terminated due to one or more I/O completion callbacks. --*/ { NTSTATUS Status; LARGE_INTEGER TimeOut; PLARGE_INTEGER pTimeOut; PPEB Peb; Peb = NtCurrentPeb(); switch( (DWORD)hHandle ) { case STD_INPUT_HANDLE: hHandle = Peb->ProcessParameters->StandardInput; break; case STD_OUTPUT_HANDLE: hHandle = Peb->ProcessParameters->StandardOutput; break; case STD_ERROR_HANDLE: hHandle = Peb->ProcessParameters->StandardError; break; } if (CONSOLE_HANDLE(hHandle) && VerifyConsoleIoHandle(hHandle)) { hHandle = GetConsoleInputWaitHandle(); } pTimeOut = BaseFormatTimeOut(&TimeOut,dwMilliseconds); rewait: Status = NtWaitForSingleObject(hHandle,(BOOLEAN)bAlertable,pTimeOut); if ( !NT_SUCCESS(Status) ) { BaseSetLastNTError(Status); Status = (NTSTATUS)0xffffffff; } else { if ( bAlertable && Status == STATUS_ALERTED ) { goto rewait; } } return (DWORD)Status; }
/* * @implemented */ BOOL WINAPI WaitForDebugEvent(IN LPDEBUG_EVENT lpDebugEvent, IN DWORD dwMilliseconds) { LARGE_INTEGER WaitTime; PLARGE_INTEGER Timeout; DBGUI_WAIT_STATE_CHANGE WaitStateChange; NTSTATUS Status; /* Convert to NT Timeout */ Timeout = BaseFormatTimeOut(&WaitTime, dwMilliseconds); /* Loop while we keep getting interrupted */ do { /* Call the native API */ Status = DbgUiWaitStateChange(&WaitStateChange, Timeout); } while ((Status == STATUS_ALERTED) || (Status == STATUS_USER_APC)); /* Check if the wait failed */ if (!(NT_SUCCESS(Status)) || (Status == DBG_UNABLE_TO_PROVIDE_HANDLE)) { /* Set the error code and quit */ BaseSetLastNTError(Status); return FALSE; } /* Check if we timed out */ if (Status == STATUS_TIMEOUT) { /* Fail with a timeout error */ SetLastError(ERROR_SEM_TIMEOUT); return FALSE; } /* Convert the structure */ Status = DbgUiConvertStateChangeStructure(&WaitStateChange, lpDebugEvent); if (!NT_SUCCESS(Status)) { /* Set the error code and quit */ BaseSetLastNTError(Status); return FALSE; } /* Check what kind of event this was */ switch (lpDebugEvent->dwDebugEventCode) { /* New thread was created */ case CREATE_THREAD_DEBUG_EVENT: /* Setup the thread data */ SaveThreadHandle(lpDebugEvent->dwProcessId, lpDebugEvent->dwThreadId, lpDebugEvent->u.CreateThread.hThread); break; /* New process was created */ case CREATE_PROCESS_DEBUG_EVENT: /* Setup the process data */ SaveProcessHandle(lpDebugEvent->dwProcessId, lpDebugEvent->u.CreateProcessInfo.hProcess); /* Setup the thread data */ SaveThreadHandle(lpDebugEvent->dwProcessId, lpDebugEvent->dwThreadId, lpDebugEvent->u.CreateProcessInfo.hThread); break; /* Process was exited */ case EXIT_PROCESS_DEBUG_EVENT: /* Mark the thread data as such and fall through */ MarkProcessHandle(lpDebugEvent->dwProcessId); /* Thread was exited */ case EXIT_THREAD_DEBUG_EVENT: /* Mark the thread data */ MarkThreadHandle(lpDebugEvent->dwThreadId); break; /* Nothing to do */ case EXCEPTION_DEBUG_EVENT: case LOAD_DLL_DEBUG_EVENT: case UNLOAD_DLL_DEBUG_EVENT: case OUTPUT_DEBUG_STRING_EVENT: case RIP_EVENT: break; /* Fail anything else */ default: return FALSE; } /* Return success */ return TRUE; }
NTSTATUS InjectSelfToRemoteProcess(HANDLE hProcess, HANDLE hThread) { NTSTATUS Status; PVOID pvBuffer; DWORD Length; WCHAR szSelfPath[MAX_PATH]; CONTEXT ThreadContext; LARGE_INTEGER TimeOut; INJECT_DLL_CURRENT_THREAD inj; ThreadContext.ContextFlags = CONTEXT_CONTROL; Status = NtGetContextThread(hThread, &ThreadContext); if (!NT_SUCCESS(Status)) { // BaseSetLastNTError(Status); // PrintError(RtlGetLastWin32Error()); return Status; } // PrintConsoleW(L"Eip = %08X\n", ThreadContext.Eip); // getch(); Length = Nt_GetExeDirectory(szSelfPath, countof(szSelfPath)); if (Length == NULL) return STATUS_UNSUCCESSFUL; static WCHAR szDll[] = L"LocaleEmulator.dll"; StrCopyW(szSelfPath + Length, szDll); Length += CONST_STRLEN(szDll); pvBuffer = NULL; Status = Nt_AllocateMemory(hProcess, &pvBuffer, MEMORY_PAGE_SIZE); if (!NT_SUCCESS(Status)) { // BaseSetLastNTError(Status); // PrintError(RtlGetLastWin32Error()); return Status; } Length *= sizeof(WCHAR); inj.pfLdrLoadDll = LdrLoadDll; inj.ReturnAddr = ThreadContext.Eip; inj.ModuleFileName.Length = Length; inj.ModuleFileName.MaximumLength = Length + sizeof(WCHAR); inj.ModuleFileName.Buffer = (LPWSTR)((ULONG_PTR)pvBuffer + sizeof(inj)); Status = STATUS_UNSUCCESSFUL; LOOP_ONCE { Status = Nt_WriteMemory(hProcess, pvBuffer, &inj, sizeof(inj)); if (!NT_SUCCESS(Status)) break; Length += sizeof(WCHAR); Status = Nt_WriteMemory(hProcess, (PVOID)((ULONG_PTR)pvBuffer + sizeof(inj)), szSelfPath, Length); if (!NT_SUCCESS(Status)) break; ThreadContext.Eip = (DWORD)(PBYTE)pvBuffer + sizeof(inj) + Length; Status = Nt_WriteMemory( hProcess, (PVOID)ThreadContext.Eip, LoadExternDll, (ULONG_PTR)_LoadExternDllEnd - (ULONG_PTR)LoadExternDll ); if (!NT_SUCCESS(Status)) break; Status = NtSetContextThread(hThread, &ThreadContext); if (!NT_SUCCESS(Status)) break; Status = NtResumeThread(hThread, NULL); if (!NT_SUCCESS(Status)) break; BaseFormatTimeOut(&TimeOut, 500); for (DWORD TryTimes = 30; TryTimes; --TryTimes) { DWORD Val; Status = Nt_ReadMemory(hProcess, pvBuffer, &Val, sizeof(Val)); if (!NT_SUCCESS(Status)) break; if (Val != 0) { NtDelayExecution(FALSE, &TimeOut); continue; } break; } if (!NT_SUCCESS(Status)) break; NtDelayExecution(FALSE, &TimeOut); Status = NtGetContextThread(hThread, &ThreadContext); if (!NT_SUCCESS(Status)) break; if ((ULONG_PTR)ThreadContext.Eip < (ULONG_PTR)pvBuffer || (ULONG_PTR)ThreadContext.Eip > (ULONG_PTR)pvBuffer + MEMORY_PAGE_SIZE) { Status = STATUS_SUCCESS; } else { Status = STATUS_UNSUCCESSFUL; } } // BaseSetLastNTError(Status); // PrintError(RtlGetLastWin32Error()); // Nt_FreeMemory(hProcess, pvBuffer); return Status; }