Ejemplo n.º 1
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
Ejemplo n.º 2
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))
Ejemplo n.º 3
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]:] )
Ejemplo n.º 4
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
Ejemplo n.º 5
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
Ejemplo n.º 6
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()
Ejemplo n.º 7
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()
Ejemplo n.º 8
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)
Ejemplo n.º 9
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)
Ejemplo n.º 10
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
Ejemplo n.º 11
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()
Ejemplo n.º 12
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()