Exemplo n.º 1
0
    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")
Exemplo n.º 2
0
 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
Exemplo n.º 3
0
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
Exemplo n.º 4
0
    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()
Exemplo n.º 5
0
 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()
Exemplo n.º 7
0
 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()
Exemplo n.º 8
0
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
Exemplo n.º 9
0
    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)
Exemplo n.º 10
0
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))
Exemplo n.º 11
0
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]:] )
Exemplo n.º 12
0
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()
Exemplo n.º 13
0
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)
Exemplo n.º 14
0
    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" )
Exemplo n.º 15
0
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
Exemplo n.º 16
0
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:
Exemplo n.º 17
0
    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)])
Exemplo n.º 18
0
 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
Exemplo n.º 19
0
 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()
Exemplo n.º 20
0
    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
Exemplo n.º 21
0
 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()
Exemplo n.º 22
0
 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()
Exemplo n.º 23
0
    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")
Exemplo n.º 24
0
    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)])
Exemplo n.º 25
0
    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
Exemplo n.º 26
0
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()
Exemplo n.º 27
0
    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)
Exemplo n.º 28
0
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
Exemplo n.º 29
0
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()
Exemplo n.º 30
0
 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)
Exemplo n.º 31
0
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
Exemplo n.º 32
0
 def __init__(self, pid):
     self.debugger = PtraceDebugger ()
     self.process = self.debugger.addProcess (int (pid), False)
     self.addrs = list ()
Exemplo n.º 33
0
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()
Exemplo n.º 34
0
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)
Exemplo n.º 35
0
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
Exemplo n.º 37
0
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()
Exemplo n.º 38
0
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
Exemplo n.º 39
0
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
Exemplo n.º 40
0
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)
Exemplo n.º 41
0
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
Exemplo n.º 42
0
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