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 }