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