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 }