aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rozman <simon@rozman.si>2020-10-19 22:23:09 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2020-10-30 16:51:01 +0100
commitfaf7d3771c9bd7b8b9a3ee20ceed2a432542c318 (patch)
treebad93f3e3406a5b57b51d9a2b61133e9f9dd49bd
parentapi: installer: switch from MSM to ZIP distribution (diff)
downloadwintun-faf7d3771c9bd7b8b9a3ee20ceed2a432542c318.tar.xz
wintun-faf7d3771c9bd7b8b9a3ee20ceed2a432542c318.zip
api: connect rundll32 child with parent using stdout and stderr
The WintunCreateAdapter()+CreateAdapter() and WintunDeleteAdapter()+ DeleteAdapter() communicate using Unicode anonymous pipes now. This allows the WintunCreateAdapter() to exactly determine the adapter CreateAdapter() just created by its GUID rather than its name - avoiding the possible ambiguity caused by same-adapter-name ordinal suffixes. This also allows exact retrieval of the result code and pending reboot flag from the rundll32 child process. Furthermore, CreateAdapter() and DeleteAdapter() are now available in _DEBUG for all platforms to allow testing. It took a #pragma comment( linker, "/EXPORT") trick to stop compiler from decorating function names and exporting as _CreateAdapter@16() and _DeleteAdapter@16() in x86. Signed-off-by: Simon Rozman <simon@rozman.si>
-rw-r--r--api/adapter.c205
-rw-r--r--api/rundll32.c64
-rw-r--r--api/wintun.h4
3 files changed, 249 insertions, 24 deletions
diff --git a/api/adapter.c b/api/adapter.c
index faaa8e3..d1e8786 100644
--- a/api/adapter.c
+++ b/api/adapter.c
@@ -1260,8 +1260,86 @@ CreateTemporaryDirectory(_Out_cap_c_(MAX_PATH) WCHAR *RandomTempSubDirectory)
#if defined(_M_IX86) || defined(_M_ARM)
+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 0;
+ if (SizeRead % sizeof(WCHAR))
+ return 1;
+ Offset += SizeRead / sizeof(WCHAR);
+ State->Response[Offset] = 0;
+ }
+ return 2;
+}
+
+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 0;
+ if (SizeRead % sizeof(WCHAR))
+ return 1;
+ 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;
+ Logger(Level, Msg);
+ State = OnNone;
+ Count = 0;
+ }
+ }
+ }
+}
+
static WINTUN_STATUS
-ExecuteRunDll32(_In_z_ const WCHAR *Arguments)
+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)))
@@ -1297,18 +1375,69 @@ ExecuteRunDll32(_In_z_ const WCHAR *Arguments)
goto cleanupDelete;
}
_snwprintf_s(CommandLine, CommandLineLen, _TRUNCATE, L"rundll32 \"%.*s\",%s", MAX_PATH, DllPath, Arguments);
- /* TODO: Create stdio pipes to intercept logged messages. */
- STARTUPINFOW si = { .cb = sizeof(STARTUPINFO), .dwFlags = STARTF_USESHOWWINDOW, .wShowWindow = SW_HIDE };
+ SECURITY_ATTRIBUTES sa = { .nLength = sizeof(SECURITY_ATTRIBUTES),
+ .bInheritHandle = TRUE,
+ .lpSecurityDescriptor =
+ SecurityAttributes ? SecurityAttributes->lpSecurityDescriptor : NULL };
+ typedef enum
+ {
+ Stdout = 0,
+ Stderr
+ } stdid_t;
+ HANDLE StreamR[] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE },
+ StreamW[] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE };
+ if (!CreatePipe(&StreamR[Stdout], &StreamW[Stdout], &sa, 0) ||
+ !CreatePipe(&StreamR[Stderr], &StreamW[Stderr], &sa, 0))
+ {
+ Result = LOG_LAST_ERROR(L"Failed to create pipes");
+ goto cleanupPipes;
+ }
+ if (!SetHandleInformation(StreamR[Stdout], HANDLE_FLAG_INHERIT, 0) ||
+ !SetHandleInformation(StreamR[Stderr], HANDLE_FLAG_INHERIT, 0))
+ {
+ Result = LOG_LAST_ERROR(L"Failed to set handle info");
+ goto cleanupPipes;
+ }
+ if (ResponseCapacity)
+ Response[0] = 0;
+ PROCESS_STDOUT_STATE ProcessStdoutState = { .Stdout = StreamR[Stdout],
+ .Response = Response,
+ .ResponseCapacity = ResponseCapacity };
+ HANDLE Thread[] = { NULL, NULL };
+ if ((Thread[Stdout] = CreateThread(SecurityAttributes, 0, ProcessStdout, &ProcessStdoutState, 0, NULL)) == NULL ||
+ (Thread[Stderr] = CreateThread(SecurityAttributes, 0, ProcessStderr, StreamR[Stderr], 0, NULL)) == NULL)
+ {
+ Result = LOG_LAST_ERROR(L"Failed to spawn reader threads");
+ goto cleanupThreads;
+ }
+ STARTUPINFOW si = { .cb = sizeof(STARTUPINFO),
+ .dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES,
+ .wShowWindow = SW_HIDE,
+ .hStdOutput = StreamW[Stdout],
+ .hStdError = StreamW[Stderr] };
PROCESS_INFORMATION pi;
- if (!CreateProcessW(RunDll32Path, CommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
+ if (!CreateProcessW(RunDll32Path, CommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
{
Result = LOG_LAST_ERROR(L"Creating process failed");
- goto cleanupCommandLine;
+ goto cleanupThreads;
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
-cleanupCommandLine:
+cleanupThreads:
+ for (size_t i = _countof(Thread); i--;)
+ if (Thread[i])
+ {
+ CloseHandle(StreamW[i]);
+ StreamW[i] = INVALID_HANDLE_VALUE;
+ WaitForSingleObject(Thread[i], INFINITE);
+ CloseHandle(Thread[i]);
+ }
+cleanupPipes:
+ CloseHandle(StreamR[Stderr]);
+ CloseHandle(StreamW[Stderr]);
+ CloseHandle(StreamR[Stdout]);
+ CloseHandle(StreamW[Stdout]);
HeapFree(Heap, 0, CommandLine);
cleanupDelete:
DeleteFileW(DllPath);
@@ -1317,6 +1446,29 @@ cleanupDirectory:
return Result;
}
+static WINTUN_STATUS
+GetAdapter(_In_z_count_c_(MAX_POOL) const WCHAR *Pool, _In_ const GUID *CfgInstanceID, _Out_ WINTUN_ADAPTER **Adapter)
+{
+ HANDLE Mutex = NamespaceTakeMutex(Pool);
+ if (!Mutex)
+ return ERROR_INVALID_HANDLE;
+ HDEVINFO DevInfo;
+ SP_DEVINFO_DATA DevInfoData;
+ DWORD Result = GetDevInfoData(CfgInstanceID, &DevInfo, &DevInfoData);
+ if (Result != ERROR_SUCCESS)
+ {
+ LOG(WINTUN_LOG_ERR, L"Failed to locate adapter");
+ goto cleanupMutex;
+ }
+ Result = CreateAdapterData(Pool, DevInfo, &DevInfoData, Adapter);
+ if (Result != ERROR_SUCCESS)
+ LOG(WINTUN_LOG_ERR, L"Failed to create adapter data");
+ SetupDiDestroyDeviceInfoList(DevInfo);
+cleanupMutex:
+ NamespaceReleaseMutex(Mutex);
+ return Result;
+}
+
#endif
WINTUN_STATUS WINAPI
@@ -1328,7 +1480,6 @@ WintunCreateAdapter(
_Inout_ BOOL *RebootRequired)
{
#if defined(_M_IX86) || defined(_M_ARM)
- UNREFERENCED_PARAMETER(RebootRequired);
if (NativeMachine != IMAGE_FILE_PROCESS)
{
LOG(WINTUN_LOG_INFO, L"Spawning native process for the job");
@@ -1345,13 +1496,33 @@ WintunCreateAdapter(
Name,
RequestedGUID ? StringFromGUID2(RequestedGUID, RequestedGUIDStr, _countof(RequestedGUIDStr)) : 0,
RequestedGUIDStr);
- DWORD Result = ExecuteRunDll32(Arguments);
+ WCHAR Response[8 + 1 + MAX_GUID_STRING_LEN + 1 + 8 + 1];
+ DWORD Result = ExecuteRunDll32(Arguments, Response, _countof(Response));
if (Result != ERROR_SUCCESS)
{
LOG(WINTUN_LOG_ERR, L"Error executing worker process");
return Result;
}
- return WintunGetAdapter(Pool, Name, Adapter);
+ 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");
+ Result = ERROR_INVALID_PARAMETER;
+ goto cleanupArgv;
+ }
+ Result = wcstoul(Argv[0], NULL, 16);
+ if (Result == ERROR_SUCCESS && GetAdapter(Pool, &CfgInstanceID, Adapter) != ERROR_SUCCESS)
+ {
+ LOG(WINTUN_LOG_ERR, L"Failed to get adapter");
+ Result = ERROR_FILE_NOT_FOUND;
+ }
+ if (wcstoul(Argv[2], NULL, 16))
+ *RebootRequired = TRUE;
+ cleanupArgv:
+ LocalFree(Argv);
+ return Result;
}
#endif
@@ -1422,9 +1593,23 @@ WintunDeleteAdapter(_In_ const WINTUN_ADAPTER *Adapter, _Inout_ BOOL *RebootRequ
L"DeleteAdapter %.*s",
StringFromGUID2(&Adapter->CfgInstanceID, GuidStr, _countof(GuidStr)),
GuidStr);
- DWORD Result = ExecuteRunDll32(Arguments);
+ WCHAR Response[8 + 1 + 8 + 1];
+ DWORD Result = ExecuteRunDll32(Arguments, Response, _countof(Response));
if (Result != ERROR_SUCCESS)
LOG(WINTUN_LOG_ERR, L"Error executing worker process");
+ int Argc;
+ WCHAR **Argv = CommandLineToArgvW(Response, &Argc);
+ if (Argc < 2)
+ {
+ LOG(WINTUN_LOG_ERR, L"Incomplete or invalid response");
+ Result = ERROR_INVALID_PARAMETER;
+ goto cleanupArgv;
+ }
+ Result = wcstoul(Argv[0], NULL, 16);
+ if (wcstoul(Argv[1], NULL, 16))
+ *RebootRequired = TRUE;
+ cleanupArgv:
+ LocalFree(Argv);
return Result;
}
#endif
diff --git a/api/rundll32.c b/api/rundll32.c
index 77e4842..dcd6628 100644
--- a/api/rundll32.c
+++ b/api/rundll32.c
@@ -5,7 +5,34 @@
#include "pch.h"
-#if defined(_M_AMD64) || defined(_M_ARM64)
+#define EXPORT comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
+
+#if defined(_M_AMD64) || defined(_M_ARM64) || defined(_DEBUG)
+
+static DWORD
+WriteFormatted(_In_ DWORD StdHandle, _In_z_ const WCHAR *Template, ...)
+{
+ WCHAR *FormattedMessage = NULL;
+ DWORD SizeWritten;
+ va_list Arguments;
+ va_start(Arguments, Template);
+ WriteFile(
+ GetStdHandle(StdHandle),
+ FormattedMessage,
+ sizeof(WCHAR) * FormatMessageW(
+ FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER,
+ Template,
+ 0,
+ 0,
+ (void *)&FormattedMessage,
+ 0,
+ &Arguments),
+ &SizeWritten,
+ NULL);
+ LocalFree(FormattedMessage);
+ va_end(Arguments);
+ return SizeWritten / sizeof(WCHAR);
+}
static BOOL CALLBACK
ConsoleLogger(_In_ WINTUN_LOGGER_LEVEL Level, _In_ const WCHAR *LogLine)
@@ -14,22 +41,23 @@ ConsoleLogger(_In_ WINTUN_LOGGER_LEVEL Level, _In_ const WCHAR *LogLine)
switch (Level)
{
case WINTUN_LOG_INFO:
- Template = L"[+] %s\n";
+ Template = L"[+] %1\n";
break;
case WINTUN_LOG_WARN:
- Template = L"[-] %s\n";
+ Template = L"[-] %1\n";
break;
case WINTUN_LOG_ERR:
- Template = L"[!] %s\n";
+ Template = L"[!] %1\n";
break;
default:
return FALSE;
}
- fwprintf(stderr, Template, LogLine);
+ WriteFormatted(STD_ERROR_HANDLE, Template, LogLine);
return TRUE;
}
-static BOOL ElevateToSystem(void)
+static BOOL
+ElevateToSystem(void)
{
HANDLE CurrentProcessToken, ThreadToken, ProcessSnapshot, WinlogonProcess, WinlogonToken, DuplicatedToken;
PROCESSENTRY32W ProcessEntry = { .dwSize = sizeof(PROCESSENTRY32W) };
@@ -126,21 +154,24 @@ cleanup:
static int Argc;
static WCHAR **Argv;
-static void Init(void)
+static void
+Init(void)
{
WintunSetLogger(ConsoleLogger);
Argv = CommandLineToArgvW(GetCommandLineW(), &Argc);
ElevateToSystem();
}
-static void Done(void)
+static void
+Done(void)
{
RevertToSelf();
LocalFree(Argv);
}
-__declspec(dllexport) VOID __stdcall CreateAdapter(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
+VOID __stdcall CreateAdapter(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
{
+# pragma EXPORT
UNREFERENCED_PARAMETER(hwnd);
UNREFERENCED_PARAMETER(hinst);
UNREFERENCED_PARAMETER(lpszCmdLine);
@@ -160,16 +191,23 @@ __declspec(dllexport) VOID __stdcall CreateAdapter(HWND hwnd, HINSTANCE hinst, L
WINTUN_ADAPTER *Adapter;
BOOL RebootRequired = FALSE;
DWORD Result = WintunCreateAdapter(Argv[2], Argv[3], Argc > 4 ? &RequestedGUID : NULL, &Adapter, &RebootRequired);
- if (Result != ERROR_SUCCESS)
- goto cleanup;
+ WCHAR GuidStr[MAX_GUID_STRING_LEN];
+ WriteFormatted(
+ STD_OUTPUT_HANDLE,
+ L"%1!X! %2!.*s! %3!X!",
+ Result,
+ StringFromGUID2(Result == ERROR_SUCCESS ? &Adapter->CfgInstanceID : &GUID_NULL, GuidStr, _countof(GuidStr)),
+ GuidStr,
+ RebootRequired);
WintunFreeAdapter(Adapter);
cleanup:
Done();
}
-__declspec(dllexport) VOID __stdcall DeleteAdapter(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
+VOID __stdcall DeleteAdapter(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
{
+# pragma EXPORT
UNREFERENCED_PARAMETER(hwnd);
UNREFERENCED_PARAMETER(hinst);
UNREFERENCED_PARAMETER(lpszCmdLine);
@@ -183,7 +221,7 @@ __declspec(dllexport) VOID __stdcall DeleteAdapter(HWND hwnd, HINSTANCE hinst, L
if (FAILED(CLSIDFromString(Argv[2], &Adapter.CfgInstanceID)))
goto cleanup;
BOOL RebootRequired = FALSE;
- WintunDeleteAdapter(&Adapter, &RebootRequired);
+ WriteFormatted(STD_OUTPUT_HANDLE, L"%1!X! %2!X!", WintunDeleteAdapter(&Adapter, &RebootRequired), RebootRequired);
cleanup:
Done();
diff --git a/api/wintun.h b/api/wintun.h
index 0917a6c..dadac10 100644
--- a/api/wintun.h
+++ b/api/wintun.h
@@ -206,6 +206,8 @@ typedef BOOL(CALLBACK *WINTUN_LOGGER_FUNC)(_In_ WINTUN_LOGGER_LEVEL Level, _In_z
/**
* Sets logger callback function.
*
- * @param NewLogger Pointer to callback function to use as a new global logger.
+ * @param NewLogger Pointer to callback function to use as a new global logger. NewLogger may be called from various
+ * threads concurrently. Should the logging require serialization, you must handle serialization in
+ * NewLogger.
*/
typedef void(WINAPI *WINTUN_SET_LOGGER_FUNC)(_In_ WINTUN_LOGGER_FUNC NewLogger);