Sometimes you’ll want to set a maximum time to debug your target, especially when fuzzing or analyzing malware. This is an example on how to code a custom debugging loop with a timeout. It launches the Windows Calculator and stops when the target process is closed or after a 5 seconds timeout.
from winappdbg import * from time import time # Using the Debug object in a "with" context ensures proper cleanup. with Debug( bKillOnExit = True ) as dbg: # Run the Windows Calculator (calc.exe). dbg.execl('calc.exe') # For the extra paranoid: this makes sure calc.exe dies # even if our own process is killed from the Task Manager. System.set_kill_on_exit_mode(True) # The execution time limit is 5 seconds. maxTime = time() + 5 # Loop while calc.exe is alive and the time limit wasn't reached. while dbg and time() < maxTime: try: # Get the next debug event. dbg.wait(1000) # 1 second accuracy # Show the current time on screen. print time() # If wait() times out just try again. # On any other error stop debugging. except WindowsError, e: if e.winerror in (win32.ERROR_SEM_TIMEOUT, win32.WAIT_TIMEOUT): continue raise # Dispatch the event and continue execution. try: dbg.dispatch() finally: dbg.cont()
This is an example on how to dump the memory map and contents of a process into an SQLite database. A table is created where each row is a memory region, and the columns are the properties of that region (address, size, mapped filename, etc.) and it’s data. The data is compressed using zlib to reduce the database size, but simply commenting out line 160 stores the data in uncompressed form.
import os import sys import zlib import winappdbg from winappdbg import win32 try: import sqlite3 as sqlite except ImportError: from pysqlite2 import dbapi2 as sqlite # Create a snaphot of running processes. system = winappdbg.System() system.request_debug_privileges() system.scan_processes() # Get all processes that match the requested filenames. for filename in sys.argv[1:]: print "Looking for: %s" % filename for process, pathname in system.find_processes_by_filename(filename): pid = process.get_pid() bits = process.get_bits() print "Dumping memory for process ID %d (%d bits)" % (pid, bits) # Parse the database filename. dbfile = '%d.db' % pid if os.path.exists(dbfile): counter = 1 while 1: dbfile = '%d_%.3d.db' % (pid, counter) if not os.path.exists(dbfile): break counter += 1 del counter print "Creating database %s" % dbfile # Connect to the database and get a cursor. database = sqlite.connect(dbfile) cursor = database.cursor() # Create the table for the memory map. cursor.execute(""" CREATE TABLE MemoryMap ( Address INTEGER PRIMARY KEY, Size INTEGER, State STRING, Access STRING, Type STRING, File STRING, Data BINARY ) """) # Get a memory map of the process. memoryMap = process.get_memory_map() mappedFilenames = process.get_mapped_filenames(memoryMap) # For each memory block in the map... for mbi in memoryMap: # Address and size of memory block. BaseAddress = mbi.BaseAddress RegionSize = mbi.RegionSize # State (free or allocated). if mbi.State == win32.MEM_RESERVE: State = "Reserved" elif mbi.State == win32.MEM_COMMIT: State = "Commited" elif mbi.State == win32.MEM_FREE: State = "Free" else: State = "Unknown" # Page protection bits (R/W/X/G). if mbi.State != win32.MEM_COMMIT: Protect = "" else: if mbi.Protect & win32.PAGE_NOACCESS: Protect = "--- " elif mbi.Protect & win32.PAGE_READONLY: Protect = "R-- " elif mbi.Protect & win32.PAGE_READWRITE: Protect = "RW- " elif mbi.Protect & win32.PAGE_WRITECOPY: Protect = "RC- " elif mbi.Protect & win32.PAGE_EXECUTE: Protect = "--X " elif mbi.Protect & win32.PAGE_EXECUTE_READ: Protect = "R-X " elif mbi.Protect & win32.PAGE_EXECUTE_READWRITE: Protect = "RWX " elif mbi.Protect & win32.PAGE_EXECUTE_WRITECOPY: Protect = "RCX " else: Protect = "??? " if mbi.Protect & win32.PAGE_GUARD: Protect += "G" else: Protect += "-" if mbi.Protect & win32.PAGE_NOCACHE: Protect += "N" else: Protect += "-" if mbi.Protect & win32.PAGE_WRITECOMBINE: Protect += "W" else: Protect += "-" # Type (file mapping, executable image, or private memory). if mbi.Type == win32.MEM_IMAGE: Type = "Image" elif mbi.Type == win32.MEM_MAPPED: Type = "Mapped" elif mbi.Type == win32.MEM_PRIVATE: Type = "Private" elif mbi.Type == 0: Type = "" else: Type = "Unknown" # Mapped file name, if any. FileName = mappedFilenames.get(BaseAddress, None) # Read the data contained in the memory block, if any. Data = None if mbi.has_content(): print 'Reading %s-%s' % ( winappdbg.HexDump.address(BaseAddress, bits), winappdbg.HexDump.address(BaseAddress + RegionSize, bits) ) Data = process.read(BaseAddress, RegionSize) Data = zlib.compress(Data, zlib.Z_BEST_COMPRESSION) Data = sqlite.Binary(Data) # Output a row in the table. cursor.execute( 'INSERT INTO MemoryMap VALUES (?, ?, ?, ?, ?, ?, ?)', (BaseAddress, RegionSize, State, Protect, Type, FileName, Data) ) # Commit the changes, close the cursor and the database. database.commit() cursor.close() database.close() print "Ok." print "Done."
This example will find all memory addresses in a target process that are executable and whose address consists of alphanumeric characters only. This is useful when exploiting a stack buffer overflow and the input string is limited to alphanumeric characters only.
Note that in 64 bit processors most memory addresses are not alphanumeric, so this example is meaningful for 32 bits only.
from struct import pack from winappdbg import System, Process, HexDump # Iterator of alphanumeric executable addresses. def iterate_alnum_jump_addresses(process): # Determine the size of a pointer in the current architecture. if System.bits == 32: fmt = 'L' elif System.bits == 64: fmt = 'Q' print "Warning! 64 bit addresses are not likely to be alphanumeric!" else: raise NotImplementedError # Get an iterator for the target process memory. iterator = process.generate_memory_snapshot() # Iterate the memory regions of the target process. for mbi in iterator: # Discard non executable memory. if not mbi.is_executable(): continue # Get the module that owns this memory region, if any. address = mbi.BaseAddress module = process.get_module_at_address(address) # Yield each alphanumeric address in this memory region. max_address = address + mbi.RegionSize while address < max_address: packed = pack(fmt, address) if packed.isalnum(): yield address, packed, module address = address + 1 # Iterate and print alphanumeric executable addresses. def print_alnum_jump_addresses(pid): # Request debug privileges so we can inspect the memory of services too. System.request_debug_privileges() # Suspend the process so there are no malloc's and free's while iterating. process = Process(pid) process.suspend() try: # For each executable alphanumeric address... for address, packed, module in iterate_alnum_jump_addresses(process): # Format the address for printing. numeric = HexDump.address(address, process.get_bits()) ascii = repr(packed) # Format the module name for printing. if module: modname = module.get_name() else: modname = "" # Try to disassemble the code at this location. try: code = process.disassemble(address, 16) except NotImplementedError: code = "" # Print it. print numeric, ascii, modname, code # Resume the process when we're done. # This is inside a "finally" block, so if the program is interrupted # for any reason we don't leave the process suspended. finally: process.resume()
Beginning with Windows XP SP3, it’s possible to query a process and find out its Data Execution Prevention (DEP) settings. It may have DEP enabled or disabled, DEP-ATL thunking emulation enabled or disabled, and these settings may be changeable on runtime or permanent for the lifetime of the process.
This example shows all 32 bits processes the current user has permission to access and shows their DEP settings.
from winappdbg import System, Table from winappdbg.win32 import PROCESS_DEP_ENABLE, \ PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION, \ ERROR_ACCESS_DENIED # Prepare the table. header = ( " PID ", "DEP ", "DEP-ATL ", "Permanent ", "Filename " ) separator = [ " " * len(x) for x in header ] table = Table() table.addRow( *header ) table.addRow( *separator ) # Request debug privileges. System.request_debug_privileges() # Scan for running processes. system = System() try: system.scan_processes() #system.scan_process_filenames() except WindowsError: system.scan_processes_fast() # For each running process... for process in system.iter_processes(): try: # Get the process ID. pid = process.get_pid() # Skip "special" process IDs. if pid in (0, 4, 8): continue # Skip 64 bit processes. if process.get_bits() != 32: continue # Get the DEP policy flags. flags, permanent = process.get_dep_policy() # Determine if DEP is enabled. if flags & PROCESS_DEP_ENABLE: dep = " X" else: dep = "" # Determine if DEP-ATL thunk emulation is enabled. if flags & PROCESS_DEP_DISABLE_ATL_THUNK_EMULATION: atl = "" else: atl = " X" # Determine if the current DEP flag is permanent. if permanent: perm = " X" else: perm = "" # Skip processes we don't have permission to access. except WindowsError, e: if e.winerror == ERROR_ACCESS_DENIED: continue raise # Get the filename. filename = process.get_filename() # Add the process to the table. table.addRow( pid, dep, atl, perm, filename ) # Print the table. table.show()
WinAppDbg supports several disassembler engines. When more than one compatible engine is installed a default one is picked. However, you can manually select which one you want to use.
This example shows you how to list the supported disassembler engines for the desired architecture and pick one.
from sys import argv from winappdbg import Disassembler, HexInput, CrashDump # If there are no command line arguments... if len( argv ) == 1: # Show the help message. print "Usage:" print " %s <file> [offset] [size] [arch] [engine]" % argv # Show the available disassembler engines. print print "Supported disassembler engines:" print "-------------------------------" for engine in Disassembler.engines: print print "Name: %s" % engine.name print "Description: %s" % engine.desc print "Supported architectures: %s" % ", ".join( engine.supported ) # If there are command line arguments... else: # Get the arguments from the command line. filename = argv try: offset = HexInput.address( argv ) except IndexError: offset = 0 try: size = HexInput.integer( argv ) except IndexError: size = 0 try: arch = argv except IndexError: arch = None try: engine = argv except IndexError: engine = None # Load the requested disassembler engine. disasm = Disassembler( arch, engine ) # Load the binary code. with open( filename, 'rb' ) as fd: fd.seek( offset ) if size: code = fd.read( size ) else: code = fd.read() # Disassemble the code. disassembly = disasm.decode( offset, code ) # Show the disassembly. print CrashDump.dump_code( disassembly, offset )
Global atoms are WORD numeric values that can be associated to arbitrary strings. They are used primarily for IPC purposes on Windows XP (Vista and 7 don’t seem to be using them anymore). This example shows how to retrieve the string from any atom value.
from winappdbg.win32 import GlobalGetAtomName, MAXINTATOM # print all valid named global atoms to standard output. def print_atoms(): for x in xrange(0, MAXINTATOM): try: n = GlobalGetAtomName(x) if n == "#%d" % x: # comment out to print continue # valid numeric atoms print "Atom %4x: %r" % (x, n) except WindowsError: pass