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