aboutsummaryrefslogtreecommitdiffstats
path: root/api/rundll32.c
diff options
context:
space:
mode:
Diffstat (limited to 'api/rundll32.c')
-rw-r--r--api/rundll32.c398
1 files changed, 398 insertions, 0 deletions
diff --git a/api/rundll32.c b/api/rundll32.c
new file mode 100644
index 0000000..5ea2b15
--- /dev/null
+++ b/api/rundll32.c
@@ -0,0 +1,398 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.
+ */
+
+#include "rundll32.h"
+#include "adapter.h"
+#include "main.h"
+#include "logger.h"
+#include "resource.h"
+#include <Windows.h>
+#include <shellapi.h>
+#include <Shlwapi.h>
+#include <cfgmgr32.h>
+#include <objbase.h>
+#include <assert.h>
+
+#ifdef MAYBE_WOW64
+
+_Return_type_success_(return != FALSE)
+static BOOL
+AppendToBuffer(_Inout_ LPWSTR *Buffer, _In_ CONST WCHAR Addition, _Inout_ SIZE_T *BufferPos, _Inout_ SIZE_T *BufferLen)
+{
+ SIZE_T NewPos;
+ if (FAILED(SIZETAdd(*BufferPos, sizeof(Addition), &NewPos)))
+ return FALSE;
+ if (NewPos >= *BufferLen)
+ {
+ SIZE_T NewLen;
+ if (FAILED(SIZETMult(NewPos, 3, &NewLen)))
+ return FALSE;
+ LPWSTR NewBuffer = ReZalloc(*Buffer, NewLen);
+ if (!NewBuffer)
+ return FALSE;
+ *Buffer = NewBuffer;
+ *BufferLen = NewLen;
+ }
+ SIZE_T NewIndex = *BufferPos / sizeof(**Buffer);
+ if (*Buffer + NewIndex < *Buffer)
+ return FALSE;
+ (*Buffer)[NewIndex] = Addition;
+ *BufferPos = NewPos;
+ return TRUE;
+}
+
+_Must_inspect_result_
+static _Return_type_success_(return != NULL)
+_Post_maybenull_
+LPWSTR
+ArgvToCommandLineW(_In_ SIZE_T ArgCount, ...)
+{
+ LPWSTR Output = NULL;
+ SIZE_T BufferPos = 0, BufferLen = 0;
+# define Append(Char) \
+ do \
+ { \
+ if (!AppendToBuffer(&Output, Char, &BufferPos, &BufferLen)) \
+ goto cleanupBuffer; \
+ } while (0)
+
+ va_list Args;
+ va_start(Args, ArgCount);
+ for (SIZE_T i = 0; i < ArgCount; ++i)
+ {
+ LPCWSTR Arg = va_arg(Args, LPCWSTR);
+ SIZE_T ArgLen = wcslen(Arg);
+ if (ArgLen >= DWORD_MAX >> 3)
+ goto cleanupBuffer;
+ if (i)
+ Append(L' ');
+ Append(L'"');
+ for (SIZE_T j = 0;; ++j)
+ {
+ SIZE_T NumberBackslashes = 0;
+
+ while (j < ArgLen && Arg[j] == L'\\')
+ {
+ ++j;
+ ++NumberBackslashes;
+ }
+ if (j >= ArgLen)
+ {
+ for (SIZE_T k = 0; k < NumberBackslashes * 2; ++k)
+ Append(L'\\');
+ break;
+ }
+ else if (Arg[j] == L'"')
+ {
+ for (SIZE_T k = 0; k < NumberBackslashes * 2 + 1; ++k)
+ Append(L'\\');
+ Append(Arg[j]);
+ }
+ else
+ {
+ for (SIZE_T k = 0; k < NumberBackslashes; ++k)
+ Append(L'\\');
+ Append(Arg[j]);
+ }
+ }
+ Append(L'"');
+ }
+ va_end(Args);
+ return Output;
+
+cleanupBuffer:
+ Free(Output);
+ return NULL;
+# undef Append
+}
+
+typedef struct _PROCESS_STDOUT_STATE
+{
+ HANDLE Stdout;
+ LPWSTR Response;
+ DWORD ResponseCapacity;
+} PROCESS_STDOUT_STATE;
+
+_Return_type_success_(return != ERROR_SUCCESS)
+static DWORD WINAPI
+ProcessStdout(_Inout_ PROCESS_STDOUT_STATE *State)
+{
+ for (DWORD Offset = 0, MaxLen = State->ResponseCapacity - 1; Offset < MaxLen;)
+ {
+ DWORD Size;
+ if (FAILED(DWordMult(MaxLen - Offset, sizeof(WCHAR), &Size)))
+ return ERROR_BUFFER_OVERFLOW;
+ if (!ReadFile(State->Stdout, State->Response + Offset, Size, &Size, NULL))
+ return ERROR_SUCCESS;
+ if (Size % sizeof(WCHAR))
+ return ERROR_INVALID_DATA;
+ Offset += Size / sizeof(WCHAR);
+ State->Response[Offset] = 0;
+ }
+ return ERROR_BUFFER_OVERFLOW;
+}
+
+static DWORD WINAPI
+ProcessStderr(_In_ HANDLE Stderr)
+{
+ WCHAR Msg[0x200], Buf[0x220], LevelRune;
+ DWORD64 Timestamp;
+ DWORD SizeRead;
+ WINTUN_LOGGER_LEVEL Level;
+ for (;;)
+ {
+ if (!ReadFile(Stderr, Buf, sizeof(Buf), &SizeRead, NULL) || !SizeRead)
+ return ERROR_SUCCESS;
+ if (SizeRead % sizeof(*Buf))
+ return ERROR_INVALID_DATA;
+ Msg[0] = Buf[SizeRead / sizeof(*Buf) - 1] = L'\0';
+ if (swscanf_s(Buf, L"[%c %I64u] %[^\n]", &LevelRune, 1, &Timestamp, Msg, (DWORD)_countof(Msg)) != 3 || !Msg[0])
+ return ERROR_INVALID_DATA;
+ if (!((Level = WINTUN_LOG_INFO, LevelRune == L'+') || (Level = WINTUN_LOG_WARN, LevelRune == L'-') ||
+ (Level = WINTUN_LOG_ERR, LevelRune == L'!')))
+ return ERROR_INVALID_DATA;
+ Logger(Level, Timestamp, Msg);
+ }
+}
+
+static _Return_type_success_(return != FALSE)
+BOOL
+ExecuteRunDll32(
+ _In_z_ LPCWSTR Function,
+ _In_z_ LPCWSTR Arguments,
+ _Out_z_cap_c_(ResponseCapacity) LPWSTR 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 (!ResourceCreateTemporaryDirectory(RandomTempSubDirectory))
+ {
+ LOG(WINTUN_LOG_ERR, L"Failed to create temporary folder");
+ return FALSE;
+ }
+ WCHAR DllPath[MAX_PATH] = { 0 };
+ if (!PathCombineW(DllPath, RandomTempSubDirectory, L"setupapihost.dll"))
+ {
+ LastError = ERROR_BUFFER_OVERFLOW;
+ goto cleanupDirectory;
+ }
+ LPCWSTR WintunDllResourceName;
+ switch (NativeMachine)
+ {
+ case IMAGE_FILE_MACHINE_AMD64:
+ WintunDllResourceName = L"setupapihost-amd64.dll";
+ break;
+ case IMAGE_FILE_MACHINE_ARM64:
+ WintunDllResourceName = L"setupapihost-arm64.dll";
+ break;
+ default:
+ LOG(WINTUN_LOG_ERR, L"Unsupported platform 0x%x", NativeMachine);
+ LastError = ERROR_NOT_SUPPORTED;
+ goto cleanupDirectory;
+ }
+ if (!ResourceCopyToFile(DllPath, WintunDllResourceName))
+ {
+ LastError = LOG(WINTUN_LOG_ERR, L"Failed to copy resource %s to %s", WintunDllResourceName, DllPath);
+ goto cleanupDelete;
+ }
+ size_t CommandLineLen = 10 + MAX_PATH + 2 + wcslen(Arguments) + 1 + wcslen(Function) + 1;
+ LPWSTR CommandLine = AllocArray(CommandLineLen, sizeof(*CommandLine));
+ if (!CommandLine)
+ {
+ LastError = GetLastError();
+ goto cleanupDelete;
+ }
+ if (_snwprintf_s(
+ CommandLine,
+ CommandLineLen,
+ _TRUNCATE,
+ L"rundll32 \"%.*s\",%s %s",
+ MAX_PATH,
+ DllPath,
+ Function,
+ 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;
+ if (!CreateProcessW(RunDll32Path, CommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
+ {
+ LastError = LOG_LAST_ERROR(L"Failed to create process: %s", CommandLine);
+ goto cleanupThreads;
+ }
+ LastError = ERROR_SUCCESS;
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+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);
+ DWORD ThreadResult;
+ if (!GetExitCodeThread(ThreadStdout, &ThreadResult))
+ LastError = LOG_LAST_ERROR(L"Failed to retrieve stdout reader result");
+ else if (ThreadResult != ERROR_SUCCESS)
+ LastError = LOG_ERROR(ThreadResult, L"Failed to read process output");
+ CloseHandle(ThreadStdout);
+ }
+cleanupPipes:
+ CloseHandle(StreamRStderr);
+ CloseHandle(StreamWStderr);
+ CloseHandle(StreamRStdout);
+ CloseHandle(StreamWStdout);
+ Free(CommandLine);
+cleanupDelete:
+ DeleteFileW(DllPath);
+cleanupDirectory:
+ RemoveDirectoryW(RandomTempSubDirectory);
+ return RET_ERROR(TRUE, LastError);
+}
+
+static _Return_type_success_(return != FALSE)
+BOOL
+InvokeClassInstaller(_In_ LPCWSTR Action, _In_ LPCWSTR Function, _In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData)
+{
+ LOG(WINTUN_LOG_INFO, L"Spawning native process to %s instance", Action);
+
+ WCHAR InstanceId[MAX_DEVICE_ID_LEN];
+ DWORD RequiredChars = _countof(InstanceId);
+ if (!SetupDiGetDeviceInstanceIdW(DevInfo, DevInfoData, InstanceId, RequiredChars, &RequiredChars))
+ {
+ LOG_LAST_ERROR(L"Failed to get adapter instance ID");
+ return FALSE;
+ }
+ LPWSTR Arguments = ArgvToCommandLineW(1, InstanceId);
+ if (!Arguments)
+ {
+ SetLastError(LOG_ERROR(ERROR_INVALID_PARAMETER, L"Command line too long"));
+ return FALSE;
+ }
+ DWORD LastError;
+ WCHAR Response[8 + 1];
+ if (!ExecuteRunDll32(Function, Arguments, Response, _countof(Response)))
+ {
+ LastError = LOG_LAST_ERROR(L"Error executing worker process: %s", Arguments);
+ goto cleanupArguments;
+ }
+ int Argc;
+ LPWSTR *Argv = CommandLineToArgvW(Response, &Argc);
+ if (Argc < 1)
+ {
+ LastError = LOG_ERROR(ERROR_INVALID_PARAMETER, L"Incomplete response: %s", Response);
+ goto cleanupArgv;
+ }
+ LastError = wcstoul(Argv[0], NULL, 16);
+cleanupArgv:
+ LocalFree(Argv);
+cleanupArguments:
+ Free(Arguments);
+ return RET_ERROR(TRUE, LastError);
+}
+
+_Use_decl_annotations_
+BOOL
+RemoveInstanceViaRundll32(HDEVINFO DevInfo, SP_DEVINFO_DATA *DevInfoData)
+{
+ return InvokeClassInstaller(L"remove", L"RemoveInstance", DevInfo, DevInfoData);
+}
+
+_Use_decl_annotations_
+BOOL
+EnableInstanceViaRundll32(HDEVINFO DevInfo, SP_DEVINFO_DATA *DevInfoData)
+{
+ return InvokeClassInstaller(L"enable", L"EnableInstance", DevInfo, DevInfoData);
+}
+
+_Use_decl_annotations_
+BOOL
+DisableInstanceViaRundll32(HDEVINFO DevInfo, SP_DEVINFO_DATA *DevInfoData)
+{
+ return InvokeClassInstaller(L"disable", L"DisableInstance", DevInfo, DevInfoData);
+}
+
+_Use_decl_annotations_
+BOOL
+CreateInstanceWin7ViaRundll32(LPWSTR InstanceId)
+{
+ LOG(WINTUN_LOG_INFO, L"Spawning native process to create instance");
+
+ DWORD LastError;
+ WCHAR Response[MAX_DEVICE_ID_LEN + 1];
+ if (!ExecuteRunDll32(L"CreateInstanceWin7", L"", Response, _countof(Response)))
+ {
+ LastError = LOG_LAST_ERROR(L"Error executing worker process");
+ goto cleanup;
+ }
+ int Argc;
+ LPWSTR *Argv = CommandLineToArgvW(Response, &Argc);
+ if (Argc < 2)
+ {
+ LastError = LOG_ERROR(ERROR_INVALID_PARAMETER, L"Incomplete response: %s", Response);
+ goto cleanupArgv;
+ }
+ LastError = wcstoul(Argv[0], NULL, 16);
+ if (LastError == ERROR_SUCCESS)
+ wcsncpy_s(InstanceId, MAX_DEVICE_ID_LEN, Argv[1], _TRUNCATE);
+cleanupArgv:
+ LocalFree(Argv);
+cleanup:
+ return RET_ERROR(TRUE, LastError);
+}
+#endif