1 /** 2 * Windows process utility code. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.sys.windows.process; 15 16 import std.exception; 17 import std.typecons; 18 19 import win32.w32api; 20 import win32.winbase; 21 import win32.windef; 22 import win32.winuser; 23 24 import ae.sys.windows.exception; 25 import ae.sys.windows.text; 26 27 alias wenforce = ae.sys.windows.exception.wenforce; 28 29 static if (_WIN32_WINNT >= 0x500) { 30 31 struct CreatedProcessImpl 32 { 33 PROCESS_INFORMATION pi; 34 alias pi this; 35 36 DWORD wait() 37 { 38 WaitForSingleObject(hProcess, INFINITE); 39 DWORD dwExitCode; 40 wenforce(GetExitCodeProcess(hProcess, &dwExitCode), "GetExitCodeProcess"); 41 return dwExitCode; 42 } 43 44 ~this() 45 { 46 CloseHandle(pi.hProcess); 47 CloseHandle(pi.hThread); 48 } 49 } 50 51 alias RefCounted!CreatedProcessImpl CreatedProcess; 52 CreatedProcess createProcess(string applicationName, string commandLine, STARTUPINFOW si = STARTUPINFOW.init) 53 { 54 CreatedProcess result; 55 wenforce(CreateProcessW(toWStringz(applicationName), cast(LPWSTR)toWStringz(commandLine), null, null, false, 0, null, null, &si, &result.pi), "CreateProcess"); 56 AllowSetForegroundWindow(result.dwProcessId); 57 AttachThreadInput(GetCurrentThreadId(), result.dwThreadId, TRUE); 58 AllowSetForegroundWindow(result.dwProcessId); 59 return result; 60 } 61 62 enum TOKEN_ADJUST_SESSIONID = 0x0100; 63 //enum SecurityImpersonation = 2; 64 //enum TokenPrimary = 1; 65 alias extern(Windows) BOOL function( 66 HANDLE hToken, 67 DWORD dwLogonFlags, 68 LPCWSTR lpApplicationName, 69 LPWSTR lpCommandLine, 70 DWORD dwCreationFlags, 71 LPVOID lpEnvironment, 72 LPCWSTR lpCurrentDirectory, 73 LPSTARTUPINFOW lpStartupInfo, 74 LPPROCESS_INFORMATION lpProcessInfo 75 ) CreateProcessWithTokenWFunc; 76 77 /// Create a non-elevated process, if the current process is elevated. 78 CreatedProcess createDesktopUserProcess(string applicationName, string commandLine, STARTUPINFOW si = STARTUPINFOW.init) 79 { 80 CreateProcessWithTokenWFunc CreateProcessWithTokenW = cast(CreateProcessWithTokenWFunc)GetProcAddress(GetModuleHandle("advapi32.dll"), "CreateProcessWithTokenW"); 81 82 HANDLE hShellProcess = null, hShellProcessToken = null, hPrimaryToken = null; 83 HWND hwnd = null; 84 DWORD dwPID = 0; 85 86 // Enable SeIncreaseQuotaPrivilege in this process. (This won't work if current process is not elevated.) 87 HANDLE hProcessToken = null; 88 wenforce(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hProcessToken), "OpenProcessToken failed"); 89 scope(exit) CloseHandle(hProcessToken); 90 91 TOKEN_PRIVILEGES tkp; 92 tkp.PrivilegeCount = 1; 93 LookupPrivilegeValue(null, SE_INCREASE_QUOTA_NAME.ptr, &tkp.Privileges()[0].Luid); 94 tkp.Privileges()[0].Attributes = SE_PRIVILEGE_ENABLED; 95 wenforce(AdjustTokenPrivileges(hProcessToken, FALSE, &tkp, 0, null, null), "AdjustTokenPrivileges failed"); 96 97 // Get an HWND representing the desktop shell. 98 // CAVEATS: This will fail if the shell is not running (crashed or terminated), or the default shell has been 99 // replaced with a custom shell. This also won't return what you probably want if Explorer has been terminated and 100 // restarted elevated. 101 hwnd = GetShellWindow(); 102 enforce(hwnd, "No desktop shell is present"); 103 104 // Get the PID of the desktop shell process. 105 GetWindowThreadProcessId(hwnd, &dwPID); 106 enforce(dwPID, "Unable to get PID of desktop shell."); 107 108 // Open the desktop shell process in order to query it (get the token) 109 hShellProcess = wenforce(OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPID), "Can't open desktop shell process"); 110 scope(exit) CloseHandle(hShellProcess); 111 112 // Get the process token of the desktop shell. 113 wenforce(OpenProcessToken(hShellProcess, TOKEN_DUPLICATE, &hShellProcessToken), "Can't get process token of desktop shell"); 114 scope(exit) CloseHandle(hShellProcessToken); 115 116 // Duplicate the shell's process token to get a primary token. 117 // Based on experimentation, this is the minimal set of rights required for CreateProcessWithTokenW (contrary to current documentation). 118 const DWORD dwTokenRights = TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID; 119 wenforce(DuplicateTokenEx(hShellProcessToken, dwTokenRights, null, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, &hPrimaryToken), "Can't get primary token"); 120 scope(exit) CloseHandle(hPrimaryToken); 121 122 CreatedProcess result; 123 124 // Start the target process with the new token. 125 wenforce(CreateProcessWithTokenW( 126 hPrimaryToken, 127 0, 128 toWStringz(applicationName), cast(LPWSTR)toWStringz(commandLine), 129 0, 130 null, 131 null, 132 &si, 133 &result.pi, 134 ), "CreateProcessWithTokenW failed"); 135 136 AllowSetForegroundWindow(result.dwProcessId); 137 AttachThreadInput(GetCurrentThreadId(), result.dwThreadId, TRUE); 138 AllowSetForegroundWindow(result.dwProcessId); 139 140 return result; 141 } 142 143 // -------------------------------------------------------------------------- 144 145 import win32.tlhelp32; 146 147 struct ToolhelpSnapshotImpl 148 { 149 HANDLE hSnapshot; 150 151 ~this() 152 { 153 CloseHandle(hSnapshot); 154 } 155 } 156 157 alias RefCounted!ToolhelpSnapshotImpl ToolhelpSnapshot; 158 159 ToolhelpSnapshot createToolhelpSnapshot(DWORD dwFlags, DWORD th32ProcessID=0) 160 { 161 ToolhelpSnapshot result; 162 auto hSnapshot = CreateToolhelp32Snapshot(dwFlags, th32ProcessID); 163 wenforce(hSnapshot != INVALID_HANDLE_VALUE, "CreateToolhelp32Snapshot"); 164 result.hSnapshot = hSnapshot; 165 return result; 166 } 167 168 struct ToolhelpIterator(STRUCT, alias FirstFunc, alias NextFunc) 169 { 170 private: 171 ToolhelpSnapshot snapshot; 172 STRUCT s; 173 BOOL bSuccess; 174 175 this(ToolhelpSnapshot snapshot) 176 { 177 this.snapshot = snapshot; 178 s.dwSize = STRUCT.sizeof; 179 bSuccess = FirstFunc(snapshot.hSnapshot, &s); 180 } 181 182 public: 183 @property 184 bool empty() const { return bSuccess == 0; } 185 186 @property 187 ref STRUCT front() { return s; } 188 189 void popFront() 190 { 191 bSuccess = NextFunc(snapshot.hSnapshot, &s); 192 } 193 } 194 195 alias ToolhelpIterator!(PROCESSENTRY32, Process32First, Process32Next) ProcessIterator; 196 @property ProcessIterator processes(ToolhelpSnapshot snapshot) { return ProcessIterator(snapshot); } 197 198 alias ToolhelpIterator!(THREADENTRY32, Thread32First, Thread32Next) ThreadIterator; 199 @property ThreadIterator threads(ToolhelpSnapshot snapshot) { return ThreadIterator(snapshot); } 200 201 alias ToolhelpIterator!(MODULEENTRY32, Module32First, Module32Next) ModuleIterator; 202 @property ModuleIterator modules(ToolhelpSnapshot snapshot) { return ModuleIterator(snapshot); } 203 204 alias ToolhelpIterator!(HEAPLIST32, Heap32ListFirst, Heap32ListNext) HeapIterator; 205 @property HeapIterator heaps(ToolhelpSnapshot snapshot) { return HeapIterator(snapshot); } 206 207 // -------------------------------------------------------------------------- 208 209 struct ProcessWatcher 210 { 211 PROCESSENTRY32[DWORD] oldProcesses; 212 213 void update(void delegate(ref PROCESSENTRY32) oldHandler, void delegate(ref PROCESSENTRY32) newHandler, bool handleExisting = false) 214 { 215 PROCESSENTRY32[DWORD] newProcesses; 216 foreach (ref process; createToolhelpSnapshot(TH32CS_SNAPPROCESS).processes) 217 newProcesses[process.th32ProcessID] = process; 218 219 if (oldProcesses || handleExisting) // Skip calling delegates on first run 220 { 221 if (oldHandler) 222 foreach (pid, ref process; oldProcesses) 223 if (pid !in newProcesses) 224 oldHandler(process); 225 226 if (newHandler) 227 foreach (pid, ref process; newProcesses) 228 if (pid !in oldProcesses) 229 newHandler(process); 230 } 231 232 oldProcesses = newProcesses; 233 } 234 } 235 236 } // _WIN32_WINNT >= 0x500 237 238 // *************************************************************************** 239 240 alias ubyte* RemoteAddress; 241 242 void readProcessMemory(HANDLE h, RemoteAddress addr, void[] data) 243 { 244 size_t c; 245 wenforce(ReadProcessMemory(h, addr, data.ptr, data.length, &c), "ReadProcessMemory"); 246 enforce(c==data.length, "Not all data read"); 247 } 248 249 void writeProcessMemory(HANDLE h, RemoteAddress addr, const(void)[] data) 250 { 251 size_t c; 252 wenforce(WriteProcessMemory(h, addr, data.ptr, data.length, &c), "WriteProcessMemory"); 253 enforce(c==data.length, "Not all data written"); 254 } 255 256 void readProcessVar(T)(HANDLE h, RemoteAddress addr, T* v) 257 { 258 h.readProcessMemory(addr, v[0..1]); 259 } 260 261 T readProcessVar(T)(HANDLE h, RemoteAddress addr) 262 { 263 T v; 264 h.readProcessVar(addr, &v); 265 return v; 266 } 267 268 void writeProcessVar(T)(HANDLE h, RemoteAddress addr, auto ref T v) 269 { 270 h.writeProcessMemory(addr, (&v)[0..1]); 271 } 272 273 struct RemoteProcessVarImpl(T) 274 { 275 T local; 276 @property T* localPtr() { return &local; } 277 RemoteAddress remotePtr; 278 HANDLE hProcess; 279 280 this(HANDLE hProcess) 281 { 282 this.hProcess = hProcess; 283 remotePtr = cast(RemoteAddress)wenforce(VirtualAllocEx(hProcess, null, T.sizeof, MEM_COMMIT, PAGE_READWRITE)); 284 } 285 286 void read() 287 { 288 readProcessMemory (hProcess, remotePtr, localPtr[0..1]); 289 } 290 291 void write() 292 { 293 writeProcessMemory(hProcess, remotePtr, localPtr[0..1]); 294 } 295 296 ~this() 297 { 298 VirtualFreeEx(hProcess, remotePtr, 0, MEM_RELEASE); 299 } 300 } 301 302 /// Binding to a variable located in another process. 303 /// Automatically allocates and deallocates remote memory. 304 /// Use .read() and .write() to update local/remote data. 305 template RemoteProcessVar(T) 306 { 307 alias RefCounted!(RemoteProcessVarImpl!T) RemoteProcessVar; 308 }