1import os
2import sys
3import ctypes
4import enum
5import platform
6import logging
7import struct
8
9from ctypes.wintypes import HANDLE, BOOL, DWORD, HWND, HINSTANCE, HKEY, LPVOID, LPWSTR, PBOOL
10from ctypes import c_ulong, c_char_p, c_int, c_void_p, WinError, get_last_error, windll
11
12from privileges import enable_debug_privilege
13
14if platform.system() != 'Windows':
15	raise Exception('This script will ovbiously only work on Windows')
16
17# https://stackoverflow.com/questions/1405913/how-do-i-determine-if-my-python-shell-is-executing-in-32bit-or-64bit-mode-on-os
18IS_PYTHON_64 = False if (8 * struct.calcsize("P")) == 32 else True
19
20class MINIDUMP_TYPE(enum.IntFlag):
21	MiniDumpNormal						  = 0x00000000
22	MiniDumpWithDataSegs					= 0x00000001
23	MiniDumpWithFullMemory				  = 0x00000002
24	MiniDumpWithHandleData				  = 0x00000004
25	MiniDumpFilterMemory					= 0x00000008
26	MiniDumpScanMemory					  = 0x00000010
27	MiniDumpWithUnloadedModules			 = 0x00000020
28	MiniDumpWithIndirectlyReferencedMemory  = 0x00000040
29	MiniDumpFilterModulePaths			   = 0x00000080
30	MiniDumpWithProcessThreadData		   = 0x00000100
31	MiniDumpWithPrivateReadWriteMemory	  = 0x00000200
32	MiniDumpWithoutOptionalData			 = 0x00000400
33	MiniDumpWithFullMemoryInfo			  = 0x00000800
34	MiniDumpWithThreadInfo				  = 0x00001000
35	MiniDumpWithCodeSegs					= 0x00002000
36	MiniDumpWithoutAuxiliaryState		   = 0x00004000
37	MiniDumpWithFullAuxiliaryState		  = 0x00008000
38	MiniDumpWithPrivateWriteCopyMemory	  = 0x00010000
39	MiniDumpIgnoreInaccessibleMemory		= 0x00020000
40	MiniDumpWithTokenInformation			= 0x00040000
41	MiniDumpWithModuleHeaders			   = 0x00080000
42	MiniDumpFilterTriage					= 0x00100000
43	MiniDumpValidTypeFlags				  = 0x001fffff
44
45class WindowsBuild(enum.Enum):
46	WIN_XP  = 2600
47	WIN_2K3 = 3790
48	WIN_VISTA = 6000
49	WIN_7 = 7600
50	WIN_8 = 9200
51	WIN_BLUE = 9600
52	WIN_10_1507 = 10240
53	WIN_10_1511 = 10586
54	WIN_10_1607 = 14393
55	WIN_10_1707 = 15063
56
57class WindowsMinBuild(enum.Enum):
58	WIN_XP = 2500
59	WIN_2K3 = 3000
60	WIN_VISTA = 5000
61	WIN_7 = 7000
62	WIN_8 = 8000
63	WIN_BLUE = 9400
64	WIN_10 = 9800
65
66#utter microsoft bullshit commencing..
67def getWindowsBuild():
68    class OSVersionInfo(ctypes.Structure):
69        _fields_ = [
70            ("dwOSVersionInfoSize" , ctypes.c_int),
71            ("dwMajorVersion"      , ctypes.c_int),
72            ("dwMinorVersion"      , ctypes.c_int),
73            ("dwBuildNumber"       , ctypes.c_int),
74            ("dwPlatformId"        , ctypes.c_int),
75            ("szCSDVersion"        , ctypes.c_char*128)];
76    GetVersionEx = getattr( ctypes.windll.kernel32 , "GetVersionExA")
77    version  = OSVersionInfo()
78    version.dwOSVersionInfoSize = ctypes.sizeof(OSVersionInfo)
79    GetVersionEx( ctypes.byref(version) )
80    return version.dwBuildNumber
81
82DELETE = 0x00010000
83READ_CONTROL = 0x00020000
84WRITE_DAC = 0x00040000
85WRITE_OWNER = 0x00080000
86
87SYNCHRONIZE = 0x00100000
88
89STANDARD_RIGHTS_REQUIRED = DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER
90STANDARD_RIGHTS_ALL = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE
91
92if getWindowsBuild() >= WindowsMinBuild.WIN_VISTA.value:
93	PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF
94else:
95	PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF
96
97FILE_SHARE_READ = 1
98FILE_SHARE_WRITE = 2
99FILE_SHARE_DELETE = 4
100FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
101FILE_FLAG_BACKUP_SEMANTICS = 0x2000000
102
103FILE_CREATE_NEW = 1
104FILE_CREATE_ALWAYS = 2
105FILE_OPEN_EXISTING = 3
106FILE_OPEN_ALWAYS = 4
107FILE_TRUNCATE_EXISTING = 5
108
109FILE_GENERIC_READ = 0x80000000
110FILE_GENERIC_WRITE = 0x40000000
111FILE_GENERIC_EXECUTE = 0x20000000
112FILE_GENERIC_ALL = 0x10000000
113
114
115FILE_ATTRIBUTE_READONLY = 0x1
116FILE_ATTRIBUTE_HIDDEN = 0x2
117FILE_ATTRIBUTE_DIRECTORY = 0x10
118FILE_ATTRIBUTE_NORMAL = 0x80
119FILE_ATTRIBUTE_REPARSE_POINT = 0x400
120GENERIC_READ = 0x80000000
121FILE_READ_ATTRIBUTES = 0x80
122
123PROCESS_QUERY_INFORMATION = 0x0400
124PROCESS_VM_READ = 0x0010
125
126MAX_PATH = 260
127
128
129"""
130class SECURITY_ATTRIBUTES(ctypes.Structure):
131    _fields_ = (
132        ('length', ctypes.wintypes.DWORD),
133        ('p_security_descriptor', ctypes.wintypes.LPVOID),
134        ('inherit_handle', ctypes.wintypes.BOOLEAN),
135        )
136LPSECURITY_ATTRIBUTES = ctypes.POINTER(SECURITY_ATTRIBUTES)
137"""
138Psapi = windll.psapi
139GetProcessImageFileName = Psapi.GetProcessImageFileNameA
140GetProcessImageFileName.restype = ctypes.wintypes.DWORD
141QueryFullProcessImageName = ctypes.windll.kernel32.QueryFullProcessImageNameA
142QueryFullProcessImageName.restype = ctypes.wintypes.DWORD
143EnumProcesses = Psapi.EnumProcesses
144EnumProcesses.restype = ctypes.wintypes.DWORD
145
146LPSECURITY_ATTRIBUTES = LPVOID #we dont pass this for now
147# https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
148CreateFile = ctypes.windll.kernel32.CreateFileW
149CreateFile.argtypes = (
150	LPWSTR,
151	DWORD,
152	DWORD,
153    LPSECURITY_ATTRIBUTES,
154	DWORD,
155	DWORD,
156	HANDLE,
157    )
158CreateFile.restype = ctypes.wintypes.HANDLE
159
160PHANDLE = ctypes.POINTER(HANDLE)
161PDWORD = ctypes.POINTER(DWORD)
162
163GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess
164GetCurrentProcess.argtypes = ()
165GetCurrentProcess.restype = HANDLE
166
167# https://msdn.microsoft.com/en-us/library/ms684139.aspx
168IsWow64Process  = ctypes.windll.kernel32.IsWow64Process
169IsWow64Process.argtypes = (HANDLE, PBOOL)
170IsWow64Process.restype = BOOL
171
172CloseHandle = ctypes.windll.kernel32.CloseHandle
173CloseHandle.argtypes = (HANDLE, )
174CloseHandle.restype = BOOL
175
176# https://msdn.microsoft.com/en-us/library/windows/desktop/ms684320(v=vs.85).aspx
177OpenProcess = ctypes.windll.kernel32.OpenProcess
178OpenProcess.argtypes = (DWORD, BOOL, DWORD )
179OpenProcess.restype = HANDLE
180
181# https://msdn.microsoft.com/en-us/library/windows/desktop/ms680360(v=vs.85).aspx
182MiniDumpWriteDump = ctypes.windll.DbgHelp.MiniDumpWriteDump
183MiniDumpWriteDump.argtypes = (HANDLE , DWORD , HANDLE, DWORD, DWORD, DWORD, DWORD)
184MiniDumpWriteDump.restype = BOOL
185
186def is64bitProc(process_handle):
187	is64 = BOOL()
188	res = IsWow64Process(process_handle, ctypes.byref(is64))
189	if res == 0:
190		logging.warning('Failed to get process version info!')
191		WinError(get_last_error())
192	return not bool(is64.value)
193
194# https://waitfordebug.wordpress.com/2012/01/27/pid-enumeration-on-windows-with-pure-python-ctypes/
195def enum_pids():
196
197	max_array = c_ulong * 4096 # define long array to capture all the processes
198	pProcessIds = max_array() # array to store the list of processes
199	pBytesReturned = c_ulong() # the number of bytes returned in the array
200	#EnumProcess
201	res = EnumProcesses(
202		ctypes.byref(pProcessIds),
203		ctypes.sizeof(pProcessIds),
204		ctypes.byref(pBytesReturned)
205	)
206	if res == 0:
207		logging.error(WinError(get_last_error()))
208		return []
209
210	# get the number of returned processes
211	nReturned = int(pBytesReturned.value/ctypes.sizeof(c_ulong()))
212	return [i for i in pProcessIds[:nReturned]]
213
214#https://msdn.microsoft.com/en-us/library/windows/desktop/ms683217(v=vs.85).aspx
215def enum_process_names():
216	pid_to_name = {}
217
218	for pid in enum_pids():
219		pid_to_name[pid] = 'Not found'
220		process_handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, pid)
221		if process_handle is None:
222			logging.debug('[Enum Processes]Failed to open process PID: %d Reason: %s ' % (pid, WinError(get_last_error())))
223			continue
224
225		image_name = (ctypes.c_char*MAX_PATH)()
226		max_path = DWORD(4096)
227		#res = GetProcessImageFileName(process_handle, image_name, MAX_PATH)
228		res = QueryFullProcessImageName(process_handle, 0 ,image_name, ctypes.byref(max_path))
229		if res == 0:
230			logging.debug('[Enum Proceses]Failed GetProcessImageFileName on PID: %d Reason: %s ' % (pid, WinError(get_last_error())))
231			continue
232
233		pid_to_name[pid] = image_name.value.decode()
234	return pid_to_name
235
236def create_dump(pid, output_filename, mindumptype, with_debug = False):
237	if with_debug:
238		logging.debug('Enabling SeDebugPrivilege')
239		assigned = enable_debug_privilege()
240		msg = ['failure', 'success'][assigned]
241		logging.debug('SeDebugPrivilege assignment %s' % msg)
242
243	logging.debug('Opening process PID: %d' % pid)
244	process_handle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
245	if process_handle is None:
246		logging.warning('Failed to open process PID: %d' % pid)
247		logging.error(WinError(get_last_error()))
248		return
249	logging.debug('Process handle: 0x%04x' % process_handle)
250	is64 = is64bitProc(process_handle)
251	if is64 != IS_PYTHON_64:
252		logging.warning('process architecture mismatch! This could case error! Python arch: %s Target process arch: %s' % ('x86' if not IS_PYTHON_64 else 'x64', 'x86' if not is64 else 'x64'))
253
254	logging.debug('Creating file handle for output file')
255	file_handle = CreateFile(output_filename, FILE_GENERIC_READ | FILE_GENERIC_WRITE, 0, None,  FILE_CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, None)
256	if file_handle == -1:
257		logging.warning('Failed to create file')
258		logging.error(WinError(get_last_error()))
259		return
260	logging.debug('Dumping process to file')
261	res = MiniDumpWriteDump(process_handle, pid, file_handle, mindumptype, 0,0,0)
262	if not bool(res):
263		logging.warning('Failed to dump process to file')
264		logging.error(WinError(get_last_error()))
265	logging.info('Dump file created succsessfully')
266	CloseHandle(file_handle)
267	CloseHandle(process_handle)
268
269def main():
270	import argparse
271
272	parser = argparse.ArgumentParser(description='Tool to create process dumps using windows API')
273	parser.add_argument('-d', '--with-debug', action='store_true', help='enable SeDebugPrivilege, use this if target process is not in the same user context as your script')
274	parser.add_argument('-v', '--verbose', action='count', default=0, help = 'verbosity, add more - see more')
275
276	subparsers = parser.add_subparsers(help = 'commands')
277	subparsers.required = True
278	subparsers.dest = 'command'
279	enumerate_group = subparsers.add_parser('enum', help='Enumerate running processes')
280	dump_group = subparsers.add_parser('dump', help = 'Dump running process')
281	target_group = dump_group.add_mutually_exclusive_group(required=True)
282	target_group.add_argument('-p', '--pid', type=int, help='PID of process to dump')
283	target_group.add_argument('-n', '--name', help='Name of process to dump')
284	dump_group.add_argument('-o', '--outfile', help='Output .dmp file name', required = True)
285
286	args = parser.parse_args()
287
288	if args.verbose == 0:
289		logging.basicConfig(level=logging.INFO)
290	elif args.verbose == 1:
291		logging.basicConfig(level=logging.DEBUG)
292	else:
293		logging.basicConfig(level=1)
294
295	mindumptype = MINIDUMP_TYPE.MiniDumpNormal | MINIDUMP_TYPE.MiniDumpWithFullMemory
296
297	if args.with_debug:
298		logging.debug('Enabling SeDebugPrivilege')
299		assigned = enable_debug_privilege()
300		msg = ['failure', 'success'][assigned]
301		logging.debug('SeDebugPrivilege assignment %s' % msg)
302
303	if args.command == 'enum':
304		pid_to_name = enum_process_names()
305		t = [p for p in pid_to_name]
306		t.sort()
307		for pid in t:
308			logging.info('PID: %d Name: %s' % (pid, pid_to_name[pid]))
309		return
310
311	if args.command == 'dump':
312		if args.pid:
313			logging.info('Dumpig process PID %d' % args.pid)
314			create_dump(args.pid, args.outfile, mindumptype, with_debug = args.with_debug)
315
316		if args.name:
317			pid_to_name = enum_process_names()
318			for pid in pid_to_name:
319				if pid_to_name[pid].find(args.name) != -1:
320					logging.info('Dumpig process PID %d' % pid)
321					create_dump(pid, args.outfile, mindumptype, with_debug = args.with_debug)
322					return
323			logging.info('Failed to find process by name!')
324
325if __name__=='__main__':
326	main()
327
328