Sometimes you’ll want to set a maximum time to debug your target, especially when fuzzing. 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
dbg = Debug(bKillOnExit = True)
try:
dbg.execl('calc.exe')
maxTime = time() + 5 # 5 seconds timeout
while dbg.get_debugee_count() > 0 and time() < maxTime:
try:
print time()
event = dbg.wait(1000)
except WindowsError, e:
if win32.winerror(e) in (win32.ERROR_SEM_TIMEOUT, win32.WAIT_TIMEOUT):
continue
raise
try:
dbg.dispatch(event)
finally:
dbg.cont(event)
finally:
dbg.stop()
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:]:
for process, pathname in system.find_processes_by_filename(filename):
pid = process.get_pid()
print "Dumping memory for process ID %d" % pid
# 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),
winappdbg.HexDump.address(BaseAddress + RegionSize)
)
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.
from struct import pack
from winappdbg import System, Process, HexDump
# Iterator of alphanumeric executable addresses
def iterate_alnum_jump_addresses(memory_snapshot):
# Determine the size of a pointer in the current architecture
if System.bits == 32:
fmt = 'L'
elif System.bits == 64:
fmt = 'Q'
else:
raise NotImplementedError
# Iterate the memory regions of the target process
for mbi in memory_snapshot:
# Discard non executable memory
if not mbi.is_executable():
continue
# Yield each alphanumeric address in this memory region.
address = mbi.BaseAddress
max_address = address + mbi.RegionSize
while address < max_address:
packed = pack(fmt, address)
if packed.isalnum():
yield address, packed
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:
# Get an iterator for the target process memory.
iterator = process.generate_memory_snapshot()
# Print each executable alphanumeric address.
for address, packed in iterate_alnum_jump_addresses(iterator):
print HexDump.address(address), repr(packed)
# 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()
This example hooks all text drawing functions in GDI and prints the text. It can be useful to extract text messages and logs from GUI programs.
from winappdbg import Debug, EventHandler, DebugLog
from ctypes import *
#------------------------------------------------------------------------------
# BOOL TextOut(
# __in HDC hdc,
# __in int nXStart,
# __in int nYStart,
# __in LPCTSTR lpString,
# __in int cbString
# );
def TextOutA(event, ra, hdc, nXStart, nYStart, lpString, cbString):
log_ansi(event, "TextOutA", lpString, cbString)
def TextOutW(event, ra, hdc, nXStart, nYStart, lpString, cbString):
log_wide(event, "TextOutW", lpString, cbString)
# BOOL ExtTextOut(
# __in HDC hdc,
# __in int X,
# __in int Y,
# __in UINT fuOptions,
# __in const RECT *lprc,
# __in LPCTSTR lpString,
# __in UINT cbCount,
# __in const INT *lpDx
# );
def ExtTextOutA(event, ra, hdc, X, Y, fuOptions, lprc, lpString, cbCount, lpDx):
log_ansi(event, "ExtTextOutA", lpString, cbCount)
def ExtTextOutW(event, ra, hdc, X, Y, fuOptions, lprc, lpString, cbCount, lpDx):
log_wide(event, "ExtTextOutW", lpString, cbCount)
# typedef struct _POLYTEXT {
# int x;
# int y;
# UINT n;
# LPCTSTR lpstr;
# UINT uiFlags;
# RECT rcl;
# int *pdx;
# } POLYTEXT, *PPOLYTEXT;
class POLYTEXT(Structure):
_fields_ = [
('x', c_int),
('y', c_int),
('n', c_uint),
('lpstr', c_void_p),
('uiFlags', c_uint),
('rcl', c_uint * 4),
('pdx', POINTER(c_int)),
]
# BOOL PolyTextOut(
# __in HDC hdc,
# __in const POLYTEXT *pptxt,
# __in int cStrings
# );
def PolyTextOutA(event, ra, hdc, pptxt, cStrings):
process = event.get_process()
sizeof_polytext = sizeof(POLYTEXT)
while cStrings:
txt = process.read_structure(pptxt, POLYTEXT)
log_ansi(event, "PolyTextOutA", txt.lpstr, txt.n)
pptxt = pptxt + sizeof_polytext
cStrings = cStrings - 1
def PolyTextOutW(event, ra, hdc, pptxt, cStrings):
process = event.get_process()
sizeof_polytext = sizeof(POLYTEXT)
while cStrings:
txt = process.read_structure(pptxt, POLYTEXT)
log_wide(event, "PolyTextOutW", txt.lpstr, txt.n)
pptxt = pptxt + sizeof_polytext
cStrings = cStrings - 1
#------------------------------------------------------------------------------
def log_ansi(event, fn, lpString, nCount):
if lpString and nCount:
if c_int(nCount).value == -1:
lpString = event.get_process().peek_string(lpString, fUnicode = False)
else:
lpString = event.get_process().peek(lpString, nCount)
print DebugLog.log_text("%s( %r );" % (fn, lpString))
def log_wide(event, fn, lpString, nCount):
if lpString and nCount:
if c_int(nCount).value == -1:
lpString = event.get_process().peek_string(lpString, fUnicode = True)
else:
lpString = event.get_process().peek(lpString, nCount * 2)
lpString = unicode(lpString, 'U16', 'replace')
print DebugLog.log_text("%s( %r );" % (fn, lpString))
class MyEventHandler( EventHandler ):
def load_dll(self, event):
pid = event.get_pid()
module = event.get_module()
if module.match_name("gdi32.dll"):
event.debug.hook_function(pid, module.resolve("TextOutA"), TextOutA, paramCount = 5)
event.debug.hook_function(pid, module.resolve("TextOutW"), TextOutW, paramCount = 5)
event.debug.hook_function(pid, module.resolve("ExtTextOutA"), ExtTextOutA, paramCount = 8)
event.debug.hook_function(pid, module.resolve("ExtTextOutW"), ExtTextOutW, paramCount = 8)
event.debug.hook_function(pid, module.resolve("PolyTextOutA"), PolyTextOutA, paramCount = 2)
event.debug.hook_function(pid, module.resolve("PolyTextOutW"), PolyTextOutW, paramCount = 2)
def simple_debugger(argv):
print DebugLog.log_text("Trace started on %s" % argv[0])
debug = Debug( MyEventHandler() )
try:
debug.execv(argv)
debug.loop()
finally:
debug.stop()
print DebugLog.log_text("Trace stopped on %s" % argv[0])
Global atoms are WORD numeric values that can be associated to arbitrary strings. They are used primarily for IPC purposes on Windows. 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