1 /* 2 * PROJECT: ReactOS Local Spooler API Tests 3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) 4 * PURPOSE: Functions needed to run our code as a service. This is needed to run in SYSTEM security context. 5 * COPYRIGHT: Copyright 2015 Colin Finck (colin@reactos.org) 6 */ 7 8 #include <apitest.h> 9 10 #define WIN32_NO_STATUS 11 #include <windef.h> 12 #include <winbase.h> 13 #include <wingdi.h> 14 #include <winreg.h> 15 #include <winsvc.h> 16 #include <winspool.h> 17 #include <winsplp.h> 18 #include <tlhelp32.h> 19 20 #include "localspl_apitest.h" 21 22 //#define NDEBUG 23 #include <debug.h> 24 25 26 static void 27 _DoDLLInjection() 28 { 29 DWORD cbDLLPath; 30 HANDLE hProcess; 31 HANDLE hSnapshot; 32 HANDLE hThread; 33 PROCESSENTRY32W pe; 34 PVOID pLoadLibraryAddress; 35 PVOID pLoadLibraryArgument; 36 PWSTR p; 37 WCHAR wszFilePath[MAX_PATH]; 38 39 // Get the full path to our EXE file. 40 if (!GetModuleFileNameW(NULL, wszFilePath, _countof(wszFilePath))) 41 { 42 DPRINT("GetModuleFileNameW failed with error %lu!\n", GetLastError()); 43 return; 44 } 45 46 // Replace the extension. 47 p = wcsrchr(wszFilePath, L'.'); 48 if (!p) 49 { 50 DPRINT("File path has no file extension: %S\n", wszFilePath); 51 return; 52 } 53 54 wcscpy(p, L".dll"); 55 cbDLLPath = (lstrlenW(wszFilePath) + 1) * sizeof(WCHAR); 56 57 // Create a snapshot of the currently running processes. 58 hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 59 if (hSnapshot == INVALID_HANDLE_VALUE) 60 { 61 DPRINT("CreateToolhelp32Snapshot failed with error %lu!\n", GetLastError()); 62 return; 63 } 64 65 // Enumerate through all running processes. 66 pe.dwSize = sizeof(pe); 67 if (!Process32FirstW(hSnapshot, &pe)) 68 { 69 DPRINT("Process32FirstW failed with error %lu!\n", GetLastError()); 70 return; 71 } 72 73 do 74 { 75 // Check if this is the spooler server process. 76 if (_wcsicmp(pe.szExeFile, L"spoolsv.exe") != 0) 77 continue; 78 79 // Open a handle to the process. 80 hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID); 81 if (!hProcess) 82 { 83 DPRINT("OpenProcess failed with error %lu!\n", GetLastError()); 84 return; 85 } 86 87 // Get the address of LoadLibraryW. 88 pLoadLibraryAddress = (PVOID)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW"); 89 if (!pLoadLibraryAddress) 90 { 91 DPRINT("GetProcAddress failed with error %lu!\n", GetLastError()); 92 return; 93 } 94 95 // Allocate memory for the DLL path in the spooler process. 96 pLoadLibraryArgument = VirtualAllocEx(hProcess, NULL, cbDLLPath, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 97 if (!pLoadLibraryArgument) 98 { 99 DPRINT("VirtualAllocEx failed with error %lu!\n", GetLastError()); 100 return; 101 } 102 103 // Write the DLL path to the process memory. 104 if (!WriteProcessMemory(hProcess, pLoadLibraryArgument, wszFilePath, cbDLLPath, NULL)) 105 { 106 DPRINT("WriteProcessMemory failed with error %lu!\n", GetLastError()); 107 return; 108 } 109 110 // Create a new thread in the spooler process that calls LoadLibraryW as the start routine with our DLL as the argument. 111 // This effectively injects our DLL into the spooler process and we can inspect localspl.dll there just like the spooler. 112 hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryAddress, pLoadLibraryArgument, 0, NULL); 113 if (!hThread) 114 { 115 DPRINT("CreateRemoteThread failed with error %lu!\n", GetLastError()); 116 return; 117 } 118 119 CloseHandle(hThread); 120 break; 121 } 122 while (Process32NextW(hSnapshot, &pe)); 123 } 124 125 static DWORD WINAPI 126 _ServiceControlHandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext) 127 { 128 return NO_ERROR; 129 } 130 131 static void WINAPI 132 _ServiceMain(DWORD dwArgc, LPWSTR* lpszArgv) 133 { 134 SERVICE_STATUS_HANDLE hServiceStatus; 135 SERVICE_STATUS ServiceStatus; 136 137 UNREFERENCED_PARAMETER(dwArgc); 138 UNREFERENCED_PARAMETER(lpszArgv); 139 140 // Register our service for control. 141 hServiceStatus = RegisterServiceCtrlHandlerExW(SERVICE_NAME, _ServiceControlHandlerEx, NULL); 142 143 // Report SERVICE_RUNNING status. 144 ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; 145 ServiceStatus.dwServiceSpecificExitCode = 0; 146 ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; 147 ServiceStatus.dwWaitHint = 4000; 148 ServiceStatus.dwWin32ExitCode = NO_ERROR; 149 ServiceStatus.dwCurrentState = SERVICE_RUNNING; 150 SetServiceStatus(hServiceStatus, &ServiceStatus); 151 152 // Do our funky crazy stuff. 153 _DoDLLInjection(); 154 155 // Our work is done. 156 ServiceStatus.dwCurrentState = SERVICE_STOPPED; 157 SetServiceStatus(hServiceStatus, &ServiceStatus); 158 } 159 160 START_TEST(service) 161 { 162 int argc; 163 char** argv; 164 165 #if defined(_M_AMD64) 166 if (!winetest_interactive) 167 { 168 skip("ROSTESTS-366: Skipping localspl_apitest:service because it hangs on Windows Server 2003 x64-Testbot. Set winetest_interactive to run it anyway.\n"); 169 return; 170 } 171 #endif 172 173 SERVICE_TABLE_ENTRYW ServiceTable[] = 174 { 175 { SERVICE_NAME, _ServiceMain }, 176 { NULL, NULL } 177 }; 178 179 // This is no real test, but an easy way to integrate the service handler routines into the API-Test executable. 180 // Therefore, bail out if someone tries to run "service" as a usual test. 181 argc = winetest_get_mainargs(&argv); 182 if (argc != 3) 183 return; 184 185 // If we have exactly 3 arguments, we're run as a service, so initialize the corresponding service handler functions. 186 StartServiceCtrlDispatcherW(ServiceTable); 187 188 // Prevent the testing framework from outputting a "0 tests executed" line here. 189 ExitProcess(0); 190 } 191