From 3883024a92f2998132959f57cdb6fcfb1e3d56c3 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Tue, 9 Nov 2021 21:25:37 -0800 Subject: [PATCH 1/6] fix missing prefix-f and remove useless comment --- qiling/debugger/qdb/frontend.py | 2 +- qiling/debugger/qdb/qdb.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index a69e61951..0bbf0f6e5 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -87,7 +87,7 @@ def unpack(bs, sz): for line in range(lines): offset = line * sz * 4 - print("0x{addr+offset:x}:\t", end="") + print(f"0x{addr+offset:x}:\t", end="") idx = line * ql.pointersize for each in mem_read[idx:idx+ql.pointersize]: diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index b1a234d3e..61b1c95a1 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -300,7 +300,6 @@ def do_quit(self: QlQdb, *args) -> bool: """ exit Qdb and stop running qiling instance """ - # breakpoint() self._ql.stop() return True From 25864a44cfe52c663feca45728117fe1d6bfdf56 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Wed, 10 Nov 2021 02:57:47 -0800 Subject: [PATCH 2/6] new command ui for qdb --- qiling/debugger/qdb/frontend.py | 20 +++++++++++--------- qiling/debugger/qdb/qdb.py | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index 0bbf0f6e5..3c7b9286d 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -120,17 +120,19 @@ def _try_read(ql: Qiling, address: int, size: int) -> Optional[bytes]: # divider printer @contextmanager -def context_printer(ql: Qiling, field_name: str, ruler: str = "=") -> None: - _height, _width = get_terminal_size() - print(field_name, ruler * (_width - len(field_name) - 1)) +def context_printer(ql: Qiling, field_name: str, ruler: str = "─") -> None: + height, width = get_terminal_size() + bar = (width - len(field_name)) // 2 - 1 + print(ruler * bar, field_name, ruler * bar) yield - print(ruler * _width) + if "DISASM" in field_name: + print(ruler * width) def context_reg(ql: Qiling, saved_states: Optional[Mapping[str, int]] = None, /, *args, **kwargs) -> None: # context render for registers - with context_printer(ql, "[Registers]"): + with context_printer(ql, "[ REGISTERS ]"): _cur_regs = dump_regs(ql) @@ -194,12 +196,12 @@ def context_reg(ql: Qiling, saved_states: Optional[Mapping[str, int]] = None, /, print(color.GREEN, "[{cpsr[mode]} mode], Thumb: {cpsr[thumb]}, FIQ: {cpsr[fiq]}, IRQ: {cpsr[irq]}, NEG: {cpsr[neg]}, ZERO: {cpsr[zero]}, Carry: {cpsr[carry]}, Overflow: {cpsr[overflow]}".format(cpsr=get_arm_flags(ql.reg.cpsr)), color.END, sep="") # context render for Stack - with context_printer(ql, "[Stack]", ruler="-"): + with context_printer(ql, "[ STACK ]", ruler="─"): for idx in range(8): _addr = ql.reg.arch_sp + idx * 4 _val = ql.mem.read(_addr, ql.pointersize) - print(f"$sp+0x{idx*4:02x}|[0x{_addr:08x}]=> 0x{ql.unpack(_val):08x}", end="") + print(f"$sp+0x{idx*4:02x}│ [0x{_addr:08x}] —▸ 0x{ql.unpack(_val):08x}", end="") try: # try to deference wether its a pointer _deref = ql.mem.read(_addr, ql.pointersize) @@ -213,14 +215,14 @@ def context_reg(ql: Qiling, saved_states: Optional[Mapping[str, int]] = None, /, def print_asm(ql: Qiling, ins: CsInsn) -> None: fmt = (ins.address, ins.mnemonic.ljust(6), ins.op_str) if ql.reg.arch_pc == ins.address: - print(f"PC ==> 0x{fmt[0]:x}\t{fmt[1]} {fmt[2]}") + print(f"{color.BOLD} ► 0x{fmt[0]:x}\t{fmt[1]} {fmt[2]}{color.END}") else: print(f"\t0x{fmt[0]:x}\t{fmt[1]} {fmt[2]}") def context_asm(ql: Qiling, address: int) -> None: - with context_printer(ql, field_name="[Code]"): + with context_printer(ql, field_name="[ DISASM ]"): # assembly before current location diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 61b1c95a1..7899eb376 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -23,7 +23,7 @@ class QlQdb(cmd.Cmd, QlDebugger): def __init__(self: QlQdb, ql: Qiling, init_hook: str = "", rr: bool = False) -> None: self._ql = ql - self.prompt = "(Qdb) " + self.prompt = f"{color.BOLD}{color.RED}Qdb> {color.END}" self.breakpoints = {} self._saved_reg_dump = None self.bp_list = {} From f74e303b6ca81309ff56da5f0b6efdadceb6607c Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Wed, 10 Nov 2021 22:36:37 -0800 Subject: [PATCH 3/6] better hint for branch insn --- qiling/debugger/qdb/const.py | 2 ++ qiling/debugger/qdb/frontend.py | 25 +++++++++++----- qiling/debugger/qdb/qdb.py | 52 ++++++++++++++++----------------- qiling/debugger/qdb/utils.py | 13 +++++---- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/qiling/debugger/qdb/const.py b/qiling/debugger/qdb/const.py index bb5ca921a..fd8b358ef 100644 --- a/qiling/debugger/qdb/const.py +++ b/qiling/debugger/qdb/const.py @@ -13,6 +13,8 @@ class color: UNDERLINE = '\033[4m' BOLD = '\033[1m' END = '\033[0m' + RESET = '\x1b[39m' + FORMAT_LETTER = { diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index 3c7b9286d..47d1cd3df 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -11,7 +11,7 @@ from qiling.const import QL_ARCH -from .utils import dump_regs, get_arm_flags, disasm, parse_int +from .utils import dump_regs, get_arm_flags, disasm, parse_int, handle_bnj from .const import * @@ -212,12 +212,20 @@ def context_reg(ql: Qiling, saved_states: Optional[Mapping[str, int]] = None, /, print(f" => 0x{ql.unpack(_deref):08x}") -def print_asm(ql: Qiling, ins: CsInsn) -> None: - fmt = (ins.address, ins.mnemonic.ljust(6), ins.op_str) - if ql.reg.arch_pc == ins.address: - print(f"{color.BOLD} ► 0x{fmt[0]:x}\t{fmt[1]} {fmt[2]}{color.END}") - else: - print(f"\t0x{fmt[0]:x}\t{fmt[1]} {fmt[2]}") +def print_asm(ql: Qiling, insn: CsInsn, to_jump: Optional[bool] = None, address: int = None) -> None: + + opcode = "".join(f"{b:02x}" for b in insn.bytes) + trace_line = f"0x{insn.address:08x} │ {opcode:10s} {insn.mnemonic:10} {insn.op_str:35s}" + + cursor = " " + if ql.reg.arch_pc == insn.address: + cursor = "►" + + jump_sign = " " + if to_jump and address != ql.reg.arch_pc+4: + jump_sign = f"{color.RED}✓{color.END}" + + print(f"{jump_sign} {cursor} {color.DARKGRAY}{trace_line}{color.END}") def context_asm(ql: Qiling, address: int) -> None: @@ -251,7 +259,8 @@ def context_asm(ql: Qiling, address: int) -> None: # assembly for current location cur_ins = disasm(ql, address) - print_asm(ql, cur_ins) + to_jump, next_stop = handle_bnj(ql, address) + print_asm(ql, cur_ins, to_jump=to_jump) # assembly after current location diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 7899eb376..97802a7ad 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -22,7 +22,7 @@ class QlQdb(cmd.Cmd, QlDebugger): def __init__(self: QlQdb, ql: Qiling, init_hook: str = "", rr: bool = False) -> None: - self._ql = ql + self.ql = ql self.prompt = f"{color.BOLD}{color.RED}Qdb> {color.END}" self.breakpoints = {} self._saved_reg_dump = None @@ -35,7 +35,7 @@ def __init__(self: QlQdb, ql: Qiling, init_hook: str = "", rr: bool = False) -> super().__init__() # setup a breakpoint at entry point or user specified address - address = self._ql.loader.entry_point if not init_hook else parse_int(init_hook) + address = self.ql.loader.entry_point if not init_hook else parse_int(init_hook) self.set_breakpoint(address, is_temp=True) self.dbg_hook() @@ -44,7 +44,7 @@ def dbg_hook(self: QlQdb): hook every instruction with callback funtion _bp_handler """ - self._ql.hook_code(self._bp_handler) + self.ql.hook_code(self._bp_handler) @property def cur_addr(self: QlQdb) -> int: @@ -52,7 +52,7 @@ def cur_addr(self: QlQdb) -> int: getter for current address of qiling instance """ - return self._ql.reg.arch_pc + return self.ql.reg.arch_pc @cur_addr.setter def cur_addr(self: QlQdb, address: int) -> None: @@ -60,7 +60,7 @@ def cur_addr(self: QlQdb, address: int) -> None: setter for current address of qiling instance """ - self._ql.reg.arch_pc = address + self.ql.reg.arch_pc = address def _bp_handler(self: QlQdb, *args) -> None: """ @@ -83,21 +83,21 @@ def _save(self: QlQdb, *args) -> None: internal function for saving state of qiling instance """ - self._states_list.append(self._ql.save()) + self._states_list.append(self.ql.save()) def _restore(self: QlQdb, *args) -> None: """ internal function for restoring state of qiling instance """ - self._ql.restore(self._states_list.pop()) + self.ql.restore(self._states_list.pop()) def _run(self: Qldbg, *args) -> None: """ internal function for launching qiling instance """ - self._ql.run() + self.ql.run() def parseline(self: QlQdb, line: str) -> Tuple[Optional[str], Optional[str], str]: """ @@ -150,12 +150,12 @@ def do_run(self: QlQdb, *args) -> None: launch qiling instance from a fresh start """ - self._ql = Qiling( - argv=self._ql.argv, - rootfs=self._ql.rootfs, - verbose=self._ql.verbose, - console=self._ql.console, - log_file=self._ql.log_file, + self.ql = Qiling( + argv=self.ql.argv, + rootfs=self.ql.rootfs, + verbose=self.ql.verbose, + console=self.ql.console, + log_file=self.ql.log_file, ) self.dbg_hook() @@ -166,8 +166,8 @@ def do_context(self: QlQdb, *args) -> None: show context information for current location """ - context_reg(self._ql, self._saved_reg_dump) - context_asm(self._ql, self.cur_addr) + context_reg(self.ql, self._saved_reg_dump) + context_asm(self.ql, self.cur_addr) def do_backward(self: QlQdb, *args) -> None: """ @@ -187,13 +187,13 @@ def do_step(self: QlQdb, *args) -> Optional[bool, None]: execute one instruction at a time """ - if self._ql is None: + if self.ql is None: print(f"{color.RED}[!] The program is not being run.{color.END}") else: - self._saved_reg_dump = dict(filter(lambda d: isinstance(d[0], str), self._ql.reg.save().items())) + self._saved_reg_dump = dict(filter(lambda d: isinstance(d[0], str), self.ql.reg.save().items())) - next_stop = handle_bnj(self._ql, self.cur_addr) + _, next_stop = handle_bnj(self.ql, self.cur_addr) if next_stop is CODE_END: return True @@ -226,8 +226,8 @@ def do_start(self: QlQdb, address: str = "", *args) -> None: pause at entry point by setting a temporary breakpoint on it """ - # entry = self._ql.loader.entry_point # ld.so - # entry = self._ql.loader.elf_entry # .text of binary + # entry = self.ql.loader.entry_point # ld.so + # entry = self.ql.loader.elf_entry # .text of binary self._run() @@ -236,7 +236,7 @@ def do_breakpoint(self: QlQdb, address: str = "") -> None: set breakpoint on specific address """ - # address = parse_int(address) if address else self._ql.reg.arch_pc + # address = parse_int(address) if address else self.ql.reg.arch_pc address = parse_int(address) if address else self.cur_addr self.set_breakpoint(address) @@ -263,7 +263,7 @@ def do_examine(self: QlQdb, line: str) -> None: """ try: - if not examine_mem(self._ql, line): + if not examine_mem(self.ql, line): self.do_help("examine") except: print(f"{color.RED}[!] something went wrong ...{color.END}") @@ -273,7 +273,7 @@ def do_show(self: QlQdb, *args) -> None: show some runtime information """ - self._ql.mem.show_mapinfo() + self.ql.mem.show_mapinfo() print(f"Breakpoints: {[hex(addr) for addr in self.bp_list.keys()]}") def do_disassemble(self: QlQdb, address: str, /, *args, **kwargs) -> None: @@ -282,7 +282,7 @@ def do_disassemble(self: QlQdb, address: str, /, *args, **kwargs) -> None: """ try: - context_asm(self._ql, parse_int(address), 4) + context_asm(self.ql, parse_int(address), 4) except: print(f"{color.RED}[!] something went wrong ...{color.END}") @@ -301,7 +301,7 @@ def do_quit(self: QlQdb, *args) -> bool: exit Qdb and stop running qiling instance """ - self._ql.stop() + self.ql.stop() return True do_r = do_run diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index c5a61d0e7..42370c8a7 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -68,7 +68,7 @@ def _get_mode(bits): # parse unsigned integer from string def parse_int(s: str) -> int: - return int(s, 16) if s.startswith("0x") else int(s) + return int(s, 0) # check wether negative value or not @@ -103,11 +103,12 @@ def is_thumb(bits: int) -> bool: return bits & 0x00000020 != 0 -def disasm(ql: Qiling, address: int) -> Optional[int]: - # md = ql.create_disassembler() +def disasm(ql: Qiling, address: int, detail: bool = False) -> Optional[int]: + md = ql.disassembler + md.detail = detail try: - ret = next(ql.disassembler.disasm(_read_inst(ql, address), address)) + ret = next(md.disasm(_read_inst(ql, address), address)) except StopIteration: ret = None @@ -350,7 +351,7 @@ def regdst_eq_pc(op_str): if ret_addr & 1: ret_addr -= 1 - return ret_addr + return (to_jump, ret_addr) def handle_bnj_mips(ql: Qiling, cur_addr: str) -> int: @@ -409,7 +410,7 @@ def _read_reg(regs, _reg): # target address is always the rightmost one ret_addr = targets[-1] - return ret_addr + return (to_jump, ret_addr) class Breakpoint(object): """ From 8557a6430ff5b422d2906648243ed2ac728307fc Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Wed, 10 Nov 2021 22:37:17 -0800 Subject: [PATCH 4/6] remove tailing space --- qiling/debugger/qdb/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 42370c8a7..d5445f242 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -68,7 +68,7 @@ def _get_mode(bits): # parse unsigned integer from string def parse_int(s: str) -> int: - return int(s, 0) + return int(s, 0) # check wether negative value or not From 138b8f84a7f33789f3337e44f9a779903d4e950d Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Wed, 10 Nov 2021 23:49:50 -0800 Subject: [PATCH 5/6] add feature: branch hint --- qiling/debugger/qdb/frontend.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/qiling/debugger/qdb/frontend.py b/qiling/debugger/qdb/frontend.py index 47d1cd3df..844dae9fd 100644 --- a/qiling/debugger/qdb/frontend.py +++ b/qiling/debugger/qdb/frontend.py @@ -198,18 +198,33 @@ def context_reg(ql: Qiling, saved_states: Optional[Mapping[str, int]] = None, /, # context render for Stack with context_printer(ql, "[ STACK ]", ruler="─"): - for idx in range(8): - _addr = ql.reg.arch_sp + idx * 4 - _val = ql.mem.read(_addr, ql.pointersize) - print(f"$sp+0x{idx*4:02x}│ [0x{_addr:08x}] —▸ 0x{ql.unpack(_val):08x}", end="") + for idx in range(10): + addr = ql.reg.arch_sp + idx * ql.pointersize + val = ql.mem.read(addr, ql.pointersize) + print(f"$sp+0x{idx*ql.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{ql.unpack(val):08x}", end="") try: # try to deference wether its a pointer - _deref = ql.mem.read(_addr, ql.pointersize) + buf = ql.mem.read(addr, ql.pointersize) except: - _deref = None - - if _deref: - print(f" => 0x{ql.unpack(_deref):08x}") + buf = None + + if (addr := ql.unpack(buf)): + try: # try to deference again + buf = ql.mem.read(addr, ql.pointersize) + except: + buf = None + + if buf: + try: + s = ql.mem.string(addr) + except: + s = None + + if s and s.isprintable(): + print(f" ◂— {ql.mem.string(addr)}", end="") + else: + print(f" ◂— 0x{ql.unpack(buf):08x}", end="") + print() def print_asm(ql: Qiling, insn: CsInsn, to_jump: Optional[bool] = None, address: int = None) -> None: From beb0b76cc4b447a2679c094395061085c633271f Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Thu, 11 Nov 2021 03:47:49 -0800 Subject: [PATCH 6/6] fix mips branch hint --- qiling/debugger/qdb/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index d5445f242..b0467959e 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -370,6 +370,7 @@ def _read_reg(regs, _reg): # default breakpoint address if no jumps and branches here ret_addr = cur_addr + MIPS_INST_SIZE + to_jump = False if line.mnemonic.startswith('j') or line.mnemonic.startswith('b'): # make sure at least delay slot executed