├── __init__.py ├── output └── README ├── re.txt ├── .landscape.yaml ├── README.rst ├── dumper.py ├── maltracer.py └── defines.py /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /output/README: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /re.txt: -------------------------------------------------------------------------------- 1 | http -------------------------------------------------------------------------------- /.landscape.yaml: -------------------------------------------------------------------------------- 1 | strictness: medium 2 | autodetect: yes 3 | 4 | pylint: 5 | disable: 6 | - protected-access 7 | - unused-wildcard-import 8 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Maltracer |landscape badge| 2 | ============================ 3 | 4 | .. |landscape badge| image:: https://landscape.io/github/buffer/maltracer/master/landscape.png 5 | :target: https://landscape.io/github/buffer/maltracer/master 6 | :alt: Code Health 7 | 8 | Win32 Python code for tracing malware activities on infected hosts 9 | -------------------------------------------------------------------------------- /dumper.py: -------------------------------------------------------------------------------- 1 | # dumper.py 2 | # 3 | # Copyright(c) 2009-2010 Angelo Dell'Aera 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | 19 | 20 | from ctypes import * 21 | from defines import * 22 | 23 | kernel32 = windll.kernel32 24 | 25 | class Dumper(object): 26 | def __init__(self, pid, low_addr, hi_addr): 27 | self.pid = pid 28 | self.low_addr = low_addr 29 | self.hi_addr = hi_addr 30 | self.data = "" 31 | 32 | def open_process(self): 33 | self.h_process = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ, 34 | False, 35 | self.pid) 36 | 37 | def close_process(self): 38 | kernel32.CloseHandle(self.h_process) 39 | 40 | def get_system_info(self): 41 | self.system_info = SYSTEM_INFO() 42 | kernel32.GetSystemInfo(byref(self.system_info)) 43 | 44 | def dump_mem(self): 45 | page_size = self.system_info.dwPageSize 46 | #max_addr = self.system_info.lpMaximumApplicationAddress 47 | #min_addr = self.system_info.lpMinimumApplicationAddress 48 | mem = self.low_addr 49 | data = "" 50 | 51 | while (mem < self.hi_addr): 52 | #mbi = MEMORY_BASIC_INFORMATION() 53 | buf = create_string_buffer(page_size) 54 | count = c_ulong(0) 55 | 56 | if not kernel32.ReadProcessMemory(self.h_process, 57 | mem, 58 | buf, 59 | page_size, 60 | byref(count)): 61 | return False 62 | 63 | data = buf.raw 64 | cmem = mem 65 | hex_line = '' 66 | ascii_line = '' 67 | prefix = '' 68 | first = True 69 | 70 | while (cmem < mem + page_size): 71 | if not cmem % 16: 72 | if not first: 73 | hex_line += '"' 74 | self.data += "%s %s %s\n" % (prefix, hex_line, ascii_line) 75 | first = False 76 | 77 | prefix = '/* 0x%x */ ' % (cmem, ) 78 | hex_line = '"' 79 | ascii_line = '// ' 80 | 81 | byte = data[int(cmem - mem)] 82 | if byte.isalnum(): 83 | ascii_line += byte 84 | else: 85 | ascii_line += '.' 86 | 87 | hex_line += str("\\x%s" % (byte.encode('hex'), )) 88 | cmem += 1 89 | 90 | mem += page_size 91 | 92 | def run(self): 93 | self.get_system_info() 94 | self.open_process() 95 | self.dump_mem() 96 | self.close_process() 97 | 98 | if __name__ == "__main__": 99 | pid = raw_input("Process PID: ") 100 | min_addr = raw_input("Low address: ") 101 | max_addr = raw_input("High address: ") 102 | dumpfile = raw_input("Dump file:") 103 | dumper = Dumper(int(pid), 104 | int(min_addr, 16), 105 | int(max_addr, 16)) 106 | dumper.run() 107 | 108 | fd = open(dumpfile, 'wb') 109 | fd.write(dumper.data) 110 | fd.close() 111 | 112 | -------------------------------------------------------------------------------- /maltracer.py: -------------------------------------------------------------------------------- 1 | # maltracer.py 2 | # 3 | # Copyright(c) 2009-2010 Angelo Dell'Aera 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License version 2 as 7 | # published by the Free Software Foundation. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, 17 | # MA 02111-1307 USA 18 | 19 | from ctypes import * 20 | from defines import * 21 | import sys, os, time, datetime 22 | 23 | kernel32 = windll.kernel32 24 | psapi = windll.psapi 25 | 26 | DUMP_DIR = "output" 27 | 28 | class Maltracer(object): 29 | def __init__(self): 30 | pass 31 | 32 | def check_acls(self): 33 | self.regexp = set() 34 | for line in file('re.txt'): 35 | self.regexp.add(line) 36 | 37 | def check_dump_dir(self): 38 | now = datetime.datetime.now() 39 | timestamp = int(time.mktime(now.timetuple())) 40 | 41 | if not os.access(DUMP_DIR, os.F_OK): 42 | try: 43 | os.makedirs(DUMP_DIR) 44 | except: #pylint:disable=bare-except 45 | sys.exit(0) 46 | 47 | self.dumpdir = "%s\\%s" % (DUMP_DIR, str(timestamp)) 48 | if not os.access(self.dumpdir, os.F_OK): 49 | try: 50 | os.makedirs(self.dumpdir) 51 | except: #pylint:disable=bare-except 52 | sys.exit(0) 53 | 54 | def open_process(self, pid): 55 | h_process = kernel32.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_READ, 56 | False, 57 | pid) 58 | return h_process 59 | 60 | def close_process(self, h_process): 61 | kernel32.CloseHandle(h_process) 62 | 63 | def get_system_info(self): 64 | self.system_info = SYSTEM_INFO() 65 | kernel32.GetSystemInfo(byref(self.system_info)) 66 | 67 | def isprintable(self, c): 68 | return ((c >= 0x20) and (c <= 0x7f)) 69 | 70 | def strings(self, raw): 71 | count = 0 72 | lines = [] 73 | line = "" 74 | data = raw 75 | for c in data: 76 | if self.isprintable(ord(c)): 77 | line += c 78 | count += 1 79 | else: 80 | if count > 4: 81 | lines.append(line) 82 | count = 0 83 | line = "" 84 | 85 | return lines 86 | 87 | def dump_mem(self, pid, h_process): 88 | print "[*] PID: %d" % (pid,) 89 | page_size = self.system_info.dwPageSize 90 | max_addr = self.system_info.lpMaximumApplicationAddress 91 | min_addr = self.system_info.lpMinimumApplicationAddress 92 | mem = min_addr 93 | 94 | while (mem < max_addr): 95 | mbi = MEMORY_BASIC_INFORMATION() 96 | count = c_ulong(0) 97 | 98 | if kernel32.VirtualQueryEx(h_process, mem, byref(mbi), sizeof(mbi)) < sizeof(mbi): 99 | mem += page_size 100 | continue 101 | 102 | if mbi.State == 0x1000 and mbi.Type == 0x20000: 103 | buf = create_string_buffer(mbi.RegionSize) 104 | if kernel32.ReadProcessMemory(h_process, 105 | mem, 106 | buf, 107 | mbi.RegionSize, 108 | byref(count)): 109 | for regexp in self.regexp: 110 | if buf.raw.find(regexp) != -1: 111 | d = "%s\\%s" % (self.dumpdir, str(pid)) 112 | if not os.access(d, os.F_OK): 113 | try: 114 | os.makedirs(d) 115 | except: #pylint:disable=bare-except 116 | sys.exit(0) 117 | fd = open("%s\\%s-0x%.8x.dmp" % (d, regexp, mem,), 'wb') 118 | fd.write(buf.raw) 119 | fd.close() 120 | s = self.strings(buf.raw) 121 | fd = open("%s\\%s-0x%.8x.txt" % (d, regexp, mem,), 'wb') 122 | for p in s: 123 | fd.write("%s\n" % (p,) ) 124 | fd.close() 125 | break 126 | mem += mbi.RegionSize 127 | else: 128 | mem += page_size 129 | 130 | def enumerate_processes(self): 131 | array = c_ulong * 1024 132 | aProcesses = array() 133 | cb = sizeof(aProcesses) 134 | cbNeeded = c_ulong() 135 | 136 | psapi.EnumProcesses(byref(aProcesses), cb, byref(cbNeeded)) 137 | nReturned = cbNeeded.value / sizeof(c_ulong) 138 | 139 | pids = [i for i in aProcesses][:nReturned] 140 | return pids 141 | 142 | def run_iexplore(self): 143 | si = STARTUPINFO() 144 | pi = PROCESS_INFORMATION() 145 | kernel32.CreateProcessA("C:\\Program Files\\Internet Explorer\\iexplore.exe", 146 | "iexplore.exe www.google.com", 147 | None, 148 | None, 149 | False, 150 | 0x00000010, 151 | None, 152 | None, 153 | byref(si), 154 | byref(pi)) 155 | 156 | self.iexplore_pid = str(pi.dwProcessId) 157 | self.iexplore_handle = pi.hProcess 158 | print "[*] Starting Internet Explorer (PID %s)" % (self.iexplore_pid, ) 159 | time.sleep(10) 160 | 161 | def terminate_iexplore(self): 162 | kernel32.TerminateProcess(self.iexplore_handle, 1) 163 | 164 | def run(self): 165 | self.check_acls() 166 | self.check_dump_dir() 167 | maltracer_pid = kernel32.GetCurrentProcessId() 168 | print "[*] Maltracer started (PID %d)" % (maltracer_pid, ) 169 | self.run_iexplore() 170 | #pids = self.enumerate_processes() 171 | self.get_system_info() 172 | 173 | #for pid in pids: 174 | for pid in [int(self.iexplore_pid), ]: 175 | if pid == maltracer_pid: 176 | continue 177 | h_process = self.open_process(pid) 178 | self.dump_mem(pid, h_process) 179 | self.close_process(h_process) 180 | self.terminate_iexplore() 181 | 182 | 183 | if __name__ == "__main__": 184 | maltracer = Maltracer() 185 | maltracer.run() 186 | 187 | -------------------------------------------------------------------------------- /defines.py: -------------------------------------------------------------------------------- 1 | 2 | from ctypes import * 3 | 4 | # Let's map the Microsoft types to ctypes for clarity 5 | BYTE = c_ubyte 6 | WORD = c_ushort 7 | DWORD = c_ulong 8 | LPBYTE = POINTER(c_ubyte) 9 | LPTSTR = POINTER(c_char) 10 | HANDLE = c_void_p 11 | PVOID = c_void_p 12 | LPVOID = c_void_p 13 | UINT_PTR = c_ulong 14 | SIZE_T = c_ulong 15 | 16 | PROCESS_CREATE_PROCESS = 0x0080 17 | PROCESS_CREATE_THREAD = 0x0002 18 | PROCESS_DUP_HANDLE = 0x0040 19 | PROCESS_QUERY_INFORMATION = 0x0400 20 | PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 21 | PROCESS_SET_INFORMATION = 0x0200 22 | PROCESS_SET_QUOTA = 0x0100 23 | PROCESS_SUSPEND_RESUME = 0x0800 24 | PROCESS_TERMINATE = 0x0001 25 | PROCESS_VM_OPERATION = 0x0008 26 | PROCESS_VM_READ = 0x0010 27 | PROCESS_VM_WRITE = 0x0020 28 | SYNCHRONIZE = 0x00100000 29 | PROCESS_ALL_ACCESS = 0x001F0FFF 30 | 31 | # Constants 32 | DEBUG_PROCESS = 0x00000001 33 | CREATE_NEW_CONSOLE = 0x00000010 34 | INFINITE = 0xFFFFFFFF 35 | DBG_CONTINUE = 0x00010002 36 | 37 | 38 | # Debug event constants 39 | EXCEPTION_DEBUG_EVENT = 0x1 40 | CREATE_THREAD_DEBUG_EVENT = 0x2 41 | CREATE_PROCESS_DEBUG_EVENT = 0x3 42 | EXIT_THREAD_DEBUG_EVENT = 0x4 43 | EXIT_PROCESS_DEBUG_EVENT = 0x5 44 | LOAD_DLL_DEBUG_EVENT = 0x6 45 | UNLOAD_DLL_DEBUG_EVENT = 0x7 46 | OUTPUT_DEBUG_STRING_EVENT = 0x8 47 | RIP_EVENT = 0x9 48 | 49 | # debug exception codes. 50 | EXCEPTION_ACCESS_VIOLATION = 0xC0000005 51 | EXCEPTION_BREAKPOINT = 0x80000003 52 | EXCEPTION_GUARD_PAGE = 0x80000001 53 | EXCEPTION_SINGLE_STEP = 0x80000004 54 | 55 | 56 | # Thread constants for CreateToolhelp32Snapshot() 57 | TH32CS_SNAPHEAPLIST = 0x00000001 58 | TH32CS_SNAPPROCESS = 0x00000002 59 | TH32CS_SNAPTHREAD = 0x00000004 60 | TH32CS_SNAPMODULE = 0x00000008 61 | TH32CS_INHERIT = 0x80000000 62 | TH32CS_SNAPALL = (TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE) 63 | THREAD_ALL_ACCESS = 0x001F03FF 64 | 65 | # Context flags for GetThreadContext() 66 | CONTEXT_FULL = 0x00010007 67 | CONTEXT_DEBUG_REGISTERS = 0x00010010 68 | 69 | # Memory permissions 70 | PAGE_EXECUTE_READWRITE = 0x00000040 71 | 72 | # Hardware breakpoint conditions 73 | HW_ACCESS = 0x00000003 74 | HW_EXECUTE = 0x00000000 75 | HW_WRITE = 0x00000001 76 | 77 | # Memory page permissions, used by VirtualProtect() 78 | PAGE_NOACCESS = 0x00000001 79 | PAGE_READONLY = 0x00000002 80 | PAGE_READWRITE = 0x00000004 81 | PAGE_WRITECOPY = 0x00000008 82 | PAGE_EXECUTE = 0x00000010 83 | PAGE_EXECUTE_READ = 0x00000020 84 | PAGE_EXECUTE_READWRITE = 0x00000040 85 | PAGE_EXECUTE_WRITECOPY = 0x00000080 86 | PAGE_GUARD = 0x00000100 87 | PAGE_NOCACHE = 0x00000200 88 | PAGE_WRITECOMBINE = 0x00000400 89 | 90 | 91 | MEM_COMMIT = 0x1000 92 | MEM_RESERVE = 0x2000 93 | VIRTUAL_MEM = (MEM_COMMIT | MEM_RESERVE) 94 | 95 | # Structures for CreateProcessA() function 96 | # STARTUPINFO describes how to spawn the process 97 | class STARTUPINFO(Structure): 98 | _fields_ = [ 99 | ("cb", DWORD), 100 | ("lpReserved", LPTSTR), 101 | ("lpDesktop", LPTSTR), 102 | ("lpTitle", LPTSTR), 103 | ("dwX", DWORD), 104 | ("dwY", DWORD), 105 | ("dwXSize", DWORD), 106 | ("dwYSize", DWORD), 107 | ("dwXCountChars", DWORD), 108 | ("dwYCountChars", DWORD), 109 | ("dwFillAttribute",DWORD), 110 | ("dwFlags", DWORD), 111 | ("wShowWindow", WORD), 112 | ("cbReserved2", WORD), 113 | ("lpReserved2", LPBYTE), 114 | ("hStdInput", HANDLE), 115 | ("hStdOutput", HANDLE), 116 | ("hStdError", HANDLE), 117 | ] 118 | 119 | # PROCESS_INFORMATION receives its information 120 | # after the target process has been successfully 121 | # started. 122 | class PROCESS_INFORMATION(Structure): 123 | _fields_ = [ 124 | ("hProcess", HANDLE), 125 | ("hThread", HANDLE), 126 | ("dwProcessId", DWORD), 127 | ("dwThreadId", DWORD), 128 | ] 129 | 130 | # When the dwDebugEventCode is evaluated 131 | class EXCEPTION_RECORD(Structure): 132 | pass 133 | 134 | EXCEPTION_RECORD._fields_ = [ 135 | ("ExceptionCode", DWORD), 136 | ("ExceptionFlags", DWORD), 137 | ("ExceptionRecord", POINTER(EXCEPTION_RECORD)), 138 | ("ExceptionAddress", PVOID), 139 | ("NumberParameters", DWORD), 140 | ("ExceptionInformation", UINT_PTR * 15), 141 | ] 142 | 143 | class _EXCEPTION_RECORD(Structure): 144 | _fields_ = [ 145 | ("ExceptionCode", DWORD), 146 | ("ExceptionFlags", DWORD), 147 | ("ExceptionRecord", POINTER(EXCEPTION_RECORD)), 148 | ("ExceptionAddress", PVOID), 149 | ("NumberParameters", DWORD), 150 | ("ExceptionInformation", UINT_PTR * 15), 151 | ] 152 | 153 | # Exceptions 154 | class EXCEPTION_DEBUG_INFO(Structure): 155 | _fields_ = [ 156 | ("ExceptionRecord", EXCEPTION_RECORD), 157 | ("dwFirstChance", DWORD), 158 | ] 159 | 160 | # it populates this union appropriately 161 | class DEBUG_EVENT_UNION(Union): 162 | _fields_ = [ 163 | ("Exception", EXCEPTION_DEBUG_INFO), 164 | # ("CreateThread", CREATE_THREAD_DEBUG_INFO), 165 | # ("CreateProcessInfo", CREATE_PROCESS_DEBUG_INFO), 166 | # ("ExitThread", EXIT_THREAD_DEBUG_INFO), 167 | # ("ExitProcess", EXIT_PROCESS_DEBUG_INFO), 168 | # ("LoadDll", LOAD_DLL_DEBUG_INFO), 169 | # ("UnloadDll", UNLOAD_DLL_DEBUG_INFO), 170 | # ("DebugString", OUTPUT_DEBUG_STRING_INFO), 171 | # ("RipInfo", RIP_INFO), 172 | ] 173 | 174 | # DEBUG_EVENT describes a debugging event 175 | # that the debugger has trapped 176 | class DEBUG_EVENT(Structure): 177 | _fields_ = [ 178 | ("dwDebugEventCode", DWORD), 179 | ("dwProcessId", DWORD), 180 | ("dwThreadId", DWORD), 181 | ("u", DEBUG_EVENT_UNION), 182 | ] 183 | 184 | # Used by the CONTEXT structure 185 | class FLOATING_SAVE_AREA(Structure): 186 | _fields_ = [ 187 | 188 | ("ControlWord", DWORD), 189 | ("StatusWord", DWORD), 190 | ("TagWord", DWORD), 191 | ("ErrorOffset", DWORD), 192 | ("ErrorSelector", DWORD), 193 | ("DataOffset", DWORD), 194 | ("DataSelector", DWORD), 195 | ("RegisterArea", BYTE * 80), 196 | ("Cr0NpxState", DWORD), 197 | ] 198 | 199 | # The CONTEXT structure which holds all of the 200 | # register values after a GetThreadContext() call 201 | class CONTEXT(Structure): 202 | _fields_ = [ 203 | 204 | ("ContextFlags", DWORD), 205 | ("Dr0", DWORD), 206 | ("Dr1", DWORD), 207 | ("Dr2", DWORD), 208 | ("Dr3", DWORD), 209 | ("Dr6", DWORD), 210 | ("Dr7", DWORD), 211 | ("FloatSave", FLOATING_SAVE_AREA), 212 | ("SegGs", DWORD), 213 | ("SegFs", DWORD), 214 | ("SegEs", DWORD), 215 | ("SegDs", DWORD), 216 | ("Edi", DWORD), 217 | ("Esi", DWORD), 218 | ("Ebx", DWORD), 219 | ("Edx", DWORD), 220 | ("Ecx", DWORD), 221 | ("Eax", DWORD), 222 | ("Ebp", DWORD), 223 | ("Eip", DWORD), 224 | ("SegCs", DWORD), 225 | ("EFlags", DWORD), 226 | ("Esp", DWORD), 227 | ("SegSs", DWORD), 228 | ("ExtendedRegisters", BYTE * 512), 229 | ] 230 | 231 | # THREADENTRY32 contains information about a thread 232 | # we use this for enumerating all of the system threads 233 | 234 | class THREADENTRY32(Structure): 235 | _fields_ = [ 236 | ("dwSize", DWORD), 237 | ("cntUsage", DWORD), 238 | ("th32ThreadID", DWORD), 239 | ("th32OwnerProcessID", DWORD), 240 | ("tpBasePri", DWORD), 241 | ("tpDeltaPri", DWORD), 242 | ("dwFlags", DWORD), 243 | ] 244 | 245 | # Supporting struct for the SYSTEM_INFO_UNION union 246 | class PROC_STRUCT(Structure): 247 | _fields_ = [ 248 | ("wProcessorArchitecture", WORD), 249 | ("wReserved", WORD), 250 | ] 251 | 252 | 253 | # Supporting union for the SYSTEM_INFO struct 254 | class SYSTEM_INFO_UNION(Union): 255 | _fields_ = [ 256 | ("dwOemId", DWORD), 257 | ("sProcStruc", PROC_STRUCT), 258 | ] 259 | # SYSTEM_INFO structure is populated when a call to 260 | # kernel32.GetSystemInfo() is made. We use the dwPageSize 261 | # member for size calculations when setting memory breakpoints 262 | class SYSTEM_INFO(Structure): 263 | _fields_ = [ 264 | ("uSysInfo", SYSTEM_INFO_UNION), 265 | ("dwPageSize", DWORD), 266 | ("lpMinimumApplicationAddress", LPVOID), 267 | ("lpMaximumApplicationAddress", LPVOID), 268 | ("dwActiveProcessorMask", DWORD), 269 | ("dwNumberOfProcessors", DWORD), 270 | ("dwProcessorType", DWORD), 271 | ("dwAllocationGranularity", DWORD), 272 | ("wProcessorLevel", WORD), 273 | ("wProcessorRevision", WORD), 274 | ] 275 | 276 | # MEMORY_BASIC_INFORMATION contains information about a 277 | # particular region of memory. A call to kernel32.VirtualQuery() 278 | # populates this structure. 279 | class MEMORY_BASIC_INFORMATION(Structure): 280 | _fields_ = [ 281 | ("BaseAddress", PVOID), 282 | ("AllocationBase", PVOID), 283 | ("AllocationProtect", DWORD), 284 | ("RegionSize", SIZE_T), 285 | ("State", DWORD), 286 | ("Protect", DWORD), 287 | ("Type", DWORD), 288 | ] 289 | --------------------------------------------------------------------------------