diff options
author | Simon Rozman <simon@rozman.si> | 2020-11-04 08:11:44 +0100 |
---|---|---|
committer | Simon Rozman <simon@rozman.si> | 2020-11-04 13:21:42 +0100 |
commit | 0a51e26730993e1f4a2faee33f7b6cd27f871e5a (patch) | |
tree | 510a951cdf8fddeaccef2aa0b3fc07604e6bacbe /api/rundll32_i.c | |
parent | api: translate NTSTATUS to Win32 error codes (diff) | |
download | wintun-0a51e26730993e1f4a2faee33f7b6cd27f871e5a.tar.xz wintun-0a51e26730993e1f4a2faee33f7b6cd27f871e5a.zip |
api: include the rundll32 helpers the MSVC-typical way
Signed-off-by: Simon Rozman <simon@rozman.si>
Diffstat (limited to 'api/rundll32_i.c')
-rw-r--r-- | api/rundll32_i.c | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/api/rundll32_i.c b/api/rundll32_i.c new file mode 100644 index 0000000..ca4a504 --- /dev/null +++ b/api/rundll32_i.c @@ -0,0 +1,360 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2020 WireGuard LLC. All Rights Reserved. + */ + +/* TODO: This is currently #include'd in adapter.c. Move into rundll32.c properly. */ + +typedef struct _PROCESS_STDOUT_STATE +{ + HANDLE Stdout; + WCHAR *Response; + DWORD ResponseCapacity; +} PROCESS_STDOUT_STATE; + +static DWORD WINAPI +ProcessStdout(_Inout_ PROCESS_STDOUT_STATE *State) +{ + for (DWORD Offset = 0, MaxLen = State->ResponseCapacity - 1; Offset < MaxLen;) + { + DWORD SizeRead; + if (!ReadFile(State->Stdout, State->Response + Offset, sizeof(WCHAR) * (MaxLen - Offset), &SizeRead, NULL)) + return ERROR_SUCCESS; + if (SizeRead % sizeof(WCHAR)) + return ERROR_INVALID_DATA; + Offset += SizeRead / sizeof(WCHAR); + State->Response[Offset] = 0; + } + return ERROR_BUFFER_OVERFLOW; +} + +static DWORD WINAPI +ProcessStderr(_In_ HANDLE Stderr) +{ + enum + { + OnNone, + OnLevelStart, + OnLevel, + OnLevelEnd, + OnSpace, + OnMsg + } State = OnNone; + WCHAR Msg[0x200]; + DWORD Count = 0; + WINTUN_LOGGER_LEVEL Level = WINTUN_LOG_INFO; + for (;;) + { + WCHAR Buf[0x200]; + DWORD SizeRead; + if (!ReadFile(Stderr, Buf, sizeof(Buf), &SizeRead, NULL)) + return ERROR_SUCCESS; + if (SizeRead % sizeof(WCHAR)) + return ERROR_INVALID_DATA; + SizeRead /= sizeof(WCHAR); + for (DWORD i = 0; i < SizeRead; ++i) + { + WCHAR c = Buf[i]; + if (State == OnNone && c == L'[') + State = OnLevelStart; + else if ( + State == OnLevelStart && ((Level = WINTUN_LOG_INFO, c == L'+') || + (Level = WINTUN_LOG_WARN, c == L'-') || (Level = WINTUN_LOG_ERR, c == L'!'))) + State = OnLevelEnd; + else if (State == OnLevelEnd && c == L']') + State = OnSpace; + else if (State == OnSpace && !iswspace(c) || State == OnMsg && c != L'\r' && c != L'\n') + { + if (Count < _countof(Msg) - 1) + Msg[Count++] = c; + State = OnMsg; + } + else if (State == OnMsg && c == L'\n') + { + Msg[Count] = 0; + LoggerLog(Level, Msg); + State = OnNone; + Count = 0; + } + } + } +} + +static _Return_type_success_(return != FALSE) BOOL ExecuteRunDll32( + _In_z_ const WCHAR *Arguments, + _Out_z_cap_c_(ResponseCapacity) WCHAR *Response, + _In_ DWORD ResponseCapacity) +{ + WCHAR WindowsDirectory[MAX_PATH]; + if (!GetWindowsDirectoryW(WindowsDirectory, _countof(WindowsDirectory))) + { + LOG_LAST_ERROR(L"Failed to get Windows folder"); + return FALSE; + } + WCHAR RunDll32Path[MAX_PATH]; + if (!PathCombineW(RunDll32Path, WindowsDirectory, L"Sysnative\\rundll32.exe")) + { + SetLastError(ERROR_BUFFER_OVERFLOW); + return FALSE; + } + + DWORD LastError; + WCHAR RandomTempSubDirectory[MAX_PATH]; + if (!CreateTemporaryDirectory(RandomTempSubDirectory)) + { + LOG(WINTUN_LOG_ERR, L"Failed to create temporary folder"); + return FALSE; + } + WCHAR DllPath[MAX_PATH] = { 0 }; + if (!PathCombineW(DllPath, RandomTempSubDirectory, L"wintun.dll")) + { + LastError = ERROR_BUFFER_OVERFLOW; + goto cleanupDirectory; + } + const WCHAR *WintunDllResourceName; + switch (NativeMachine) + { + case IMAGE_FILE_MACHINE_AMD64: + WintunDllResourceName = L"wintun-amd64.dll"; + break; + case IMAGE_FILE_MACHINE_ARM64: + WintunDllResourceName = L"wintun-arm64.dll"; + break; + default: + LOG(WINTUN_LOG_ERR, L"Unsupported platform"); + LastError = ERROR_NOT_SUPPORTED; + goto cleanupDirectory; + } + if (!ResourceCopyToFile(DllPath, WintunDllResourceName)) + { + LastError = LOG(WINTUN_LOG_ERR, L"Failed to copy resource"); + goto cleanupDelete; + } + size_t CommandLineLen = 10 + MAX_PATH + 2 + wcslen(Arguments) + 1; + WCHAR *CommandLine = HeapAlloc(ModuleHeap, 0, CommandLineLen * sizeof(WCHAR)); + if (!CommandLine) + { + LOG(WINTUN_LOG_ERR, L"Out of memory"); + LastError = ERROR_OUTOFMEMORY; + goto cleanupDelete; + } + if (_snwprintf_s(CommandLine, CommandLineLen, _TRUNCATE, L"rundll32 \"%.*s\",%s", MAX_PATH, DllPath, Arguments) == + -1) + { + LOG(WINTUN_LOG_ERR, L"Command line too long"); + LastError = ERROR_INVALID_PARAMETER; + goto cleanupDelete; + } + HANDLE StreamRStdout = INVALID_HANDLE_VALUE, StreamRStderr = INVALID_HANDLE_VALUE, + StreamWStdout = INVALID_HANDLE_VALUE, StreamWStderr = INVALID_HANDLE_VALUE; + if (!CreatePipe(&StreamRStdout, &StreamWStdout, &SecurityAttributes, 0) || + !CreatePipe(&StreamRStderr, &StreamWStderr, &SecurityAttributes, 0)) + { + LastError = LOG_LAST_ERROR(L"Failed to create pipes"); + goto cleanupPipes; + } + if (!SetHandleInformation(StreamWStdout, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) || + !SetHandleInformation(StreamWStderr, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) + { + LastError = LOG_LAST_ERROR(L"Failed to set handle info"); + goto cleanupPipes; + } + if (ResponseCapacity) + Response[0] = 0; + PROCESS_STDOUT_STATE ProcessStdoutState = { .Stdout = StreamRStdout, + .Response = Response, + .ResponseCapacity = ResponseCapacity }; + HANDLE ThreadStdout = NULL, ThreadStderr = NULL; + if ((ThreadStdout = CreateThread(NULL, 0, ProcessStdout, &ProcessStdoutState, 0, NULL)) == NULL || + (ThreadStderr = CreateThread(NULL, 0, ProcessStderr, StreamRStderr, 0, NULL)) == NULL) + { + LastError = LOG_LAST_ERROR(L"Failed to spawn readers"); + goto cleanupThreads; + } + STARTUPINFOW si = { .cb = sizeof(STARTUPINFO), + .dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES, + .wShowWindow = SW_HIDE, + .hStdOutput = StreamWStdout, + .hStdError = StreamWStderr }; + PROCESS_INFORMATION pi; + HANDLE ProcessToken = GetPrimarySystemTokenFromThread(); + if (!ProcessToken) + { + LastError = LOG(WINTUN_LOG_ERR, L"Failed to get primary system token from thread"); + goto cleanupThreads; + } + if (!CreateProcessAsUserW(ProcessToken, RunDll32Path, CommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) + { + LastError = LOG_LAST_ERROR(L"Failed to create process"); + goto cleanupToken; + } + LastError = ERROR_SUCCESS; + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); +cleanupToken: + CloseHandle(ProcessToken); +cleanupThreads: + if (ThreadStderr) + { + CloseHandle(StreamWStderr); + StreamWStderr = INVALID_HANDLE_VALUE; + WaitForSingleObject(ThreadStderr, INFINITE); + CloseHandle(ThreadStderr); + } + if (ThreadStdout) + { + CloseHandle(StreamWStdout); + StreamWStdout = INVALID_HANDLE_VALUE; + WaitForSingleObject(ThreadStdout, INFINITE); + if (!GetExitCodeThread(ThreadStdout, &LastError)) + LastError = LOG_LAST_ERROR(L"Failed to retrieve stdout reader result"); + else if (LastError != ERROR_SUCCESS) + LOG_ERROR(L"Failed to read process output", LastError); + CloseHandle(ThreadStdout); + } +cleanupPipes: + CloseHandle(StreamRStderr); + CloseHandle(StreamWStderr); + CloseHandle(StreamRStdout); + CloseHandle(StreamWStdout); + HeapFree(ModuleHeap, 0, CommandLine); +cleanupDelete: + DeleteFileW(DllPath); +cleanupDirectory: + RemoveDirectoryW(RandomTempSubDirectory); + return RET_ERROR(TRUE, LastError); +} + +static _Return_type_success_(return != NULL) WINTUN_ADAPTER *CreateAdapterViaRundll32( + _In_z_ const WCHAR *Pool, + _In_z_ const WCHAR *Name, + _In_opt_ const GUID *RequestedGUID, + _Inout_ BOOL *RebootRequired) +{ + LOG(WINTUN_LOG_INFO, L"Spawning native process"); + WCHAR RequestedGUIDStr[MAX_GUID_STRING_LEN]; + WCHAR Arguments[15 + WINTUN_MAX_POOL + 3 + MAX_ADAPTER_NAME + 2 + MAX_GUID_STRING_LEN + 1]; + if (_snwprintf_s( + Arguments, + _countof(Arguments), + _TRUNCATE, + RequestedGUID ? L"CreateAdapter \"%s\" \"%s\" %.*s" : L"CreateAdapter \"%s\" \"%s\"", + Pool, + Name, + RequestedGUID ? StringFromGUID2(RequestedGUID, RequestedGUIDStr, _countof(RequestedGUIDStr)) : 0, + RequestedGUIDStr) == -1) + { + LOG(WINTUN_LOG_ERR, L"Command line too long"); + SetLastError(ERROR_INVALID_PARAMETER); + return NULL; + } + WCHAR Response[8 + 1 + MAX_GUID_STRING_LEN + 1 + 8 + 1]; + if (!ExecuteRunDll32(Arguments, Response, _countof(Response))) + { + LOG(WINTUN_LOG_ERR, L"Error executing worker process"); + return NULL; + } + DWORD LastError; + WINTUN_ADAPTER *Adapter = NULL; + int Argc; + WCHAR **Argv = CommandLineToArgvW(Response, &Argc); + GUID CfgInstanceID; + if (Argc < 3 || FAILED(CLSIDFromString(Argv[1], &CfgInstanceID))) + { + LOG(WINTUN_LOG_ERR, L"Incomplete or invalid response"); + LastError = ERROR_INVALID_PARAMETER; + goto cleanupArgv; + } + LastError = wcstoul(Argv[0], NULL, 16); + if (LastError == ERROR_SUCCESS && (Adapter = GetAdapter(Pool, &CfgInstanceID)) == NULL) + { + LOG(WINTUN_LOG_ERR, L"Failed to get adapter"); + LastError = ERROR_FILE_NOT_FOUND; + } + if (wcstoul(Argv[2], NULL, 16)) + *RebootRequired = TRUE; +cleanupArgv: + LocalFree(Argv); + SetLastError(LastError); + return Adapter; +} + +static _Return_type_success_(return != FALSE) BOOL DeleteAdapterViaRundll32( + _In_ const WINTUN_ADAPTER *Adapter, + _In_ BOOL ForceCloseSessions, + _Inout_ BOOL *RebootRequired) +{ + LOG(WINTUN_LOG_INFO, L"Spawning native process"); + WCHAR GuidStr[MAX_GUID_STRING_LEN]; + WCHAR Arguments[16 + MAX_GUID_STRING_LEN + 1]; + if (_snwprintf_s( + Arguments, + _countof(Arguments), + _TRUNCATE, + L"DeleteAdapter %d %.*s", + ForceCloseSessions ? 1 : 0, + StringFromGUID2(&Adapter->CfgInstanceID, GuidStr, _countof(GuidStr)), + GuidStr) == -1) + { + LOG(WINTUN_LOG_ERR, L"Command line too long"); + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + WCHAR Response[8 + 1 + 8 + 1]; + DWORD LastError; + if (!ExecuteRunDll32(Arguments, Response, _countof(Response))) + { + LOG(WINTUN_LOG_ERR, L"Error executing worker process"); + return FALSE; + } + int Argc; + WCHAR **Argv = CommandLineToArgvW(Response, &Argc); + if (Argc < 2) + { + LOG(WINTUN_LOG_ERR, L"Incomplete or invalid response"); + LastError = ERROR_INVALID_PARAMETER; + goto cleanupArgv; + } + LastError = wcstoul(Argv[0], NULL, 16); + if (wcstoul(Argv[1], NULL, 16)) + *RebootRequired = TRUE; +cleanupArgv: + LocalFree(Argv); + return RET_ERROR(TRUE, LastError); +} + +static _Return_type_success_(return != FALSE) BOOL + DeletePoolDriverViaRundll32(_In_z_ const WCHAR Pool[WINTUN_MAX_POOL], _Inout_ BOOL *RebootRequired) +{ + LOG(WINTUN_LOG_INFO, L"Spawning native process"); + + WCHAR Arguments[17 + WINTUN_MAX_POOL + 1]; + if (_snwprintf_s(Arguments, _countof(Arguments), _TRUNCATE, L"DeletePoolDriver %s", Pool) == -1) + { + LOG(WINTUN_LOG_ERR, L"Command line too long"); + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + WCHAR Response[8 + 1 + 8 + 1]; + DWORD LastError; + if (!ExecuteRunDll32(Arguments, Response, _countof(Response))) + { + LOG(WINTUN_LOG_ERR, L"Error executing worker process"); + return FALSE; + } + int Argc; + WCHAR **Argv = CommandLineToArgvW(Response, &Argc); + if (Argc < 2) + { + LOG(WINTUN_LOG_ERR, L"Incomplete or invalid response"); + LastError = ERROR_INVALID_PARAMETER; + goto cleanupArgv; + } + LastError = wcstoul(Argv[0], NULL, 16); + if (wcstoul(Argv[1], NULL, 16)) + *RebootRequired = TRUE; +cleanupArgv: + LocalFree(Argv); + return RET_ERROR(TRUE, LastError); +} |