def start_trace(cmd): new_pid = createChild(cmd, no_stdout=False) debugger = PtraceDebugger() debugger.traceFork() debugger.traceExec() debugger.addProcess(new_pid, is_attached=True) return debugger
class ProcessDebugger (object): def __init__(self, pid): self.debugger = PtraceDebugger () self.process = self.debugger.addProcess (int (pid), False) self.addrs = list () # TODO: Search's address in process memory by value. def search_addr_by_value (self, value): for i in self.process.readMappings(): # F**k, this is invalid bin :( if i.pathname in ['[vsyscall]', '[vdso]']: continue try: for j in i.search (value): self.addrs.append (j) except: pass return self.addrs # TODO: Convert's boolean value to binary. def search_boolean_value (self, value): return self.search_addr_by_value (struct.pack (data.BIT_ENDOF + '?', value)) # TODO: Convert's integer value to binary. def search_integer_value (self, value): return self.search_addr_by_value(struct.pack(data.BIT_ENDOF + 'i', value))
class AndroPreDump(object): def __init__(self, input) : self.data = [] self.pid = int(input) self.debugger = PtraceDebugger() self.process = self.debugger.addProcess(self.pid, is_attached=False) atexit.register(self.debugger.quit) Header = False Code = False self.procmaps = readProcessMappings(self.process) for pm in self.procmaps: if pm.permissions.find("w") != -1 and pm.pathname == None : # if Code == False and Header == True : # data = self.process.readBytes(pm.start, pm.end-pm.start) # idx = data.find("SourceFile") # if idx != -1 : # print "CODE", pm # self.data.append( (pm, data, idx) ) # Code = True if Header == False : data = self.process.readBytes(pm.start, pm.end-pm.start) idx = data.find(MAGIC_PATTERN) if idx != -1 : print "HEADER", pm self.data.append( (pm, data) ) Header = True self.dumpMemory( "java_dump_memory" ) # self.dumpFiles( "java_files" ) def write(self, idx, buff) : self.process.writeBytes( idx, buff ) def getFilesBuffer(self) : for i in self.data : d = i[1] x = d.find(MAGIC_PATTERN) idx = x while x != -1 : yield i[0].start + idx, d[x:] d = d[x+len(MAGIC_PATTERN):] idx += len(MAGIC_PATTERN) x = d.find(MAGIC_PATTERN) idx += x def dumpMemory(self, base_filename) : for i in self.data : with open(base_filename + "-" + "0x%x-0x%x" % (i[0].start, i[0].end), "w") as fd: fd.write( i[1] ) def dumpFiles(self, base_filename) : for i in self.data : with fd = open(base_filename + "-" + "0x%x-0x%x" % (i[0].start + i[2], i[0].end), "w") as fd: fd.write( i[1][i[2]:] )
def SetupTarget(): ### Let step up our debugger, fork our target ### and attach the debugger to the target dbg = PtraceDebugger() print("Forking the target...") pid = createChild(target.targetArgs, no_stdout=True) print("Attaching to the target process %d" % pid) process = dbg.addProcess(pid, True) return process
def do_dump (process, maxmem): print_process (process, "===") # Attach to the process debugger = PtraceDebugger() try: d_process = debugger.addProcess(process['pid'], False) except: print("Error attaching to the process pid {0}. Aborting".format(process['pid'])) sys.exit(1) d_process.was_attached = True procmaps = readProcessMappings(d_process) # Look for process heap region for pm in procmaps: if pm.pathname == None: mem_start=pm.start mem_end=pm.end mem_total=mem_end-mem_start if maxmem > 0 and mem_total > maxmem : print("Process memory is {0} but you defined maxmem to {1}".format(mem_total, maxmem)) return False # Use StringIO to work only in memory. This can be dangerous, because "heap" can be big. the_mem = StringIO.StringIO() # Transfer process heap memory to the_mem var. the_mem.write(d_process.readBytes(mem_start, mem_total)) # We have what we were looking for. Let's detach the process. d_process.detach() # Start search request_end=0 found=False while True: hdr_pos=fnd(the_mem, process['request'], request_end) if hdr_pos==-1: # EOF break request_pos=hdr_pos # This is the start of the possible match request_end=fnd(the_mem, "\x00", request_pos) # This is the end of the possible match # If we find double new line then this should be a valid request and data block. if fnd(the_mem,"\x0d\x0a\x0d\x0a", request_pos) < request_end: found=True # If valid, print it! print get_fragment(the_mem, request_pos, request_end) # Prepare to continue searching the_mem.seek(request_end+1) the_mem.close() if found: break
def main(): global options options = get_options() dbg = PtraceDebugger() pid = int(subprocess.check_output(['pidof', "typespeed"])) process = dbg.addProcess(pid, False) process.syscall() while True: event = dbg.waitSyscall() process = event.process op_syscall(process) dbg.quit()
def main(): global options options = get_options() dbg = PtraceDebugger() pid = int(subprocess.check_output(['pidof', "typespeed"])) process = dbg.addProcess(pid, False) process.syscall() while True: event = dbg.waitSyscall() process = event.process op_syscall(process) dbg.quit()
def debugFindRtld(pid): dbg = PtraceDebugger() process = dbg.addProcess(pid, False) for pm in readProcessMappings(process): if 'rwx' in pm.permissions: for addr in pm.search(b'\x78\x56\x34\x12'): # There are no ret instructions in the CC memory = process.readBytes(addr, 20) if b'\xC3' not in memory: process = None dbg.quit() return addr print('ERROR: Could not find the address of rtld_lock_default_lock_recursive in code cache!') exit(-1)
class TracingThread(Thread): """ tracing thread that can be cancelled """ def __init__(self, manager, pid, record, old_debugger=None, is_thread=False): """ Create a new tracing thread :param manager: overall tracing management instance :param pid: Process to trace :param record: Action recording instance :param old_debugger: Old debugger the process might be attached to at the moment :param is_thread: True if the pid is a thread else False """ super().__init__() self.manager = manager # Save tracing record self.record = record self.record.runtime_log(RuntimeActionRecord.TYPE_STARTED) self._debugger_options = FunctionCallOptions(replace_socketcall=False) self.is_thread = is_thread self.process = psutil.Process(pid) self.debugger = None self.traced_item = None self.stopped = False # Check if the process is attached to the debugger if old_debugger is not None and old_debugger.dict.get( self.process.pid): old_debugger.dict.get(self.process.pid).detach() posix.kill(self.process.pid, signal.SIGSTOP) def stop(self): """ Mark this thread to stop tracing :return: None """ self.stopped = True def run(self): """ Trace the given process or thread for one syscall """ finished = False self.record.log("Attaching debugger") try: self.debugger = PtraceDebugger() self.debugger.traceFork() self.debugger.traceExec() self.debugger.traceClone() self.traced_item = self.debugger.addProcess( self.process.pid, False, is_thread=self.is_thread) self.record.log("PTrace debugger attached successfully") TracingThread._trace_continue(self.traced_item) except Exception as e: self.record.log( "PTrace debugger attachment failed with reason: {}".format(e)) finished = True # Trace process until finished while not finished and not self.stopped: finished = self._trace(self.traced_item, self.record) if self.traced_item: self.traced_item.detach() # Keep in mind that the process maybe already gone try: if self.process.status() == psutil.STATUS_STOPPED: posix.kill(self.process.pid, signal.SIGCONT) except psutil.NoSuchProcess: pass self.record.log("Tracee appears to have ended and thread will finish") def _trace(self, traced_item, record): """ Trace the given process or thread for one syscall :param traced_item: Process or thread to trace :param record: Action recording object :return: True if the item was terminated else False """ finished = False continue_tracing = True signum = 0 try: # Wait for the next syscall event = traced_item.debugger.waitSyscall(traced_item) if not event: return False # Fetch the syscall state if self.manager.is_syscall_tracing_enabled( ) or self.manager.is_file_access_tracing_enabled(): state = traced_item.syscall_state syscall = state.event(self._debugger_options) # Trace the syscall in the process record if it is not filtered out if self.manager.should_record_syscall(syscall): record.syscall_log(syscall) # Trace file access if the syscall is a file access syscall and not filtered out if self.manager.should_record_file_access(syscall): record.file_access_log(syscall) except NewProcessEvent as event: # Trace new process and continue parent self._trace_new_item(event, record) continue_tracing = False except ProcessExecution: record.runtime_log(RuntimeActionRecord.TYPE_EXEC) except ProcessExit as event: # Finish process tracing TracingThread._trace_finish(event, record) finished = True continue_tracing = False except ProcessSignal as event: record.runtime_log(RuntimeActionRecord.TYPE_SIGNAL_RECEIVED, signal=event.signum) signum = event.signum # Continue process execution if continue_tracing: TracingThread._trace_continue(traced_item, signum) return finished @staticmethod def _trace_continue(traced_item, signum=0): """ Move the process/thread to the next execution step / syscall :param traced_item: Process or thread to proceed :param signum: Signal to emit to the traced item :return: None """ traced_item.syscall(signum) @staticmethod def _trace_finish(event, record): """ Handle process or thread completion Determine exit code and termination signal and return them :param event: ProcessExit event that was raised :param record: Action recording object :return: None """ process = event.process record.runtime_log(RuntimeActionRecord.TYPE_EXITED, signal=event.signum, exit_code=event.exitcode) # Detach from tracing process.detach() def _trace_new_item(self, event, record): """ Setup tracing the given new process or thread :param event: new process/thread event :param record: Action recording object :return: None """ parent = event.process.parent process = event.process record.runtime_log(RuntimeActionRecord.TYPE_SPAWN_CHILD, child_pid=process.pid) TracingThread._trace_continue(parent) # Start new tracing thread for the new process if not self.stopped: self.manager.trace_create_thread(process.pid, process.debugger, process.is_thread) else: TracingThread._trace_continue(process)
class 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
class PythonPtraceBackend(Backend): def __init__(self): self.debugger = PtraceDebugger() self.root = None self.stop_requested = False self.syscalls = {} self.backtracer = NullBacktracer() self.debugger.traceClone() self.debugger.traceExec() self.debugger.traceFork() def attach_process(self, pid): self.root = self.debugger.addProcess(pid, is_attached=False) def create_process(self, arguments): pid = createChild(arguments, no_stdout=False) self.root = self.debugger.addProcess(pid, is_attached=True) return self.root def get_argument(self, pid, num): return self.syscalls[pid].arguments[num].value def get_syscall_result(self, pid): if self.syscalls[pid]: return self.syscalls[pid].result return None def get_arguments_str(self, pid): self.syscalls[pid].format() return ", ".join([ "{}={}".format(i.name, i.text) for i in self.syscalls[pid].arguments ]) def read_cstring(self, pid, address): try: return self.debugger[pid].readCString(address, 255)[0].decode('utf-8') except PtraceError as e: # TODO: ptrace's PREFORMAT_ARGUMENTS, why they are lost? for arg in self.syscalls[pid].arguments: if arg.value == address: return arg.text raise e def read_bytes(self, pid, address, size): return self.debugger[pid].readBytes(address, size) def write_bytes(self, pid, address, data): return self.debugger[pid].writeBytes(address, data) def create_backtrace(self, pid): return self.backtracer.create_backtrace(self.debugger[pid]) def start(self): # First query to break at next syscall self.root.syscall() while not self.stop_requested and self.debugger: try: try: # FIXME: better mechanism to stop debugger # debugger._waitPid(..., blocking=False) may help # actually debugger receives SIGTERM, terminates all remaining process # then this method is unblocked and fails with KeyError event = self.debugger.waitSyscall() except: if self.stop_requested: return raise state = event.process.syscall_state syscall = state.event(FunctionCallOptions()) self.syscalls[event.process.pid] = syscall yield SyscallEvent(event.process.pid, syscall.name) # FIXME: # when exit_group is called, it seems that thread is still monitored # in next event we area accessing pid that is not running anymore # so when exit_group occurs, remove all processes with same Tgid and detach from them if syscall.name == 'exit_group': # result is None, never return! def read_group(pid): with open('/proc/%d/status' % pid) as f: return int({ i: j.strip() for i, j in [ i.split(':', 1) for i in f.read().splitlines() ] }['Tgid']) me = read_group(syscall.process.pid) for process in self.debugger: if read_group(process.pid) == me: process.detach() self.debugger.deleteProcess(pid=process.pid) yield ProcessExited(process.pid, syscall.arguments[0].value) else: event.process.syscall() except ProcessExit as event: # Display syscall which has not exited state = event.process.syscall_state if (state.next_event == "exit") and state.syscall: # self.syscall(state.process) TODO: pass yield ProcessExited(event.process.pid, event.exitcode) except ProcessSignal as event: # event.display() event.process.syscall(event.signum) except NewProcessEvent as event: process = event.process logging.info("*** New process %s ***", process.pid) yield ProcessCreated(pid=process.pid, parent_pid=process.parent.pid, is_thread=process.is_thread) process.syscall() process.parent.syscall() except ProcessExecution as event: logging.info("*** Process %s execution ***", event.process.pid) event.process.syscall() except DebuggerError: if self.stop_requested: return raise except PtraceError as e: if e.errno == 3: # FIXME: same problem as exit_group above ? logging.warning("Process dead?") else: raise def quit(self): self.stop_requested = True self.debugger.quit()
class 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()