From faf7d3771c9bd7b8b9a3ee20ceed2a432542c318 Mon Sep 17 00:00:00 2001 From: Simon Rozman Date: Mon, 19 Oct 2020 22:23:09 +0200 Subject: 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 --- api/adapter.c | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- api/rundll32.c | 64 ++++++++++++++---- api/wintun.h | 4 +- 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); -- cgit v1.2.3-59-g8ed1b