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