From 89d95baede3b81f219e110ecb1691e88b73c6596 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Sat, 10 Sep 2022 17:36:43 +0800 Subject: [PATCH 1/5] add features: mark and jump in qdb --- qiling/debugger/qdb/arch/arch.py | 10 +++++- qiling/debugger/qdb/context.py | 4 ++- qiling/debugger/qdb/qdb.py | 60 ++++++++++++++++++++++++++++++-- 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/qiling/debugger/qdb/arch/arch.py b/qiling/debugger/qdb/arch/arch.py index f84bdcd74..cbe6489a7 100644 --- a/qiling/debugger/qdb/arch/arch.py +++ b/qiling/debugger/qdb/arch/arch.py @@ -5,6 +5,9 @@ from qiling.const import QL_ARCH +from unicorn import UC_ERR_READ_UNMAPPED +import unicorn + class Arch: """ @@ -23,4 +26,9 @@ def archbit(self): return 4 def read_insn(self, address: int): - return self.read_mem(address, self.arch_insn_size) + try: + result = self.read_mem(address, self.arch_insn_size) + except unicorn.unicorn.UcError as err: + result = None + + return result diff --git a/qiling/debugger/qdb/context.py b/qiling/debugger/qdb/context.py index cd9fbc210..a3c71ef86 100644 --- a/qiling/debugger/qdb/context.py +++ b/qiling/debugger/qdb/context.py @@ -48,7 +48,9 @@ def disasm(self, address: int, detail: bool = False) -> Optional[CsInsn]: md = self.ql.arch.disassembler md.detail = detail - return next(md.disasm(self.read_insn(address), address), None) + if (addr := self.read_insn(address)): + return next(md.disasm(addr, address), None) + return None def try_read(self, address: int, size: int) -> Optional[bytes]: """ diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index ef9fc0758..45dfeb8e8 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -8,7 +8,7 @@ import cmd from qiling import Qiling -from qiling.const import QL_ARCH, QL_VERBOSE +from qiling.const import QL_OS, QL_ARCH, QL_VERBOSE from qiling.debugger import QlDebugger from .utils import setup_context_render, setup_branch_predictor, SnapshotManager, run_qdb_script @@ -34,6 +34,7 @@ def __init__(self, ql: Qiling, init_hook: str = "", rr: bool = False, script: st self._saved_reg_dump = None self._script = script self.bp_list = {} + self.mark_list = {} self.rr = SnapshotManager(ql) if rr else None self.mm = setup_memory_Manager(ql) @@ -72,7 +73,10 @@ def bp_handler(ql, address, size, bp_list): self.ql.hook_code(bp_handler, self.bp_list) - if init_hook and self.ql.loader.entry_point != init_hook: + if self.ql.os.type == QL_OS.BLOB: + self.ql.loader.entry_point = self.ql.loader.load_address + + elif init_hook and self.ql.loader.entry_point != init_hook: self.do_breakpoint(init_hook) self.cur_addr = self.ql.loader.entry_point @@ -348,6 +352,55 @@ def do_context(self, *args) -> None: self.render.context_stack() self.render.context_asm() + def do_jump(self, address, *args) -> None: + """ + seek to where ever valid location you want + """ + + symbol, addr = None, None + try: + addr = int(address, 0) + except: + symbol = address + + if symbol: + addr = self.mark_list.get(symbol, None) + + if self.ql.mem.is_mapped(addr, 4): + qdb_print(QDB_MSG.INFO, f"seek to 0x{addr:08x} ...") + self.cur_addr = addr + self.do_context() + + else: + qdb_print(QDB_MSG.ERROR, f"the address to be seeked isn't mapped") + + def do_mark(self, args): + """ + mark a user specified address as a symbol + """ + + args = args.split() + if len(args) == 1: + try: + tmp = int(args[0], 0) + except: + tmp = args[0] + + if type(tmp) is str: + symbol = tmp + else: + address = tmp + + else: + symbol, address = args + + if symbol: + addr = self.cur_addr if address is None else int(address, 0) + self.mark_list.update({symbol: addr}) + qdb_print(QDB_MSG.INFO, f"mark symbol '{symbol}' at address: 0x{addr:08x} ...") + else: + qdb_print(QDB_MSG.ERROR, "symbol should not be empty ...") + def do_show(self, *args) -> None: """ show some runtime information @@ -355,6 +408,7 @@ def do_show(self, *args) -> None: self.ql.mem.show_mapinfo() qdb_print(QDB_MSG.INFO, f"Breakpoints: {[hex(addr) for addr in self.bp_list.keys()]}") + qdb_print(QDB_MSG.INFO, f"Marked symbol: {[{key:hex(val)} for key,val in self.mark_list.items()]}") if self.rr: qdb_print(QDB_MSG.INFO, f"Snapshots: {len([st for st in self.rr.layers if isinstance(st, self.rr.DiffedState)])}") @@ -401,6 +455,8 @@ def do_EOF(self, *args) -> None: do_r = do_run do_s = do_step_in do_n = do_step_over + do_j = do_jump + do_m = do_mark do_q = do_quit do_x = do_examine do_p = do_backward From c1068dedacfd05394f6c593e5c1cafb51f101d76 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Sat, 10 Sep 2022 18:59:40 +0800 Subject: [PATCH 2/5] move marking-related function into a class Marker --- qiling/debugger/qdb/misc.py | 20 ++++++++--- qiling/debugger/qdb/qdb.py | 68 ++++++++++++++++++++---------------- qiling/debugger/qdb/utils.py | 47 ++++++++++++++++++++++++- 3 files changed, 98 insertions(+), 37 deletions(-) diff --git a/qiling/debugger/qdb/misc.py b/qiling/debugger/qdb/misc.py index bfff5aa8e..d9bf67362 100644 --- a/qiling/debugger/qdb/misc.py +++ b/qiling/debugger/qdb/misc.py @@ -3,7 +3,7 @@ # Cross Platform and Multi Architecture Advanced Binary Emulation Framework # -from typing import Callable, Optional +from typing import AnyStr, Callable, Optional import ast @@ -50,17 +50,27 @@ def read_int(s: str) -> int: return int(s, 0) +def try_read_int(s: AnyStr) -> Optional[int]: + """ + try to read string as integer is possible + """ + try: + ret = read_int(s) + except: + ret = None + + return ret + + def parse_int(func: Callable) -> Callable: """ function dectorator for parsing argument as integer """ def wrap(qdb, s: str = "") -> int: assert type(s) is str - try: - ret = read_int(s) - except: - ret = None + ret = try_read_int(s) return func(qdb, ret) + return wrap diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 21dc78c6e..46f98d988 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -11,9 +11,9 @@ from qiling.const import QL_OS, QL_ARCH, QL_VERBOSE from qiling.debugger import QlDebugger -from .utils import setup_context_render, setup_branch_predictor, SnapshotManager, run_qdb_script +from .utils import setup_context_render, setup_branch_predictor, setup_address_marker, SnapshotManager, run_qdb_script from .memory import setup_memory_Manager -from .misc import parse_int, Breakpoint, TempBreakpoint +from .misc import parse_int, Breakpoint, TempBreakpoint, try_read_int from .const import color from .utils import QDB_MSG, qdb_print @@ -34,7 +34,7 @@ def __init__(self, ql: Qiling, init_hook: str = "", rr: bool = False, script: st self._saved_reg_dump = None self._script = script self.bp_list = {} - self.mark_list = {} + self.marker = setup_address_marker() self.rr = SnapshotManager(ql) if rr else None self.mm = setup_memory_Manager(ql) @@ -352,54 +352,60 @@ def do_context(self, *args) -> None: self.render.context_stack() self.render.context_asm() - def do_jump(self, address, *args) -> None: + def do_jump(self, loc: str, *args) -> None: """ seek to where ever valid location you want """ - symbol, addr = None, None - try: - addr = int(address, 0) - except: - symbol = address - - if symbol: - addr = self.mark_list.get(symbol, None) + sym = self.marker.get_symbol(loc) + addr = sym if sym is not None else try_read_int(loc) + # check validation of the address to be seeked if self.ql.mem.is_mapped(addr, 4): - qdb_print(QDB_MSG.INFO, f"seek to 0x{addr:08x} ...") + if sym: + qdb_print(QDB_MSG.INFO, f"seek to {loc} @ 0x{addr:08x} ...") + else: + qdb_print(QDB_MSG.INFO, f"seek to 0x{addr:08x} ...") + self.cur_addr = addr self.do_context() else: qdb_print(QDB_MSG.ERROR, f"the address to be seeked isn't mapped") - def do_mark(self, args): + def do_mark(self, args=""): """ mark a user specified address as a symbol """ args = args.split() - if len(args) == 1: - try: - tmp = int(args[0], 0) - except: - tmp = args[0] - - if type(tmp) is str: - symbol = tmp - else: - address = tmp + if len(args) == 0: + loc = self.cur_addr + sym_name = self.marker.mark_only_loc(loc) - else: - symbol, address = args + elif len(args) == 1: + if (loc := try_read_int(args[0])): + sym_name = self.marker.mark_only_loc(loc) - if symbol: - addr = self.cur_addr if address is None else int(address, 0) - self.mark_list.update({symbol: addr}) - qdb_print(QDB_MSG.INFO, f"mark symbol '{symbol}' at address: 0x{addr:08x} ...") + else: + loc = self.cur_addr + sym_name = args[0] + if (err := self.marker.mark(sym_name, loc)): + qdb_print(QDB_MSG.ERROR, err) + return + + elif len(args) == 2: + sym_name, addr = args + if (loc := try_read_int(addr)): + self.marker.mark(sym_name, loc) + else: + qdb_print(QDB_MSG.ERROR, f"unable to mark symbol at address: '{addr}'") + return else: qdb_print(QDB_MSG.ERROR, "symbol should not be empty ...") + return + + qdb_print(QDB_MSG.INFO, f"mark symbol '{sym_name}' at address: 0x{loc:08x} ...") def do_show(self, *args) -> None: """ @@ -410,7 +416,7 @@ def do_show(self, *args) -> None: self.ql.log.info(info_line) qdb_print(QDB_MSG.INFO, f"Breakpoints: {[hex(addr) for addr in self.bp_list.keys()]}") - qdb_print(QDB_MSG.INFO, f"Marked symbol: {[{key:hex(val)} for key,val in self.mark_list.items()]}") + qdb_print(QDB_MSG.INFO, f"Marked symbol: {[{key:hex(val)} for key,val in self.marker.mark_list]}") if self.rr: qdb_print(QDB_MSG.INFO, f"Snapshots: {len([st for st in self.rr.layers if isinstance(st, self.rr.DiffedState)])}") diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index fdbde9d85..4803df8e4 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -12,7 +12,6 @@ from qiling.const import QL_ARCH from .context import Context -from .misc import read_int from .render import ( ContextRenderX86, @@ -51,6 +50,52 @@ def print_info(msg): print(color_coated) +def setup_address_marker(): + + class Marker: + def __init__(self): + self._mark_list = {} + + def get_symbol(self, sym): + return self._mark_list.get(sym, None) + + @property + def mark_list(self): + return self._mark_list.items() + + def gen_sym_name(self): + """ + generating symbol name automatically + """ + + sym_name, idx = "sym0", 0 + while sym_name in self._mark_list: + idx += 1 + sym_name = f"sym{idx}" + + return sym_name + + def mark_only_loc(self, loc): + """ + mark when location provided only + """ + + sym_name = self.gen_sym_name() + self.mark(sym_name, loc) + return sym_name + + def mark(self, sym: str, loc: int): + """ + mark loc as sym + """ + + if sym not in self.mark_list: + self._mark_list.update({sym: loc}) + else: + return f"dumplicated symbol name: {sym} at address: 0x{loc:08x}" + + return Marker() + """ helper functions for setting proper branch predictor and context render depending on different arch From 30046d83da0361261254b31650ec8c48f44329d7 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Sat, 10 Sep 2022 19:06:33 +0800 Subject: [PATCH 3/5] more comment --- qiling/debugger/qdb/utils.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index 4803df8e4..ec960c435 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -49,6 +49,11 @@ def print_info(msg): print(color_coated) +""" + + class Marker provide the ability for marking an address as a more easier rememberable alias + +""" def setup_address_marker(): @@ -57,10 +62,18 @@ def __init__(self): self._mark_list = {} def get_symbol(self, sym): + """ + get the mapped address to a symbol if it's in the mark_list + """ + return self._mark_list.get(sym, None) @property def mark_list(self): + """ + get a list about what we marked + """ + return self._mark_list.items() def gen_sym_name(self): From de7e51087f887b5011624fcf51e388d64913cea1 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Sat, 10 Sep 2022 21:28:05 +0800 Subject: [PATCH 4/5] add feature: setting register value in qdb --- qiling/debugger/qdb/qdb.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 46f98d988..33f83d1f9 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -323,6 +323,7 @@ def do_disassemble(self, address: Optional[int] = None) -> None: qdb_print(QDB_MSG.ERROR) def do_examine(self, line: str) -> None: + """ Examine memory: x/FMT ADDRESS. format letter: o(octal), x(hex), d(decimal), u(unsigned decimal), t(binary), f(float), a(address), i(instruction), c(char), s(string) and z(hex, zero padded on the left) @@ -332,6 +333,29 @@ def do_examine(self, line: str) -> None: if type(err_msg := self.mm.parse(line)) is str: qdb_print(QDB_MSG.ERROR, err_msg) + + + def do_set(self, line: str) -> None: + """ + set register value of current context + """ + # set $a = b + + reg, val = line.split("=") + reg_name = reg.strip().strip("$") + reg_val = try_read_int(val.strip()) + + if reg_name in self.ql.arch.regs.save().keys(): + if reg_val: + setattr(self.ql.arch.regs, reg_name, reg_val) + self.do_context() + qdb_print(QDB_MSG.INFO, f"set register {reg_name} to 0x{reg_val:08x}") + + else: + qdb_print(QDB_MSG.ERROR, f"error parsing input: {reg_val} as integer value") + + else: + qdb_print(QDB_MSG.ERROR, f"invalid register: {reg_name}") def do_start(self, *args) -> None: """ From 0f220941806b98393e27faea6b6b64b705f31834 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Sat, 10 Sep 2022 21:36:54 +0800 Subject: [PATCH 5/5] handle negtive value in register setting --- qiling/debugger/qdb/qdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 33f83d1f9..4dd516e7c 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -349,7 +349,7 @@ def do_set(self, line: str) -> None: if reg_val: setattr(self.ql.arch.regs, reg_name, reg_val) self.do_context() - qdb_print(QDB_MSG.INFO, f"set register {reg_name} to 0x{reg_val:08x}") + qdb_print(QDB_MSG.INFO, f"set register {reg_name} to 0x{(reg_val & 0xfffffff):08x}") else: qdb_print(QDB_MSG.ERROR, f"error parsing input: {reg_val} as integer value")