def __init__(self, input): self.data = [] self.pid = int(input) self.debugger = PtraceDebugger() self.process = self.debugger.addProcess(self.pid, is_attached=False) atexit.register(self.debugger.quit) Header = False Code = False self.procmaps = readProcessMappings(self.process) for pm in self.procmaps: if pm.permissions.find("w") != -1 and pm.pathname == None: # if Code == False and Header == True : # data = self.process.readBytes(pm.start, pm.end-pm.start) # idx = data.find("SourceFile") # if idx != -1 : # print "CODE", pm # self.data.append( (pm, data, idx) ) # Code = True if Header == False: data = self.process.readBytes(pm.start, pm.end - pm.start) idx = data.find(MAGIC_PATTERN) if idx != -1: print "HEADER", pm self.data.append((pm, data)) Header = True self.dumpMemory("java_dump_memory")
def __init__(self, pid): self.dbg = PtraceDebugger() self.pid = pid self.proc = psutil.Process(pid) self.network_connections = self.proc.connections() self.dbg_proc = None self.heap_map_info = None self.mem_maps = None
def SetupTarget(): ### Let step up our debugger, fork our target ### and attach the debugger to the target dbg = PtraceDebugger() print("Forking the target...") pid = createChild(target.targetArgs, no_stdout=True) print("Attaching to the target process %d" % pid) process = dbg.addProcess(pid, True) return process
def __init__(self): self.debugger = PtraceDebugger() self.root = None self.stop_requested = False self.syscalls = {} self.backtracer = NullBacktracer() self.debugger.traceClone() self.debugger.traceExec() self.debugger.traceFork()
def main(self): self.debugger = PtraceDebugger() try: self.runDebugger() except KeyboardInterrupt: error("Interrupt debugger: quit!") except PTRACE_ERRORS as err: writeError(getLogger(), err, "Debugger error") self.process = None self.debugger.quit() error("Quit gdb.") self.restoreTerminal()
def _main(self): self.debugger = PtraceDebugger() try: self.runDebugger() except ProcessExit as event: self.processExited(event) except PtraceError as err: error("ptrace() error: %s" % err) except KeyboardInterrupt: error("Interrupted.") except PTRACE_ERRORS as err: writeError(getLogger(), err, "Debugger error") self.debugger.quit()
def main(self): self.debugger = PtraceDebugger() try: self.runDebugger() except ChildError as event: self.event_callback(event) except ProcessExit as event: self.processExited(event) except (KeyError, PtraceError, OSError) as error: self._handle_exceptions_during_quit(error, 'main') if self.debugger: self.debugger.quit() self.quit_callback()
def do_dump (process, maxmem): print_process (process, "===") # Attach to the process debugger = PtraceDebugger() try: d_process = debugger.addProcess(process['pid'], False) except: print("Error attaching to the process pid {0}. Aborting".format(process['pid'])) sys.exit(1) d_process.was_attached = True procmaps = readProcessMappings(d_process) # Look for process heap region for pm in procmaps: if pm.pathname == None: mem_start=pm.start mem_end=pm.end mem_total=mem_end-mem_start if maxmem > 0 and mem_total > maxmem : print("Process memory is {0} but you defined maxmem to {1}".format(mem_total, maxmem)) return False # Use StringIO to work only in memory. This can be dangerous, because "heap" can be big. the_mem = StringIO.StringIO() # Transfer process heap memory to the_mem var. the_mem.write(d_process.readBytes(mem_start, mem_total)) # We have what we were looking for. Let's detach the process. d_process.detach() # Start search request_end=0 found=False while True: hdr_pos=fnd(the_mem, process['request'], request_end) if hdr_pos==-1: # EOF break request_pos=hdr_pos # This is the start of the possible match request_end=fnd(the_mem, "\x00", request_pos) # This is the end of the possible match # If we find double new line then this should be a valid request and data block. if fnd(the_mem,"\x0d\x0a\x0d\x0a", request_pos) < request_end: found=True # If valid, print it! print get_fragment(the_mem, request_pos, request_end) # Prepare to continue searching the_mem.seek(request_end+1) the_mem.close() if found: break
def __init__(self, project): ProjectAgent.__init__(self, project, "dbg") self.debugger = None self.enabled = (HAS_PTRACE and project.config.use_debugger) self.fusil_processes = {} if self.enabled: self.error("Use python-ptrace debugger") self.debugger = PtraceDebugger() if project.config.debugger_trace_forks: try: self.debugger.traceFork() self.warning("Debugger trace process forks") except PtraceDebuggerError, err: self.error("Unable to trace forks: %s" % err)
class ProcessDebugger (object): def __init__(self, pid): self.debugger = PtraceDebugger () self.process = self.debugger.addProcess (int (pid), False) self.addrs = list () # TODO: Search's address in process memory by value. def search_addr_by_value (self, value): for i in self.process.readMappings(): # F**k, this is invalid bin :( if i.pathname in ['[vsyscall]', '[vdso]']: continue try: for j in i.search (value): self.addrs.append (j) except: pass return self.addrs # TODO: Convert's boolean value to binary. def search_boolean_value (self, value): return self.search_addr_by_value (struct.pack (data.BIT_ENDOF + '?', value)) # TODO: Convert's integer value to binary. def search_integer_value (self, value): return self.search_addr_by_value(struct.pack(data.BIT_ENDOF + 'i', value))
class AndroPreDump(object): def __init__(self, input) : self.data = [] self.pid = int(input) self.debugger = PtraceDebugger() self.process = self.debugger.addProcess(self.pid, is_attached=False) atexit.register(self.debugger.quit) Header = False Code = False self.procmaps = readProcessMappings(self.process) for pm in self.procmaps: if pm.permissions.find("w") != -1 and pm.pathname == None : # if Code == False and Header == True : # data = self.process.readBytes(pm.start, pm.end-pm.start) # idx = data.find("SourceFile") # if idx != -1 : # print "CODE", pm # self.data.append( (pm, data, idx) ) # Code = True if Header == False : data = self.process.readBytes(pm.start, pm.end-pm.start) idx = data.find(MAGIC_PATTERN) if idx != -1 : print "HEADER", pm self.data.append( (pm, data) ) Header = True self.dumpMemory( "java_dump_memory" ) # self.dumpFiles( "java_files" ) def write(self, idx, buff) : self.process.writeBytes( idx, buff ) def getFilesBuffer(self) : for i in self.data : d = i[1] x = d.find(MAGIC_PATTERN) idx = x while x != -1 : yield i[0].start + idx, d[x:] d = d[x+len(MAGIC_PATTERN):] idx += len(MAGIC_PATTERN) x = d.find(MAGIC_PATTERN) idx += x def dumpMemory(self, base_filename) : for i in self.data : with open(base_filename + "-" + "0x%x-0x%x" % (i[0].start, i[0].end), "w") as fd: fd.write( i[1] ) def dumpFiles(self, base_filename) : for i in self.data : with fd = open(base_filename + "-" + "0x%x-0x%x" % (i[0].start + i[2], i[0].end), "w") as fd: fd.write( i[1][i[2]:] )
def main(): global options options = get_options() dbg = PtraceDebugger() pid = int(subprocess.check_output(['pidof', "typespeed"])) process = dbg.addProcess(pid, False) process.syscall() while True: event = dbg.waitSyscall() process = event.process op_syscall(process) dbg.quit()
def debugFindRtld(pid): dbg = PtraceDebugger() process = dbg.addProcess(pid, False) for pm in readProcessMappings(process): if 'rwx' in pm.permissions: for addr in pm.search(b'\x78\x56\x34\x12'): # There are no ret instructions in the CC memory = process.readBytes(addr, 20) if b'\xC3' not in memory: process = None dbg.quit() return addr print('ERROR: Could not find the address of rtld_lock_default_lock_recursive in code cache!') exit(-1)
def __init__(self, input) : self.data = [] self.pid = int(input) self.debugger = PtraceDebugger() self.process = self.debugger.addProcess(self.pid, is_attached=False) atexit.register(self.debugger.quit) Header = False Code = False self.procmaps = readProcessMappings(self.process) for pm in self.procmaps: if pm.permissions.find("w") != -1 and pm.pathname == None : # if Code == False and Header == True : # data = self.process.readBytes(pm.start, pm.end-pm.start) # idx = data.find("SourceFile") # if idx != -1 : # print "CODE", pm # self.data.append( (pm, data, idx) ) # Code = True if Header == False : data = self.process.readBytes(pm.start, pm.end-pm.start) idx = data.find(MAGIC_PATTERN) if idx != -1 : print "HEADER", pm self.data.append( (pm, data) ) Header = True self.dumpMemory( "java_dump_memory" )
def start_trace(cmd): new_pid = createChild(cmd, no_stdout=False) debugger = PtraceDebugger() debugger.traceFork() debugger.traceExec() debugger.addProcess(new_pid, is_attached=True) return debugger
class Debugger(ProjectAgent): def __init__(self, project): ProjectAgent.__init__(self, project, "dbg") self.debugger = None self.enabled = (HAS_PTRACE and project.config.use_debugger) self.fusil_processes = {} if self.enabled: self.error("Use python-ptrace debugger") self.debugger = PtraceDebugger() if project.config.debugger_trace_forks: try: self.debugger.traceFork() self.warning("Debugger trace process forks") except PtraceDebuggerError, err: self.error("Unable to trace forks: %s" % err) else:
def _main(self): self.debugger = PtraceDebugger() try: self.runDebugger() except ProcessExit as event: self.processExited(event) except PtraceError as err: error("ptrace() error: %s" % err) except KeyboardInterrupt: error("Interrupted.") except PTRACE_ERRORS as err: writeError(getLogger(), err, "Debugger error") processes = list(self.debugger.list) self.debugger.quit() # python-ptrace seems iffy on actually detaching sometimes, so we make # sure we let the process continue for process in processes: subprocess.check_output(['kill', '-cont', str(process.pid)])
def _main(self): self.debugger = PtraceDebugger() exitcode = 0 try: exitcode = self.runDebugger() except ProcessExit as event: self.processExited(event) if event.exitcode is not None: exitcode = event.exitcode except PtraceError as err: error("ptrace() error: %s" % err) if err.errno is not None: exitcode = err.errno except KeyboardInterrupt: error("Interrupted.") exitcode = 1 except PTRACE_ERRORS as err: writeError(getLogger(), err, "Debugger error") exitcode = 1 self.debugger.quit() return exitcode
def startDebugger(self, args): debugger = PtraceDebugger() debugger.traceFork() debugger.traceExec() debugger.enableSysgood( ) # to differentiate between traps raised by syscall, no syscall newProcess = ProcessWrapper(args=args, debugger=debugger, redirect=True) # first process newProcess.syscalls_to_trace = self.syscalls_to_trace self.addProcess(newProcess) return debugger
def run(self): """ Trace the given process or thread for one syscall """ finished = False self.record.log("Attaching debugger") try: self.debugger = PtraceDebugger() self.debugger.traceFork() self.debugger.traceExec() self.debugger.traceClone() self.traced_item = self.debugger.addProcess( self.process.pid, False, is_thread=self.is_thread) self.record.log("PTrace debugger attached successfully") TracingThread._trace_continue(self.traced_item) except Exception as e: self.record.log( "PTrace debugger attachment failed with reason: {}".format(e)) finished = True # Trace process until finished while not finished and not self.stopped: finished = self._trace(self.traced_item, self.record) if self.traced_item: self.traced_item.detach() # Keep in mind that the process maybe already gone try: if self.process.status() == psutil.STATUS_STOPPED: posix.kill(self.process.pid, signal.SIGCONT) except psutil.NoSuchProcess: pass self.record.log("Tracee appears to have ended and thread will finish")
def getData(self, inputs): self.events = [] self.nevents = dict() self.debugger = PtraceDebugger() self.runProcess([self.program] + inputs) # print self.pid # if self.crashed: # print "we should terminate.." # sleep(3) if self.process is None: return None self.process.terminate() self.process.detach() # print self.nevents self.process = None return self.events
def main(self): self.debugger = PtraceDebugger() self.setupDebugger() # Create new process try: self.process = self.createProcess() except ChildError as err: writeError(getLogger(), err, "Unable to create child process") return if not self.process: return #self.backtrace() #self.process.dumpMaps() while True: if not self.debugger: # There is no more process: quit return # Execute the user command try: self.syscallTrace() except KeyboardInterrupt: self.interrupt() except NewProcessEvent as event: self.newProcess(event) except ProcessSignal as event: self.processSignal(event) except ProcessExit as event: error(event) self.nextProcess() except ProcessExecution as event: self.processExecution(event) except PtraceError as err: error("ERROR: %s" % err) if err.errno == ESRCH: self.deleteProcess(err.pid)
class SyscallTracer(Application): def __init__(self): Application.__init__(self) # Parse self.options self.parseOptions() # Setup output (log) self.setupLog() def setupLog(self): if self.options.output: fd = open(self.options.output, 'w') self._output = fd else: fd = stderr self._output = None self._setupLog(fd) def parseOptions(self): parser = OptionParser(usage="%prog pid") parser.add_option("--no-fork", "-n", help="Don't trace forks", action="store_false", dest="fork", default=True) if len(sys.argv) == 1: parser.print_help() exit(1) self.options, self.program = parser.parse_args() defaults = ( ('pid', int(sys.argv[1])), ('trace_exec', False), ('enter', False), ('profiler', False), ('type', False), ('name', False), ('string_length', 99999999), ('array_count', 20), ('raw_socketcall', False), ('output', ''), ('ignore_regex', ''), ('address', False), ('syscalls', None), ('socket', False), ('filename', False), ('show_pid', False), ('list_syscalls', False), ('show_ip', False), ('debug', False), ('verbose', False), ('quiet', False), ) for option, value in defaults: setattr(self.options, option, value) self.only = set() self.ignore_regex = None if self.options.fork: self.options.show_pid = True self.processOptions() def ignoreSyscall(self, syscall): name = syscall.name if self.only and (name not in self.only): return True if self.ignore_regex and self.ignore_regex.match(name): return True return False def displaySyscall(self, syscall): if syscall.name == 'write': fd = syscall.arguments[0].value if fd < 3: text = syscall.arguments[1].getText()[1:-1] stdout.write(text.decode('string_escape')) stdout.flush() def syscallTrace(self, process): # First query to break at next syscall self.prepareProcess(process) while True: # No more process? Exit if not self.debugger: break # Wait until next syscall enter try: event = self.debugger.waitSyscall() process = event.process except ProcessExit as event: self.processExited(event) continue except ProcessSignal as event: # event.display() process.syscall(event.signum) continue except NewProcessEvent as event: self.newProcess(event) continue except ProcessExecution as event: self.processExecution(event) continue # Process syscall enter or exit self.syscall(process) def syscall(self, process): state = process.syscall_state syscall = state.event(self.syscall_options) if syscall and (syscall.result is not None or self.options.enter): self.displaySyscall(syscall) # Break at next syscall process.syscall() def processExited(self, event): # Display syscall which has not exited state = event.process.syscall_state if (state.next_event == "exit" and (not self.options.enter) and state.syscall): self.displaySyscall(state.syscall) def prepareProcess(self, process): process.syscall() process.syscall_state.ignore_callback = self.ignoreSyscall def newProcess(self, event): process = event.process self.prepareProcess(process) process.parent.syscall() def processExecution(self, event): process = event.process process.syscall() def runDebugger(self): # Create debugger and traced process self.setupDebugger() process = self.createProcess() if not process: return self.syscall_options = FunctionCallOptions( write_types=self.options.type, write_argname=self.options.name, string_max_length=self.options.string_length, replace_socketcall=not self.options.raw_socketcall, write_address=self.options.address, max_array_count=self.options.array_count, ) self.syscall_options.instr_pointer = self.options.show_ip self.syscallTrace(process) def main(self): self._main() if self._output is not None: self._output.close() def _main(self): self.debugger = PtraceDebugger() try: self.runDebugger() except ProcessExit as event: self.processExited(event) except PtraceError as err: error("ptrace() error: %s" % err) except KeyboardInterrupt: error("Interrupted.") except PTRACE_ERRORS as err: writeError(getLogger(), err, "Debugger error") processes = list(self.debugger.list) self.debugger.quit() # python-ptrace seems iffy on actually detaching sometimes, so we make # sure we let the process continue for process in processes: subprocess.check_output(['kill', '-cont', str(process.pid)]) def createChild(self, program): pid = Application.createChild(self, program) error("execve(%s, %s, [/* 40 vars */]) = %s" % (program[0], program, pid)) return pid
class Gdb(Application): def __init__(self): Application.__init__(self) # Parse self.options self.parseOptions() # Setup output (log) self.setupLog() self.last_signal = {} # We assume user wants all possible information self.syscall_options = FunctionCallOptions( write_types=True, write_argname=True, write_address=True, ) # FIXME: Remove self.breaks! self.breaks = dict() self.followterms = [] def setupLog(self): self._setupLog(stdout) def parseOptions(self): parser = OptionParser( usage="%prog [options] -- program [arg1 arg2 ...]") self.createCommonOptions(parser) self.createLogOptions(parser) self.options, self.program = parser.parse_args() if self.options.pid is None and not self.program: parser.print_help() exit(1) self.processOptions() self.show_pid = self.options.fork def _continueProcess(self, process, signum=None): if not signum and process in self.last_signal: signum = self.last_signal[process] if signum: error("Send %s to %s" % (signalName(signum), process)) process.cont(signum) try: del self.last_signal[process] except KeyError: pass else: process.cont() def cont(self, signum=None): for process in self.debugger: process.syscall_state.clear() if process == self.process: self._continueProcess(process, signum) else: self._continueProcess(process) # Wait for a process signal signal = self.debugger.waitSignals() process = signal.process # Hit breakpoint? if signal.signum == SIGTRAP: ip = self.process.getInstrPointer() if not CPU_POWERPC: # Go before "INT 3" instruction ip -= 1 breakpoint = self.process.findBreakpoint(ip) if breakpoint: error("Stopped at %s" % breakpoint) breakpoint.desinstall(set_ip=True) else: self.processSignal(signal) return None def readRegister(self, regs): name = regs.group(0)[1:] value = self.process.getreg(name) return str(value) def parseInteger(self, text): # Remove spaces and convert to lower case text = text.strip() if " " in text: raise ValueError("Space are forbidden: %r" % text) text = text.lower() # Replace registers by their value orig_text = text text = REGISTER_REGEX.sub(self.readRegister, text) # Replace hexadecimal numbers by decimal numbers def readHexadecimal(regs): text = regs.group(0) if text.startswith("0x"): text = text[2:] elif not re.search("[a-f]", text): return text value = int(text, 16) return str(value) text = re.sub(r"(?:0x)?[0-9a-f]+", readHexadecimal, text) # Reject invalid characters if not re.match(r"^[()<>+*/&0-9-]+$", text): raise ValueError("Invalid expression: %r" % orig_text) # Use integer division (a//b) instead of float division (a/b) text = text.replace("/", "//") # Finally, evaluate the expression is_pointer = text.startswith("*") if is_pointer: text = text[1:] try: value = eval(text) value = truncateWord(value) except SyntaxError: raise ValueError("Invalid expression: %r" % orig_text) if is_pointer: value = self.process.readWord(value) return value def parseIntegers(self, text): values = [] for item in text.split(): item = item.strip() value = self.parseInteger(item) values.append(value) return values def parseBytes(self, text): # FIXME: Validate input # if not BYTES_REGEX.match(text): # raise ValueError('Follow text must be enclosed in quotes!') if PY3: text = 'b' + text.lstrip() value = eval(text) if not isinstance(value, binary_type): raise TypeError("Input is not a bytes string!") return value def addFollowTerm(self, text): # Allow terms of the form 'string', "string", '\x04', "\x01\x14" term = self.parseBytes(text) self.followterms.append(term) def showFollowTerms(self): print(self.followterms) def _xray(self): for term in self.followterms: for process in self.debugger: for procmap in readProcessMappings(process): for address in procmap.search(term): yield (process, procmap, address, term) # displays the offsets of all terms found in the process memory mappings # along with possible addresses of pointers pointing to these terms def xray(self): for process, procmap, address, term in self._xray(): pointers = " ".join(formatAddress(ptr_addr) for ptr_addr in getPointers(process, address)) print("term[%s] pid[%i] %s %s pointers: %s" % ( repr(term), process.pid, procmap, formatAddress(address), pointers)) def execute(self, command): errmsg = None if command == "cont": errmsg = self.cont() elif command == "proc": self.procInfo() elif command == "proclist": self.procList() elif command.startswith("attach "): errmsg = self.attachProcess(command[7:]) elif command == "regs": self.process.dumpRegs() elif command == "stack": self.process.dumpStack() elif command == "gcore": self.gcore(self.process) elif command == "backtrace": errmsg = self.backtrace() elif command == "where" or command.startswith("where "): errmsg = self.where(command[6:]) elif command == "where2" or command.startswith("where2 "): errmsg = self.where(command[7:], manage_bp=True) elif command == "maps": self.process.dumpMaps() elif command == "dbginfo": self.debuggerInfo() elif command == "step": errmsg = self.step(False) elif command == "stepi": errmsg = self.step(True) elif command == "sys": errmsg = self.syscallTrace() elif command == "help": self.help() elif command.startswith("set "): errmsg = self.set(command) elif command.startswith("until "): errmsg = self.until(command[6:]) elif command.startswith("switch") or command == "switch": errmsg = self.switch(command[6:]) elif command.startswith("break "): errmsg = self.breakpoint(command[6:]) elif command.startswith("breakpoints"): self.displayBreakpoints() elif command.startswith("signals"): self.displaySignals() elif command.startswith("delete "): errmsg = self.delete(command[7:]) elif command.startswith("hexdump "): errmsg = self.hexdump(command[8:]) elif command.startswith("signal "): errmsg = self.signal(command[7:]) elif command.startswith("print "): errmsg = self.print_(command[6:]) elif command.startswith("follow "): errmsg = self.addFollowTerm(command[7:]) elif command == "showfollow": self.showFollowTerms() elif command == "resetfollow": self.followterms = [] elif command == "xray": self.xray() else: errmsg = "Unknown command: %r" % command if errmsg: print(errmsg, file=stderr) return False return True def parseSignum(self, command): try: return SIGNALS[command] except KeyError: pass try: return SIGNALS["SIG" + command] except KeyError: pass try: return self.parseInteger(command) except ValueError: raise ValueError("Invalid signal number: %r" % command) def signal(self, command): try: signum = self.parseSignum(command) except ValueError as err: return str(err) last_process = self.process try: errmsg = self.cont(signum) return errmsg finally: try: del self.last_signal[last_process] except KeyError: pass def print_(self, command): try: value = self.parseInteger(command) except ValueError as err: return str(err) error("Decimal: %s" % value) error("Hexadecimal: %s" % formatWordHex(value)) for map in self.process.readMappings(): if value not in map: continue error("Address is part of mapping: %s" % map) return None def gcore(self, process): import re childPid = str(process).split('#')[-1][:-1] maps_file = open("/proc/" + childPid + "/maps", 'r') mem_file = open("/proc/" + childPid + "/mem", 'r', 0) from sys import argv dump = open("/vmdump/" + argv[1] + ".dump", "wb") for line in maps_file.readlines(): # for each mapped region m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line) if m.group(3) == 'r': # if this is a readable region if "/lib" not in line and "/usr" not in line: # for eliminating of shared libs start = int(m.group(1), 16) end = int(m.group(2), 16) mem_file.seek(start) # seek to region start chunk = mem_file.read(end - start) # read region contents dump.write(chunk,) # dump contents to standard output maps_file.close() mem_file.close() dump.close() def hexdump(self, command): max_line = 20 width = (terminalWidth() - len(formatAddress(1)) - 3) // 4 width = max(width, 1) limited = None parts = command.split(" ", 1) if 1 < len(parts): try: start_address = self.parseInteger(parts[0]) end_address = self.parseInteger(parts[1]) if end_address <= start_address: raise ValueError('End address (%s) is smaller than start address(%s)!' % (formatAddress(end_address), formatAddress(start_address))) except ValueError as err: return str(err) size = end_address - start_address max_size = width * max_line if max_size < size: limited = max_size end_address = start_address + max_size else: try: start_address = self.parseInteger(command) except ValueError as err: return str(err) end_address = start_address + 5 * width read_error = None address = start_address while address < end_address: size = min(end_address - address, width) try: # Read bytes memory = self.process.readBytes(address, size) # Format bytes hexa = formatHexa(memory) hexa = hexa.ljust(width * 3 - 1, ' ') ascii = formatAscii(memory) ascii = ascii.ljust(width, ' ') # Display previous read error, if any if read_error: warning("Warning: Unable to read memory %s" % ( formatAddressRange(*read_error))) read_error = None # Display line error("%s| %s| %s" % (formatAddress(address), hexa, ascii)) except PtraceError: if not read_error: read_error = [address, address + size] else: read_error[1] = address + size address += size # Display last read error, if any if read_error: warning("Warning: Unable to read memory %s" % ( formatAddressRange(*read_error))) if limited: warning("(limit to %s bytes)" % max_size) return None def backtrace(self): trace = self.process.getBacktrace() for func in trace: error(func) if trace.truncated: error("--limited to depth %s--" % len(trace)) return None def where(self, command, manage_bp=False): start = None stop = None try: values = self.parseIntegers(command) except ValueError as err: return str(err) if 1 <= len(values): start = values[0] if 2 <= len(values): stop = values[1] self.process.dumpCode(start, stop, manage_bp=manage_bp) return None def procInfo(self): dumpProcessInfo(error, self.process.pid, max_length=160) def procList(self): for process in self.debugger: text = str(process) if self.process == process: text += " (active)" error(text) def set(self, command): try: key, value = command[4:].split("=", 1) key = key.strip().lower() if not key.startswith("$"): return 'Register name (%s) have to start with "$"' % key key = key[1:] except ValueError: return "Invalid command: %r" % command try: value = self.parseInteger(value) except ValueError as err: return str(err) try: self.process.setreg(key, value) except ProcessError as err: return "Unable to set $%s=%s: %s" % (key, value, err) error("Set $%s to %s" % (key, value)) return None def displayInstr(self, prefix): try: if HAS_DISASSEMBLER: instr = self.process.disassembleOne() error("%s %s: %s" % ( prefix, formatAddress(instr.address), instr.text)) else: self.process.dumpCode() except PtraceError as err: error("Unable to read current instruction: %s" % err) def attachProcess(self, text): try: pid = self.parseInteger(text) except ValueError as err: return str(err) process = self.debugger.addProcess(pid, False) self.switchProcess(process) def step(self, enter_call, address=None): if address is None: self.displayInstr("Execute") if (not HAS_PTRACE_SINGLESTEP) or (not enter_call): if address is None: address = self.process.getInstrPointer() size = self.readInstrSize(address, default_size=None) if not size: return "Unable to read instruction size at %s" \ % formatAddress(address) address += size size = self.readInstrSize(address) # Set a breakpoint breakpoint = self.process.createBreakpoint(address, size) # Continue the process self.process.cont() else: # Use ptrace single step command self.process.singleStep() breakpoint = None # Execute processus until next TRAP try: self.process.waitSignals(SIGTRAP) if breakpoint: breakpoint.desinstall(set_ip=True) except: # noqa: E722 if breakpoint: breakpoint.desinstall() raise return None def newProcess(self, event): error("New process: %s" % event.process) # FIXME: This function doesn't work with multiple processes # especially when a parent waits for a child def syscallTrace(self): # Trace until syscall enter self.process.syscall() self.process.waitSyscall() # Process the syscall event state = self.process.syscall_state syscall = state.event(self.syscall_options) # Display syscall if syscall: if syscall.result is not None: text = "%s = %s" % (syscall.format(), syscall.result_text) if self.show_pid: text = "Process %s exits %s" % (syscall.process.pid, text) error(text) else: text = syscall.format() if self.show_pid: text = "Process %s enters %s" % (syscall.process.pid, text) error(text) return None def until(self, command): try: address = self.parseInteger(command) except ValueError as err: return str(err) errmsg = self.step(False, address) if errmsg: return errmsg self.displayInstr("Current") return None def switch(self, command): if not command: process_list = self.debugger.list if len(process_list) == 1: return "There is only one process!" index = process_list.index(self.process) index = (index + 1) % len(process_list) process = process_list[index] self.switchProcess(process) return try: pid = self.parseInteger(command) except ValueError as err: return str(err) try: process = self.debugger[pid] self.switchProcess(process) except KeyError: return "There is not process %s" % pid return None def switchProcess(self, process): if self.process == process: return error("Switch to %s" % process) self.process = process def nextProcess(self): try: process = next(iter(self.debugger)) self.switchProcess(process) except StopIteration: pass def displayBreakpoints(self): found = False for process in self.debugger: for bp in process.breakpoints.values(): found = True error("%s:%s" % (process, bp)) if not found: error("(no breakpoint)") def displaySignals(self): signals = list(SIGNAMES.items()) signals.sort(key=lambda key_value: key_value[0]) for signum, name in signals: error("% 2s: %s" % (signum, name)) def readInstrSize(self, address, default_size=None): if not HAS_DISASSEMBLER: return default_size try: # Get address and size of instruction at specified address instr = self.process.disassembleOne(address) return instr.size except PtraceError as err: warning("Warning: Unable to read instruction size at %s: %s" % ( formatAddress(address), err)) return default_size def breakpoint(self, command): try: address = self.parseInteger(command) except ValueError as err: return str(err) # Create breakpoint size = self.readInstrSize(address) try: bp = self.process.createBreakpoint(address, size) except PtraceError as err: return "Unable to set breakpoint at %s: %s" % ( formatAddress(address), err) error("New breakpoint: %s" % bp) return None def delete(self, command): try: address = self.parseInteger(command) except ValueError as err: return str(err) breakpoint = self.process.findBreakpoint(address) if not breakpoint: return "No breakpoint at %s " % formatAddress(address) breakpoint.desinstall() error("%s deleted" % breakpoint) return None def help(self): for command, description in COMMANDS: error("%s: %s" % (command, description)) error('') error("Value can be an hexadecimal/decimal number or a register name ($reg)") error("You can use operators a+b, a-b, a*b, a/b, a<<b, a>>b, a**b, and parenthesis in expressions") error( 'Use ";" to write multiple commands on the same line (e.g. "step; print $eax")') def processSignal(self, event): event.display() self.switchProcess(event.process) self.last_signal[self.process] = event.signum error("%s interrupted by %s" % (self.process, event.name)) def processExecution(self, event): error(event) self.switchProcess(event.process) self.interrupt() def debuggerInfo(self): error("Debugger process ID: %s" % getpid()) error("python-ptrace version %s" % VERSION) error("Website: %s" % WEBSITE) def interrupt(self): waitlist = [] for process in self.debugger: if process.is_stopped: continue try: if process.isTraced(): continue except NotImplementedError: pass warning("Interrupt %s (send SIGINT)" % process) process.kill(SIGINT) waitlist.append(process) for process in waitlist: info("Wait %s interruption" % process) try: process.waitSignals(SIGINT) except ProcessSignal as event: event.display() except KeyboardInterrupt: pass def deleteProcess(self, pid): try: process = self.debugger[pid] except KeyError: return event = process.processTerminated() error(str(event)) if process == self.process: self.nextProcess() def restoreTerminal(self): if enableEchoMode(): error("Terminal: restore echo mode") def mainLoop(self): # Read command try: self.restoreTerminal() command = raw_input(self.invite).strip() except EOFError: print() return True except KeyboardInterrupt: error("User interrupt!") self.interrupt() return False # If command is empty, reuse previous command if not command: if self.previous_command: command = self.previous_command info("Replay previous command: %s" % command) else: return False self.previous_command = None # User wants to quit? if command == "quit": return True # Execute the user command try: command_str = command ok = True for command in command_str.split(";"): command = command.strip() try: ok &= self.execute(command) except Exception as err: print("Command error: %s" % err) raise ok = False if not ok: break if ok: self.previous_command = command_str except KeyboardInterrupt: self.interrupt() except NewProcessEvent as event: self.newProcess(event) except ProcessSignal as event: self.processSignal(event) except ProcessExit as event: error(event) self.nextProcess() except ProcessExecution as event: self.processExecution(event) except PtraceError as err: error("ERROR: %s" % err) if err.errno == ESRCH: self.deleteProcess(err.pid) return False def runDebugger(self): self.setupDebugger() # Create new process try: self.process = self.createProcess() except ChildError as err: writeError(getLogger(), err, "Unable to create child process") return if not self.process: return # Trace syscalls self.invite = '(gdb) ' self.previous_command = None while True: if not self.debugger: # There is no more process: quit return done = self.mainLoop() if done: return def main(self): self.debugger = PtraceDebugger() try: self.runDebugger() except KeyboardInterrupt: error("Interrupt debugger: quit!") except PTRACE_ERRORS as err: writeError(getLogger(), err, "Debugger error") self.process = None self.debugger.quit() error("Quit gdb.") self.restoreTerminal()
def __init__(self): self.pid = searchProcessByName('wow.exe') self.name = "" print("Attach the running process %s" % self.pid) self.__tracer = PtraceProcess(PtraceDebugger(), self.pid, False)
class SyscallTracer(Application): def __init__(self): Application.__init__(self) # Parse self.options self.parseOptions() # Setup output (log) self.setupLog() def setupLog(self): if self.options.output: fd = open(self.options.output, 'w') self._output = fd else: fd = stderr self._output = None self._setupLog(fd) def parseOptions(self): parser = OptionParser(usage="%prog [options] -- program [arg1 arg2 ...]") self.createCommonOptions(parser) parser.add_option("--enter", help="Show system call enter and exit", action="store_true", default=False) parser.add_option("--profiler", help="Use profiler", action="store_true", default=False) parser.add_option("--type", help="Display arguments type and result type (default: no)", action="store_true", default=False) parser.add_option("--name", help="Display argument name (default: no)", action="store_true", default=False) parser.add_option("--string-length", "-s", help="String max length (default: 300)", type="int", default=300) parser.add_option("--array-count", help="Maximum number of array items (default: 20)", type="int", default=20) parser.add_option("--raw-socketcall", help="Raw socketcall form", action="store_true", default=False) parser.add_option("--output", "-o", help="Write output to specified log file", type="str") parser.add_option("--ignore-regex", help="Regex used to filter syscall names (eg. --ignore='^(gettimeofday|futex|f?stat)')", type="str") parser.add_option("--address", help="Display structure addressl", action="store_true", default=False) parser.add_option("--syscalls", '-e', help="Comma separated list of shown system calls (other will be skipped)", type="str", default=None) parser.add_option("--socket", help="Show only socket functions", action="store_true", default=False) parser.add_option("--filename", help="Show only syscall using filename", action="store_true", default=False) parser.add_option("--show-pid", help="Prefix line with process identifier", action="store_true", default=False) parser.add_option("--list-syscalls", help="Display system calls and exit", action="store_true", default=False) parser.add_option("-i", "--show-ip", help="print instruction pointer at time of syscall", action="store_true", default=False) self.createLogOptions(parser) self.options, self.program = parser.parse_args() if self.options.list_syscalls: syscalls = list(SYSCALL_NAMES.items()) syscalls.sort(key=lambda data: data[0]) for num, name in syscalls: print("% 3s: %s" % (num, name)) exit(0) if self.options.pid is None and not self.program: parser.print_help() exit(1) # Create "only" filter only = set() if self.options.syscalls: # split by "," and remove spaces for item in self.options.syscalls.split(","): item = item.strip() if not item or item in only: continue ok = True valid_names = list(SYSCALL_NAMES.values()) for name in only: if name not in valid_names: print("ERROR: unknow syscall %r" % name, file=stderr) ok = False if not ok: print(file=stderr) print("Use --list-syscalls options to get system calls list", file=stderr) exit(1) # remove duplicates only.add(item) if self.options.filename: for syscall, format in SYSCALL_PROTOTYPES.items(): restype, arguments = format if any(argname in FILENAME_ARGUMENTS for argtype, argname in arguments): only.add(syscall) if self.options.socket: only |= SOCKET_SYSCALL_NAMES self.only = only if self.options.ignore_regex: try: self.ignore_regex = re.compile(self.options.ignore_regex) except Exception as err: print("Invalid regular expression! %s" % err) print("(regex: %r)" % self.options.ignore_regex) exit(1) else: self.ignore_regex = None if self.options.fork: self.options.show_pid = True self.processOptions() def ignoreSyscall(self, syscall): name = syscall.name if self.only and (name not in self.only): return True if self.ignore_regex and self.ignore_regex.match(name): return True return False def displaySyscall(self, syscall): name = syscall.name text = syscall.format() if syscall.result is not None: text = "%-40s = %s" % (text, syscall.result_text) prefix = [] if self.options.show_pid: prefix.append("[%s]" % syscall.process.pid) if self.options.show_ip: prefix.append("[%s]" % formatAddress(syscall.instr_pointer)) if prefix: text = ''.join(prefix) + ' ' + text error(text) def syscallTrace(self, process): # First query to break at next syscall self.prepareProcess(process) while True: # No more process? Exit if not self.debugger: break # Wait until next syscall enter try: event = self.debugger.waitSyscall() process = event.process except ProcessExit as event: self.processExited(event) continue except ProcessSignal as event: event.display() process.syscall(event.signum) continue except NewProcessEvent as event: self.newProcess(event) continue except ProcessExecution as event: self.processExecution(event) continue # Process syscall enter or exit self.syscall(process) def syscall(self, process): state = process.syscall_state syscall = state.event(self.syscall_options) if syscall and (syscall.result is not None or self.options.enter): self.displaySyscall(syscall) # Break at next syscall process.syscall() def processExited(self, event): # Display syscall which has not exited state = event.process.syscall_state if (state.next_event == "exit") \ and (not self.options.enter) \ and state.syscall: self.displaySyscall(state.syscall) # Display exit message error("*** %s ***" % event) def prepareProcess(self, process): process.syscall() process.syscall_state.ignore_callback = self.ignoreSyscall def newProcess(self, event): process = event.process error("*** New process %s ***" % process.pid) self.prepareProcess(process) process.parent.syscall() def processExecution(self, event): process = event.process error("*** Process %s execution ***" % process.pid) process.syscall() def runDebugger(self): # Create debugger and traced process self.setupDebugger() process = self.createProcess() if not process: return self.syscall_options = FunctionCallOptions( write_types=self.options.type, write_argname=self.options.name, string_max_length=self.options.string_length, replace_socketcall=not self.options.raw_socketcall, write_address=self.options.address, max_array_count=self.options.array_count, ) self.syscall_options.instr_pointer = self.options.show_ip self.syscallTrace(process) def main(self): if self.options.profiler: from ptrace.profiler import runProfiler runProfiler(getLogger(), self._main) else: self._main() if self._output is not None: self._output.close() def _main(self): self.debugger = PtraceDebugger() try: self.runDebugger() except ProcessExit as event: self.processExited(event) except PtraceError as err: error("ptrace() error: %s" % err) except KeyboardInterrupt: error("Interrupted.") except PTRACE_ERRORS as err: writeError(getLogger(), err, "Debugger error") self.debugger.quit() def createChild(self, program): pid = Application.createChild(self, program) error("execve(%s, %s, [/* 40 vars */]) = %s" % ( program[0], program, pid)) return pid
def __init__(self, pid): self.debugger = PtraceDebugger () self.process = self.debugger.addProcess (int (pid), False) self.addrs = list ()
class Gdb(Application): def __init__(self): Application.__init__(self) # Parse self.options self.parseOptions() # Setup output (log) self.setupLog() self.last_signal = {} # We assume user wants all possible information self.syscall_options = FunctionCallOptions( write_types=True, write_argname=True, write_address=True, ) # FIXME: Remove self.breaks! self.breaks = dict() self.followterms = [] def setupLog(self): self._setupLog(stdout) def parseOptions(self): parser = OptionParser(usage="%prog [options] -- program [arg1 arg2 ...]") self.createCommonOptions(parser) self.createLogOptions(parser) self.options, self.program = parser.parse_args() if self.options.pid is None and not self.program: parser.print_help() exit(1) self.processOptions() self.show_pid = self.options.fork def _continueProcess(self, process, signum=None): if not signum and process in self.last_signal: signum = self.last_signal[process] if signum: error("Send %s to %s" % (signalName(signum), process)) process.cont(signum) try: del self.last_signal[process] except KeyError: pass else: process.cont() def cont(self, signum=None): for process in self.debugger: process.syscall_state.clear() if process == self.process: self._continueProcess(process, signum) else: self._continueProcess(process) # Wait for a process signal signal = self.debugger.waitSignals() process = signal.process # Hit breakpoint? if signal.signum == SIGTRAP: ip = self.process.getInstrPointer() if not CPU_POWERPC: # Go before "INT 3" instruction ip -= 1 breakpoint = self.process.findBreakpoint(ip) if breakpoint: error("Stopped at %s" % breakpoint) breakpoint.desinstall(set_ip=True) else: self.processSignal(signal) return None def readRegister(self, regs): name = regs.group(0)[1:] value = self.process.getreg(name) return str(value) def parseInteger(self, text): # Remove spaces and convert to lower case text = text.strip() if " " in text: raise ValueError("Space are forbidden: %r" % text) text = text.lower() # Replace registers by their value orig_text = text text = REGISTER_REGEX.sub(self.readRegister, text) # Replace hexadecimal numbers by decimal numbers def readHexadecimal(regs): text = regs.group(0) if text.startswith("0x"): text = text[2:] elif not re.search("[a-f]", text): return text value = int(text, 16) return str(value) text = re.sub(r"(?:0x)?[0-9a-f]+", readHexadecimal, text) # Reject invalid characters if not re.match(r"^[()<>+*/&0-9-]+$", text): raise ValueError("Invalid expression: %r" % orig_text) # Use integer division (a//b) instead of float division (a/b) text = text.replace("/", "//") # Finally, evaluate the expression is_pointer = text.startswith("*") if is_pointer: text = text[1:] try: value = eval(text) value = truncateWord(value) except SyntaxError: raise ValueError("Invalid expression: %r" % orig_text) if is_pointer: value = self.process.readWord(value) return value def parseIntegers(self, text): values = [] for item in text.split(): item = item.strip() value = self.parseInteger(item) values.append(value) return values def parseBytes(self, text): # FIXME: Validate input # if not BYTES_REGEX.match(text): # raise ValueError('Follow text must be enclosed in quotes!') if PY3: text = 'b' + text.lstrip() value = eval(text) if not isinstance(value, binary_type): raise TypeError("Input is not a bytes string!") return value def addFollowTerm(self, text): # Allow terms of the form 'string', "string", '\x04', "\x01\x14" term = self.parseBytes(text) self.followterms.append(term) def showFollowTerms(self): print(self.followterms) def _xray(self): for term in self.followterms: for process in self.debugger: for procmap in readProcessMappings(process): for address in procmap.search(term): yield (process, procmap, address, term) # displays the offsets of all terms found in the process memory mappings # along with possible addresses of pointers pointing to these terms def xray(self): for process, procmap, address, term in self._xray(): pointers = " ".join(formatAddress(ptr_addr) for ptr_addr in getPointers(process, address)) print("term[%s] pid[%i] %s %s pointers: %s" % ( repr(term), process.pid, procmap, formatAddress(address), pointers)) def execute(self, command): errmsg = None if command == "cont": errmsg = self.cont() elif command == "proc": self.procInfo() elif command == "proclist": self.procList() elif command.startswith("attach "): errmsg = self.attachProcess(command[7:]) elif command == "regs": self.process.dumpRegs() elif command == "stack": self.process.dumpStack() elif command == "backtrace": errmsg = self.backtrace() elif command == "where" or command.startswith("where "): errmsg = self.where(command[6:]) elif command == "where2" or command.startswith("where2 "): errmsg = self.where(command[7:], manage_bp=True) elif command == "maps": self.process.dumpMaps() elif command == "dbginfo": self.debuggerInfo() elif command == "step": errmsg = self.step(False) elif command == "stepi": errmsg = self.step(True) elif command == "sys": errmsg = self.syscallTrace() elif command == "help": self.help() elif command.startswith("set "): errmsg = self.set(command) elif command.startswith("until "): errmsg = self.until(command[6:]) elif command.startswith("switch") or command == "switch": errmsg = self.switch(command[6:]) elif command.startswith("break "): errmsg = self.breakpoint(command[6:]) elif command.startswith("breakpoints"): self.displayBreakpoints() elif command.startswith("signals"): self.displaySignals() elif command.startswith("delete "): errmsg = self.delete(command[7:]) elif command.startswith("hexdump "): errmsg = self.hexdump(command[8:]) elif command.startswith("signal "): errmsg = self.signal(command[7:]) elif command.startswith("print "): errmsg = self.print_(command[6:]) elif command.startswith("follow "): errmsg = self.addFollowTerm(command[7:]) elif command == "showfollow": self.showFollowTerms() elif command == "resetfollow": self.followterms = [] elif command == "xray": self.xray() else: errmsg = "Unknown command: %r" % command if errmsg: print(errmsg, file=stderr) return False return True def parseSignum(self, command): try: return SIGNALS[command] except KeyError: pass try: return SIGNALS["SIG"+command] except KeyError: pass try: return self.parseInteger(command) except ValueError as err: raise ValueError("Invalid signal number: %r" % command) def signal(self, command): try: signum = self.parseSignum(command) except ValueError as err: return str(err) last_process = self.process try: errmsg = self.cont(signum) return errmsg finally: try: del self.last_signal[last_process] except KeyError: pass def print_(self, command): try: value = self.parseInteger(command) except ValueError as err: return str(err) error("Decimal: %s" % value) error("Hexadecimal: %s" % formatWordHex(value)) for map in self.process.readMappings(): if value not in map: continue error("Address is part of mapping: %s" % map) return None def hexdump(self, command): max_line = 20 width = (terminalWidth() - len(formatAddress(1)) - 3) // 4 width = max(width, 1) limited = None parts = command.split(" ", 1) if 1 < len(parts): try: start_address = self.parseInteger(parts[0]) end_address = self.parseInteger(parts[1]) if end_address <= start_address: raise ValueError('End address (%s) is smaller than start address(%s)!' % (formatAddress(end_address), formatAddress(start_address))) except ValueError as err: return str(err) size = end_address - start_address max_size = width*max_line if max_size < size: limited = max_size end_address = start_address + max_size else: try: start_address = self.parseInteger(command) except ValueError as err: return str(err) end_address = start_address + 5*width read_error = None address = start_address while address < end_address: size = min(end_address - address, width) try: # Read bytes memory = self.process.readBytes(address, size) # Format bytes hexa = formatHexa(memory) hexa = hexa.ljust(width*3-1, ' ') ascii = formatAscii(memory) ascii = ascii.ljust(width, ' ') # Display previous read error, if any if read_error: warning("Warning: Unable to read memory %s" % ( formatAddressRange(*read_error))) read_error = None # Display line error("%s| %s| %s" % (formatAddress(address), hexa, ascii)) except PtraceError: if not read_error: read_error = [address, address + size] else: read_error[1] = address + size address += size # Display last read error, if any if read_error: warning("Warning: Unable to read memory %s" % ( formatAddressRange(*read_error))) if limited: warning("(limit to %s bytes)" % max_size) return None def backtrace(self): trace = self.process.getBacktrace() for func in trace: error(func) if trace.truncated: error("--limited to depth %s--" % len(trace)) return None def where(self, command, manage_bp=False): start = None stop = None try: values = self.parseIntegers(command) except ValueError as err: return str(err) if 1 <= len(values): start = values[0] if 2 <= len(values): stop = values[1] self.process.dumpCode(start, stop, manage_bp=manage_bp) return None def procInfo(self): dumpProcessInfo(error, self.process.pid, max_length=160) def procList(self): for process in self.debugger: text = str(process) if self.process == process: text += " (active)" error(text) def set(self, command): try: key, value = command[4:].split("=", 1) key = key.strip().lower() if not key.startswith("$"): return 'Register name (%s) have to start with "$"' % key key = key[1:] except ValueError as err: return "Invalid command: %r" % command try: value = self.parseInteger(value) except ValueError as err: return str(err) try: self.process.setreg(key, value) except ProcessError as err: return "Unable to set $%s=%s: %s" % (key, value, err) error("Set $%s to %s" % (key, value)) return None def displayInstr(self, prefix): try: if HAS_DISASSEMBLER: instr = self.process.disassembleOne() error("%s %s: %s" % ( prefix, formatAddress(instr.address), instr.text)) else: self.process.dumpCode() except PtraceError as err: error("Unable to read current instruction: %s" % err) def attachProcess(self, text): try: pid = self.parseInteger(text) except ValueError as err: return str(err) process = self.debugger.addProcess(pid, False) self.switchProcess(process) def step(self, enter_call, address=None): if address is None: self.displayInstr("Execute") if (not HAS_PTRACE_SINGLESTEP) or (not enter_call): if address is None: address = self.process.getInstrPointer() size = self.readInstrSize(address, default_size=None) if not size: return "Unable to read instruction size at %s" \ % formatAddress(address) address += size size = self.readInstrSize(address) # Set a breakpoint breakpoint = self.process.createBreakpoint(address, size) # Continue the process self.process.cont() else: # Use ptrace single step command self.process.singleStep() breakpoint = None # Execute processus until next TRAP try: self.process.waitSignals(SIGTRAP) if breakpoint: breakpoint.desinstall(set_ip=True) except: if breakpoint: breakpoint.desinstall() raise return None def newProcess(self, event): error("New process: %s" % event.process) # FIXME: This function doesn't work multiple multiple processes # especially when a parent waits for a child def syscallTrace(self): # Trace until syscall enter self.process.syscall() self.process.waitSyscall() # Process the syscall event state = self.process.syscall_state syscall = state.event(self.syscall_options) # Display syscall if syscall: if syscall.result is not None: text = "%s = %s" % (syscall.format(), syscall.result_text) if self.show_pid: text = "Process %s exits %s" % (syscall.process.pid, text) error(text) else: text = syscall.format() if self.show_pid: text = "Process %s enters %s" % (syscall.process.pid, text) error(text) return None def until(self, command): try: address = self.parseInteger(command) except ValueError as err: return str(err) errmsg = self.step(False, address) if errmsg: return errmsg self.displayInstr("Current") return None def switch(self, command): if not command: process_list = self.debugger.list if len(process_list) == 1: return "There is only one process!" index = process_list.index(self.process) index = (index + 1) % len(process_list) process = process_list[index] self.switchProcess(process) return try: pid = self.parseInteger(command) except ValueError as err: return str(err) try: process = self.debugger[pid] self.switchProcess(process) except KeyError: return "There is not process %s" % pid return None def switchProcess(self, process): if self.process == process: return error("Switch to %s" % process) self.process = process def nextProcess(self): try: process = next(iter(self.debugger)) self.switchProcess(process) except StopIteration: pass def displayBreakpoints(self): found = False for process in self.debugger: for bp in process.breakpoints.values(): found = True error("%s:%s" % (process, bp)) if not found: error("(no breakpoint)") def displaySignals(self): signals = list(SIGNAMES.items()) signals.sort(key=lambda key_value: key_value[0]) for signum, name in signals: error("% 2s: %s" % (signum, name)) def readInstrSize(self, address, default_size=None): if not HAS_DISASSEMBLER: return default_size try: # Get address and size of instruction at specified address instr = self.process.disassembleOne(address) return instr.size except PtraceError as err: warning("Warning: Unable to read instruction size at %s: %s" % ( formatAddress(address), err)) return default_size def breakpoint(self, command): try: address = self.parseInteger(command) except ValueError as err: return str(err) # Create breakpoint size = self.readInstrSize(address) try: bp = self.process.createBreakpoint(address, size) except PtraceError as err: return "Unable to set breakpoint at %s: %s" % ( formatAddress(address), err) error("New breakpoint: %s" % bp) return None def delete(self, command): try: address = self.parseInteger(command) except ValueError as err: return str(err) breakpoint = self.process.findBreakpoint(address) if not breakpoint: return "No breakpoint at %s " % formatAddress(address) breakpoint.desinstall() error("%s deleted" % breakpoint) return None def help(self): for command, description in COMMANDS: error("%s: %s" % (command, description)) error('') error("Value can be an hexadecimal/decimal number or a register name ($reg)") error("You can use operators a+b, a-b, a*b, a/b, a<<b, a>>b, a**b, and parenthesis in expressions") error('Use ";" to write multiple commands on the same line (eg. "step; print $eax")') def processSignal(self, event): event.display() self.switchProcess(event.process) self.last_signal[self.process] = event.signum error("%s interrupted by %s" % (self.process, event.name)) def processExecution(self, event): error(event) self.switchProcess(event.process) self.interrupt() def debuggerInfo(self): error("Debugger process ID: %s" % getpid()) error("python-ptrace version %s" % VERSION) error("Website: %s" % WEBSITE) def interrupt(self): waitlist = [] for process in self.debugger: if process.is_stopped: continue try: if process.isTraced(): continue except NotImplementedError: pass warning("Interrupt %s (send SIGINT)" % process) process.kill(SIGINT) waitlist.append(process) for process in waitlist: info("Wait %s interruption" % process) try: process.waitSignals(SIGINT) except ProcessSignal as event: event.display() except KeyboardInterrupt: pass def deleteProcess(self, pid): try: process = self.debugger[pid] except KeyError: return event = process.processTerminated() error(str(event)) if process == self.process: self.nextProcess() def restoreTerminal(self): if enableEchoMode(): error("Terminal: restore echo mode") def mainLoop(self): # Read command try: self.restoreTerminal() command = raw_input(self.invite).strip() except EOFError: print() return True except KeyboardInterrupt: error("User interrupt!") self.interrupt() return False # If command is empty, reuse previous command if not command: if self.previous_command: command = self.previous_command info("Replay previous command: %s" % command) else: return False self.previous_command = None # User wants to quit? if command == "quit": return True # Execute the user command try: command_str = command ok = True for command in command_str.split(";"): command = command.strip() try: ok &= self.execute(command) except Exception as err: print("Command error: %s" % err) raise ok = False if not ok: break if ok: self.previous_command = command_str except KeyboardInterrupt: self.interrupt() except NewProcessEvent as event: self.newProcess(event) except ProcessSignal as event: self.processSignal(event) except ProcessExit as event: error(event) self.nextProcess() except ProcessExecution as event: self.processExecution(event) except PtraceError as err: error("ERROR: %s" % err) if err.errno == ESRCH: self.deleteProcess(err.pid) return False def runDebugger(self): self.setupDebugger() # Create new process try: self.process = self.createProcess() except ChildError as err: writeError(getLogger(), err, "Unable to create child process") return if not self.process: return # Trace syscalls self.invite = '(gdb) ' self.previous_command = None while True: if not self.debugger: # There is no more process: quit return done = self.mainLoop() if done: return def main(self): self.debugger = PtraceDebugger() try: self.runDebugger() except KeyboardInterrupt: error("Interrupt debugger: quit!") except PTRACE_ERRORS as err: writeError(getLogger(), err, "Debugger error") self.process = None self.debugger.quit() error("Quit gdb.") self.restoreTerminal()
import sys import struct from gdb import Gdb from ptrace.debugger import PtraceDebugger, ProcessSignal, ProcessExit pid = sys.argv[1] gdb = Gdb() gdb.debugger = PtraceDebugger() gdb.process = None gdb.attachProcess(pid) print("[!] attached to {0}".format(pid)) #gdb.breakpoint("0x80487e0") gdb.breakpoint("0x080487d6") gdb.breakpoint("0x08048802") while (True): try: gdb.cont() eip = gdb.process.getreg("eip") print("EIP: {0}".format(hex(eip))) #if eip == 0x80487e0: # print("pipe descriptor: {0}".format(hex(gdb.process.getreg("eax")))) # WRITE WHERE if eip == 0x80487d6: eax = gdb.process.getreg("eax") i = gdb.process.readBytes(eax, 4)
class AndroPreDump: def __init__(self, input): self.data = [] self.pid = int(input) self.debugger = PtraceDebugger() self.process = self.debugger.addProcess(self.pid, is_attached=False) atexit.register(self.debugger.quit) Header = False Code = False self.procmaps = readProcessMappings(self.process) for pm in self.procmaps: if pm.permissions.find("w") != -1 and pm.pathname == None: # if Code == False and Header == True : # data = self.process.readBytes(pm.start, pm.end-pm.start) # idx = data.find("SourceFile") # if idx != -1 : # print "CODE", pm # self.data.append( (pm, data, idx) ) # Code = True if Header == False: data = self.process.readBytes(pm.start, pm.end - pm.start) idx = data.find(MAGIC_PATTERN) if idx != -1: print "HEADER", pm self.data.append((pm, data)) Header = True self.dumpMemory("java_dump_memory") # self.dumpFiles( "java_files" ) def write(self, idx, buff): self.process.writeBytes(idx, buff) def getFilesBuffer(self): for i in self.data: d = i[1] x = d.find(MAGIC_PATTERN) idx = x while x != -1: yield i[0].start + idx, d[x:] d = d[x + len(MAGIC_PATTERN):] idx += len(MAGIC_PATTERN) x = d.find(MAGIC_PATTERN) idx += x def dumpMemory(self, base_filename): for i in self.data: fd = open( base_filename + "-" + "0x%x-0x%x" % (i[0].start, i[0].end), "w") fd.write(i[1]) fd.close() def dumpFiles(self, base_filename): for i in self.data: fd = open( base_filename + "-" + "0x%x-0x%x" % (i[0].start + i[2], i[0].end), "w") fd.write(i[1][i[2]:]) fd.close()
class SyscallTracer(Application): def __init__(self): Application.__init__(self) # Parse self.options self.parseOptions() # Setup output (log) self.setupLog() def setupLog(self): if self.options.output: fd = open(self.options.output, 'w') self._output = fd else: fd = stderr self._output = None self._setupLog(fd) def parseOptions(self): parser = OptionParser( usage="%prog [options] -- program [arg1 arg2 ...]") self.createCommonOptions(parser) parser.add_option("--enter", help="Show system call enter and exit", action="store_true", default=False) parser.add_option("--profiler", help="Use profiler", action="store_true", default=False) parser.add_option( "--type", help="Display arguments type and result type (default: no)", action="store_true", default=False) parser.add_option("--name", help="Display argument name (default: no)", action="store_true", default=False) parser.add_option("--string-length", "-s", help="String max length (default: 300)", type="int", default=300) parser.add_option("--array-count", help="Maximum number of array items (default: 20)", type="int", default=20) parser.add_option("--raw-socketcall", help="Raw socketcall form", action="store_true", default=False) parser.add_option("--output", "-o", help="Write output to specified log file", type="str") parser.add_option( "--ignore-regex", help= "Regex used to filter syscall names (e.g. --ignore='^(gettimeofday|futex|f?stat)')", type="str") parser.add_option("--address", help="Display structure address", action="store_true", default=False) parser.add_option( "--syscalls", '-e', help= "Comma separated list of shown system calls (other will be skipped)", type="str", default=None) parser.add_option("--socket", help="Show only socket functions", action="store_true", default=False) parser.add_option("--filename", help="Show only syscall using filename", action="store_true", default=False) parser.add_option("--show-pid", help="Prefix line with process identifier", action="store_true", default=False) parser.add_option("--list-syscalls", help="Display system calls and exit", action="store_true", default=False) parser.add_option("-i", "--show-ip", help="print instruction pointer at time of syscall", action="store_true", default=False) self.createLogOptions(parser) self.options, self.program = parser.parse_args() if self.options.list_syscalls: syscalls = list(SYSCALL_NAMES.items()) syscalls.sort(key=lambda data: data[0]) for num, name in syscalls: print("% 3s: %s" % (num, name)) exit(0) if self.options.pid is None and not self.program: parser.print_help() exit(1) # Create "only" filter only = set() if self.options.syscalls: # split by "," and remove spaces for item in self.options.syscalls.split(","): item = item.strip() if not item or item in only: continue ok = True valid_names = list(SYSCALL_NAMES.values()) for name in only: if name not in valid_names: print("ERROR: unknown syscall %r" % name, file=stderr) ok = False if not ok: print(file=stderr) print( "Use --list-syscalls options to get system calls list", file=stderr) exit(1) # remove duplicates only.add(item) if self.options.filename: for syscall, format in SYSCALL_PROTOTYPES.items(): restype, arguments = format if any(argname in FILENAME_ARGUMENTS for argtype, argname in arguments): only.add(syscall) if self.options.socket: only |= SOCKET_SYSCALL_NAMES self.only = only if self.options.ignore_regex: try: self.ignore_regex = re.compile(self.options.ignore_regex) except Exception as err: print("Invalid regular expression! %s" % err) print("(regex: %r)" % self.options.ignore_regex) exit(1) else: self.ignore_regex = None if self.options.fork: self.options.show_pid = True self.processOptions() def ignoreSyscall(self, syscall): name = syscall.name if self.only and (name not in self.only): return True if self.ignore_regex and self.ignore_regex.match(name): return True return False def displaySyscall(self, syscall): text = syscall.format() if syscall.result is not None: text = "%-40s = %s" % (text, syscall.result_text) prefix = [] if self.options.show_pid: prefix.append("[%s]" % syscall.process.pid) if self.options.show_ip: prefix.append("[%s]" % formatAddress(syscall.instr_pointer)) if prefix: text = ''.join(prefix) + ' ' + text error(text) def syscallTrace(self, process): # First query to break at next syscall self.prepareProcess(process) while True: # No more process? Exit if not self.debugger: break # Wait until next syscall enter try: event = self.debugger.waitSyscall() except ProcessExit as event: self.processExited(event) continue except ProcessSignal as event: event.display() event.process.syscall(event.signum) continue except NewProcessEvent as event: self.newProcess(event) continue except ProcessExecution as event: self.processExecution(event) continue # Process syscall enter or exit self.syscall(event.process) def syscall(self, process): state = process.syscall_state syscall = state.event(self.syscall_options) if syscall and (syscall.result is not None or self.options.enter): self.displaySyscall(syscall) # Break at next syscall process.syscall() def processExited(self, event): # Display syscall which has not exited state = event.process.syscall_state if (state.next_event == "exit") \ and (not self.options.enter) \ and state.syscall: self.displaySyscall(state.syscall) # Display exit message error("*** %s ***" % event) def prepareProcess(self, process): process.syscall() process.syscall_state.ignore_callback = self.ignoreSyscall def newProcess(self, event): process = event.process error("*** New process %s ***" % process.pid) self.prepareProcess(process) process.parent.syscall() def processExecution(self, event): process = event.process error("*** Process %s execution ***" % process.pid) process.syscall() def runDebugger(self): # Create debugger and traced process self.setupDebugger() process = self.createProcess() if not process: return self.syscall_options = FunctionCallOptions( write_types=self.options.type, write_argname=self.options.name, string_max_length=self.options.string_length, replace_socketcall=not self.options.raw_socketcall, write_address=self.options.address, max_array_count=self.options.array_count, ) self.syscall_options.instr_pointer = self.options.show_ip self.syscallTrace(process) def main(self): if self.options.profiler: from ptrace.profiler import runProfiler runProfiler(getLogger(), self._main) else: self._main() if self._output is not None: self._output.close() def _main(self): self.debugger = PtraceDebugger() try: self.runDebugger() except ProcessExit as event: self.processExited(event) except PtraceError as err: error("ptrace() error: %s" % err) except KeyboardInterrupt: error("Interrupted.") except PTRACE_ERRORS as err: writeError(getLogger(), err, "Debugger error") self.debugger.quit() def createChild(self, program): pid = Application.createChild(self, program) error("execve(%s, %s, [/* 40 vars */]) = %s" % (program[0], program, pid)) return pid
class PythonPtraceBackend(Backend): def __init__(self): self.debugger = PtraceDebugger() self.root = None self.stop_requested = False self.syscalls = {} self.backtracer = NullBacktracer() self.debugger.traceClone() self.debugger.traceExec() self.debugger.traceFork() def attach_process(self, pid): self.root = self.debugger.addProcess(pid, is_attached=False) def create_process(self, arguments): pid = createChild(arguments, no_stdout=False) self.root = self.debugger.addProcess(pid, is_attached=True) return self.root def get_argument(self, pid, num): return self.syscalls[pid].arguments[num].value def get_syscall_result(self, pid): if self.syscalls[pid]: return self.syscalls[pid].result return None def get_arguments_str(self, pid): self.syscalls[pid].format() return ", ".join([ "{}={}".format(i.name, i.text) for i in self.syscalls[pid].arguments ]) def read_cstring(self, pid, address): try: return self.debugger[pid].readCString(address, 255)[0].decode('utf-8') except PtraceError as e: # TODO: ptrace's PREFORMAT_ARGUMENTS, why they are lost? for arg in self.syscalls[pid].arguments: if arg.value == address: return arg.text raise e def read_bytes(self, pid, address, size): return self.debugger[pid].readBytes(address, size) def write_bytes(self, pid, address, data): return self.debugger[pid].writeBytes(address, data) def create_backtrace(self, pid): return self.backtracer.create_backtrace(self.debugger[pid]) def start(self): # First query to break at next syscall self.root.syscall() while not self.stop_requested and self.debugger: try: try: # FIXME: better mechanism to stop debugger # debugger._waitPid(..., blocking=False) may help # actually debugger receives SIGTERM, terminates all remaining process # then this method is unblocked and fails with KeyError event = self.debugger.waitSyscall() except: if self.stop_requested: return raise state = event.process.syscall_state syscall = state.event(FunctionCallOptions()) self.syscalls[event.process.pid] = syscall yield SyscallEvent(event.process.pid, syscall.name) # FIXME: # when exit_group is called, it seems that thread is still monitored # in next event we area accessing pid that is not running anymore # so when exit_group occurs, remove all processes with same Tgid and detach from them if syscall.name == 'exit_group': # result is None, never return! def read_group(pid): with open('/proc/%d/status' % pid) as f: return int({ i: j.strip() for i, j in [ i.split(':', 1) for i in f.read().splitlines() ] }['Tgid']) me = read_group(syscall.process.pid) for process in self.debugger: if read_group(process.pid) == me: process.detach() self.debugger.deleteProcess(pid=process.pid) yield ProcessExited(process.pid, syscall.arguments[0].value) else: event.process.syscall() except ProcessExit as event: # Display syscall which has not exited state = event.process.syscall_state if (state.next_event == "exit") and state.syscall: # self.syscall(state.process) TODO: pass yield ProcessExited(event.process.pid, event.exitcode) except ProcessSignal as event: # event.display() event.process.syscall(event.signum) except NewProcessEvent as event: process = event.process logging.info("*** New process %s ***", process.pid) yield ProcessCreated(pid=process.pid, parent_pid=process.parent.pid, is_thread=process.is_thread) process.syscall() process.parent.syscall() except ProcessExecution as event: logging.info("*** Process %s execution ***", event.process.pid) event.process.syscall() except DebuggerError: if self.stop_requested: return raise except PtraceError as e: if e.errno == 3: # FIXME: same problem as exit_group above ? logging.warning("Process dead?") else: raise def quit(self): self.stop_requested = True self.debugger.quit()
class SyscallTracer(Application): def __init__(self, options, program, ignore_syscall_callback, syscall_callback, event_callback, quit_callback): Application.__init__(self) # Parse self.options self.options = options self.program=program self.processOptions() self.ignore_syscall_callback = ignore_syscall_callback self.syscall_callback = syscall_callback self.event_callback = event_callback self.quit_callback = quit_callback def runDebugger(self): # Create debugger and traced process self.setupDebugger() process = self.createProcess() if not process: return self.syscall_options = FunctionCallOptions( write_types=True, write_argname=True, replace_socketcall=False, string_max_length=300, write_address=False, max_array_count=20, ) self.syscall_options.instr_pointer = self.options.show_ip self.syscallTrace(process) def displaySyscall(self, syscall): self.syscall_callback(syscall) def syscall(self, process): state = process.syscall_state syscall = state.event(self.syscall_options) if syscall and (syscall.result is not None or self.options.enter): self.displaySyscall(syscall) # Break at next syscall process.syscall() def syscallTrace(self, process): # First query to break at next syscall self.prepareProcess(process) while True: # No more process? Exit if not self.debugger: break # Wait until next syscall enter try: event = self.debugger.waitSyscall() process = event.process except ProcessExit as event: self.processExited(event) continue except ProcessSignal as event: self.event_callback(event) #event.display() process.syscall(event.signum) continue except NewProcessEvent as event: self.event_callback(event) process = event.process self.prepareProcess(process) process.parent.syscall() continue except ProcessExecution as event: self.event_callback(event) process = event.process process.syscall() continue # Process syscall enter or exit self.syscall(process) def prepareProcess(self, process): process.syscall() process.syscall_state.ignore_callback = self.ignore_syscall_callback def processExited(self, event): # Display syscall which has not exited state = event.process.syscall_state if (state.next_event == "exit") and (not self.options.enter) and state.syscall: self.displaySyscall(state.syscall) self.event_callback(event) def main(self): self.debugger = PtraceDebugger() try: self.runDebugger() except ChildError as event: self.event_callback(event) except ProcessExit as event: self.processExited(event) except (KeyError, PtraceError, OSError) as error: self._handle_exceptions_during_quit(error, 'main') if self.debugger: self.debugger.quit() self.quit_callback() def quit(self): try: self.debugger.quit() except (KeyError, PtraceError, OSError) as error: self._handle_exceptions_during_quit(error, 'quit') self.quit_callback() self.debugger = None def _handle_exceptions_during_quit(self, exception, context): if isinstance(exception, KeyError): # When the debugger is waiting for a syscall and the debugger process # is closed with quit() a KeyError Exception for missing PID is fired pass elif isinstance(exception, PtraceError): print "PtraceError from %s" % context, exception elif isinstance(exception, OSError): print 'OSError from %s' % context, exception else: print 'Unexpected exception from %s' % context
class SyscallTracer(Application): def __init__(self): Application.__init__(self) # Parse self.options self.parseOptions() # Setup output (log) self.setupLog() def setupLog(self): if self.options.output: fd = open(self.options.output, 'w') self._output = fd else: fd = stderr self._output = None self._setupLog(fd) def parseOptions(self): parser = OptionParser(usage="%prog pid") parser.add_option( "--no-fork", "-n", help="Don't trace forks", action="store_false", dest="fork", default=True) if len(sys.argv) == 1: parser.print_help() exit(1) self.options, self.program = parser.parse_args() defaults = ( ('pid', int(sys.argv[1])), ('trace_exec', False), ('enter', False), ('profiler', False), ('type', False), ('name', False), ('string_length', 99999999), ('array_count', 20), ('raw_socketcall', False), ('output', ''), ('ignore_regex', ''), ('address', False), ('syscalls', None), ('socket', False), ('filename', False), ('show_pid', False), ('list_syscalls', False), ('show_ip', False), ('debug', False), ('verbose', False), ('quiet', False), ) for option, value in defaults: setattr(self.options, option, value) self.only = set() self.ignore_regex = None if self.options.fork: self.options.show_pid = True self.processOptions() def ignoreSyscall(self, syscall): name = syscall.name if self.only and (name not in self.only): return True if self.ignore_regex and self.ignore_regex.match(name): return True return False def displaySyscall(self, syscall): if syscall.name == 'write': fd = syscall.arguments[0].value if fd < 3: text = syscall.arguments[1].getText()[1:-1] stdout.write(text.decode('string_escape')) stdout.flush() def syscallTrace(self, process): # First query to break at next syscall self.prepareProcess(process) while True: # No more process? Exit if not self.debugger: break # Wait until next syscall enter try: event = self.debugger.waitSyscall() process = event.process except ProcessExit as event: self.processExited(event) continue except ProcessSignal as event: # event.display() process.syscall(event.signum) continue except NewProcessEvent as event: self.newProcess(event) continue except ProcessExecution as event: self.processExecution(event) continue # Process syscall enter or exit self.syscall(process) def syscall(self, process): state = process.syscall_state syscall = state.event(self.syscall_options) if syscall and (syscall.result is not None or self.options.enter): self.displaySyscall(syscall) # Break at next syscall process.syscall() def processExited(self, event): # Display syscall which has not exited state = event.process.syscall_state if (state.next_event == "exit" and (not self.options.enter) and state.syscall): self.displaySyscall(state.syscall) def prepareProcess(self, process): process.syscall() process.syscall_state.ignore_callback = self.ignoreSyscall def newProcess(self, event): process = event.process self.prepareProcess(process) process.parent.syscall() def processExecution(self, event): process = event.process process.syscall() def runDebugger(self): # Create debugger and traced process self.setupDebugger() process = self.createProcess() if not process: return self.syscall_options = FunctionCallOptions( write_types=self.options.type, write_argname=self.options.name, string_max_length=self.options.string_length, replace_socketcall=not self.options.raw_socketcall, write_address=self.options.address, max_array_count=self.options.array_count, ) self.syscall_options.instr_pointer = self.options.show_ip self.syscallTrace(process) def main(self): self._main() if self._output is not None: self._output.close() def _main(self): self.debugger = PtraceDebugger() try: self.runDebugger() except ProcessExit as event: self.processExited(event) except PtraceError as err: error("ptrace() error: %s" % err) except KeyboardInterrupt: error("Interrupted.") except PTRACE_ERRORS as err: writeError(getLogger(), err, "Debugger error") processes = list(self.debugger.list) self.debugger.quit() # python-ptrace seems iffy on actually detaching sometimes, so we make # sure we let the process continue for process in processes: subprocess.check_output(['kill', '-cont', str(process.pid)]) def createChild(self, program): pid = Application.createChild(self, program) error("execve(%s, %s, [/* 40 vars */]) = %s" % ( program[0], program, pid)) return pid
class TracingThread(Thread): """ tracing thread that can be cancelled """ def __init__(self, manager, pid, record, old_debugger=None, is_thread=False): """ Create a new tracing thread :param manager: overall tracing management instance :param pid: Process to trace :param record: Action recording instance :param old_debugger: Old debugger the process might be attached to at the moment :param is_thread: True if the pid is a thread else False """ super().__init__() self.manager = manager # Save tracing record self.record = record self.record.runtime_log(RuntimeActionRecord.TYPE_STARTED) self._debugger_options = FunctionCallOptions(replace_socketcall=False) self.is_thread = is_thread self.process = psutil.Process(pid) self.debugger = None self.traced_item = None self.stopped = False # Check if the process is attached to the debugger if old_debugger is not None and old_debugger.dict.get( self.process.pid): old_debugger.dict.get(self.process.pid).detach() posix.kill(self.process.pid, signal.SIGSTOP) def stop(self): """ Mark this thread to stop tracing :return: None """ self.stopped = True def run(self): """ Trace the given process or thread for one syscall """ finished = False self.record.log("Attaching debugger") try: self.debugger = PtraceDebugger() self.debugger.traceFork() self.debugger.traceExec() self.debugger.traceClone() self.traced_item = self.debugger.addProcess( self.process.pid, False, is_thread=self.is_thread) self.record.log("PTrace debugger attached successfully") TracingThread._trace_continue(self.traced_item) except Exception as e: self.record.log( "PTrace debugger attachment failed with reason: {}".format(e)) finished = True # Trace process until finished while not finished and not self.stopped: finished = self._trace(self.traced_item, self.record) if self.traced_item: self.traced_item.detach() # Keep in mind that the process maybe already gone try: if self.process.status() == psutil.STATUS_STOPPED: posix.kill(self.process.pid, signal.SIGCONT) except psutil.NoSuchProcess: pass self.record.log("Tracee appears to have ended and thread will finish") def _trace(self, traced_item, record): """ Trace the given process or thread for one syscall :param traced_item: Process or thread to trace :param record: Action recording object :return: True if the item was terminated else False """ finished = False continue_tracing = True signum = 0 try: # Wait for the next syscall event = traced_item.debugger.waitSyscall(traced_item) if not event: return False # Fetch the syscall state if self.manager.is_syscall_tracing_enabled( ) or self.manager.is_file_access_tracing_enabled(): state = traced_item.syscall_state syscall = state.event(self._debugger_options) # Trace the syscall in the process record if it is not filtered out if self.manager.should_record_syscall(syscall): record.syscall_log(syscall) # Trace file access if the syscall is a file access syscall and not filtered out if self.manager.should_record_file_access(syscall): record.file_access_log(syscall) except NewProcessEvent as event: # Trace new process and continue parent self._trace_new_item(event, record) continue_tracing = False except ProcessExecution: record.runtime_log(RuntimeActionRecord.TYPE_EXEC) except ProcessExit as event: # Finish process tracing TracingThread._trace_finish(event, record) finished = True continue_tracing = False except ProcessSignal as event: record.runtime_log(RuntimeActionRecord.TYPE_SIGNAL_RECEIVED, signal=event.signum) signum = event.signum # Continue process execution if continue_tracing: TracingThread._trace_continue(traced_item, signum) return finished @staticmethod def _trace_continue(traced_item, signum=0): """ Move the process/thread to the next execution step / syscall :param traced_item: Process or thread to proceed :param signum: Signal to emit to the traced item :return: None """ traced_item.syscall(signum) @staticmethod def _trace_finish(event, record): """ Handle process or thread completion Determine exit code and termination signal and return them :param event: ProcessExit event that was raised :param record: Action recording object :return: None """ process = event.process record.runtime_log(RuntimeActionRecord.TYPE_EXITED, signal=event.signum, exit_code=event.exitcode) # Detach from tracing process.detach() def _trace_new_item(self, event, record): """ Setup tracing the given new process or thread :param event: new process/thread event :param record: Action recording object :return: None """ parent = event.process.parent process = event.process record.runtime_log(RuntimeActionRecord.TYPE_SPAWN_CHILD, child_pid=process.pid) TracingThread._trace_continue(parent) # Start new tracing thread for the new process if not self.stopped: self.manager.trace_create_thread(process.pid, process.debugger, process.is_thread) else: TracingThread._trace_continue(process)
class SyscallTracer(Application): def __init__(self, options, program, ignore_syscall_callback, syscall_callback, event_callback, quit_callback): Application.__init__(self) # Parse self.options self.options = options self.program = program self.processOptions() self.ignore_syscall_callback = ignore_syscall_callback self.syscall_callback = syscall_callback self.event_callback = event_callback self.quit_callback = quit_callback def runDebugger(self): # Create debugger and traced process self.setupDebugger() process = self.createProcess() if not process: return self.syscall_options = FunctionCallOptions( write_types=True, write_argname=True, replace_socketcall=False, string_max_length=300, write_address=False, max_array_count=20, ) self.syscall_options.instr_pointer = self.options.show_ip self.syscallTrace(process) def displaySyscall(self, syscall): self.syscall_callback(syscall) def syscall(self, process): state = process.syscall_state syscall = state.event(self.syscall_options) if syscall and (syscall.result is not None or self.options.enter): self.displaySyscall(syscall) # Break at next syscall process.syscall() def syscallTrace(self, process): # First query to break at next syscall self.prepareProcess(process) while True: # No more process? Exit if not self.debugger: break # Wait until next syscall enter try: event = self.debugger.waitSyscall() process = event.process except ProcessExit as event: self.processExited(event) continue except ProcessSignal as event: self.event_callback(event) #event.display() process.syscall(event.signum) continue except NewProcessEvent as event: self.event_callback(event) process = event.process self.prepareProcess(process) process.parent.syscall() continue except ProcessExecution as event: self.event_callback(event) process = event.process process.syscall() continue # Process syscall enter or exit self.syscall(process) def prepareProcess(self, process): process.syscall() process.syscall_state.ignore_callback = self.ignore_syscall_callback def processExited(self, event): # Display syscall which has not exited state = event.process.syscall_state if (state.next_event == "exit") and (not self.options.enter) and state.syscall: self.displaySyscall(state.syscall) self.event_callback(event) def main(self): self.debugger = PtraceDebugger() try: self.runDebugger() except ChildError as event: self.event_callback(event) except ProcessExit as event: self.processExited(event) except (KeyError, PtraceError, OSError) as error: self._handle_exceptions_during_quit(error, 'main') if self.debugger: self.debugger.quit() self.quit_callback() def quit(self): try: self.debugger.quit() except (KeyError, PtraceError, OSError) as error: self._handle_exceptions_during_quit(error, 'quit') self.quit_callback() self.debugger = None def _handle_exceptions_during_quit(self, exception, context): if isinstance(exception, KeyError): # When the debugger is waiting for a syscall and the debugger # process is closed with quit() a KeyError Exception for missing # PID is fired pass elif isinstance(exception, PtraceError): print "PtraceError from %s" % context, exception elif isinstance(exception, OSError): print 'OSError from %s' % context, exception else: print 'Unexpected exception from %s' % context
class SSHKeyExtractor(object): def __init__(self, pid): self.dbg = PtraceDebugger() self.pid = pid self.proc = psutil.Process(pid) self.network_connections = self.proc.connections() self.dbg_proc = None self.heap_map_info = None self.mem_maps = None def __repr__(self): return "<SSHKeyExtractor: {}>".format(self.pid) def _get_mem_maps(self): mem_map = open("/proc/{}/maps".format(self.pid)).read() for line in mem_map.splitlines(): regex = r"(\w+)-(\w+)\ ([\w\-]+) (\w+) ([\w\:]+) (\w+) +(.*)" match = re.search(regex, line) yield MemoryRegion(match.groups()) def _get_heap_map_info(self): #print repr(self.mem_maps) for mem_map in self.mem_maps: if mem_map.path == "[heap]": return mem_map return None def is_valid_ptr(self, ptr, allow_nullptr=True, heap_only=True): if (ptr == 0 or ptr is None): if allow_nullptr: return True else: return False if heap_only: return ptr >= self.heap_map_info.start and ptr < self.heap_map_info.end for mem_map in self.mem_maps: valid = ptr >= mem_map.start and ptr < mem_map.end if valid: return True return False def lookup_enc(self, name): return OPENSSH_ENC_ALGS_LOOKUP.get(name, None) def read_string(self, ptr, length): val = self.dbg_proc.readCString(ptr, length) if val: return val[0].decode("utf-8", errors="ignore") return None def probe_sshenc_block(self, ptr, sshenc_size): """ char *name ; 0x808e8a4 -> "*****@*****.**" Cipher *cipher; 0x808e6d0 Cipher{name=0x80989e4 -> "*****@*****.**"} int enabled; 0 u_int key_len; 8-64 *u_int iv_len; 12 u_int block_size; 8-16 u_char *key; 0x80989e4 -> "6e4a242303346ecd60209e41b03c438b" u_char *iv; 0x8088f4e -> "7e59454fbe2247d52d29bd373c3f53ae" """ mem = self.dbg_proc.readBytes(ptr, sshenc_size) enc = sshenc_62p1.from_buffer_copy(mem) sshenc_name = self.is_valid_ptr(enc.name, allow_nullptr=False) sshenc_cipher = self.is_valid_ptr(enc.cipher, allow_nullptr=False, heap_only=False) if not (sshenc_name and sshenc_cipher): return None name_str = self.read_string(enc.name, 64) enc_properties = self.lookup_enc(name_str) #print(repr(name_str), enc_properties) if not enc_properties: return None expected_key_len = enc_properties[2] key_len_valid = expected_key_len == enc.key_len if not key_len_valid: return None cipher = self.dbg_proc.readStruct(enc.cipher, sshcipher) cipher_name_valid = self.is_valid_ptr(cipher.name, allow_nullptr=False, heap_only=False) if not cipher_name_valid: return None cipher_name = self.read_string(cipher.name, 64) if cipher_name != name_str: return None #print(cipher_name) #At this point we know pretty certain this is the sshenc struct. Let's figure out which version... expected_block_size = enc_properties[1] block_size_valid = expected_block_size == enc.block_size if not block_size_valid: enc = sshenc_61p1.from_buffer_copy(mem) block_size_valid = expected_block_size == enc.block_size if not block_size_valid: # !@#$ we can't seem to properly align the structure return None sshenc_key = self.is_valid_ptr(enc.key, allow_nullptr=False) sshenc_iv = self.is_valid_ptr(enc.iv, allow_nullptr=False) if sshenc_iv and sshenc_key: return enc return None def construct_scraped_key(self, ptr, enc): key = ScrapedKey(self.pid, self.proc.name(), enc, ptr) key.network_connections = self.network_connections key.cipher_name = self.read_string(enc.name, 64) key_raw = self.dbg_proc.readBytes(enc.key, enc.key_len) key.key = key_raw.hex() if isinstance(enc, sshenc_61p1): iv_len = enc.block_size else: iv_len = enc.iv_len iv_raw = self.dbg_proc.readBytes(enc.iv, iv_len) key.iv = iv_raw.hex() return key def align_size(self, size, multiple): add = multiple - (size % multiple) return size + add def extract(self, known_addr=None): known_addr = known_addr or [] ret = [] self.dbg_proc = self.dbg.addProcess(self.pid, False) self.dbg_proc.cont() self.mem_maps = list(self._get_mem_maps()) self.heap_map_info = self._get_heap_map_info() ptr = self.heap_map_info.start sshenc_size = max(sizeof(sshenc_61p1), sizeof(sshenc_62p1)) while ptr + sshenc_size < self.heap_map_info.end: if ptr in known_addr: sshenc_aligned_size = self.align_size(sshenc_size, 4) ptr += sshenc_aligned_size #print 'skip 0x{:x}, {}'.format(ptr, sshenc_aligned_size) continue sshenc = self.probe_sshenc_block(ptr, sshenc_size) if sshenc: key = self.construct_scraped_key(ptr, sshenc) ret.append(key) ptr += 4 return ret def cleanup(self): if self.dbg_proc: from signal import SIGTRAP, SIGSTOP, SIGKILL if self.dbg_proc.read_mem_file: self.dbg_proc.read_mem_file.close() self.dbg_proc.kill(SIGSTOP) self.dbg_proc.waitSignals(SIGTRAP, SIGSTOP) self.dbg_proc.detach() if self.dbg: self.dbg.deleteProcess(self.dbg_proc) self.dbg.quit() del self.dbg_proc del self.dbg self.proc = None