diff --git a/qiling/debugger/qdb/arch/__init__.py b/qiling/debugger/qdb/arch/__init__.py index 24794a4cb..4c5b7a385 100644 --- a/qiling/debugger/qdb/arch/__init__.py +++ b/qiling/debugger/qdb/arch/__init__.py @@ -6,3 +6,4 @@ from .arch_x86 import ArchX86 from .arch_mips import ArchMIPS from .arch_arm import ArchARM, ArchCORTEX_M +from .arch_x8664 import ArchX8664 \ No newline at end of file diff --git a/qiling/debugger/qdb/arch/arch_x8664.py b/qiling/debugger/qdb/arch/arch_x8664.py new file mode 100644 index 000000000..686e2016e --- /dev/null +++ b/qiling/debugger/qdb/arch/arch_x8664.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + +from typing import Mapping + +from .arch import Arch + +class ArchX8664(Arch): + ''' + This is currently mostly just a copy of x86 - other than the size of archbits. Some of this may be wrong. + ''' + + def __init__(self): + super().__init__() + + @property + def arch_insn_size(self): + ''' + Architecture maximum instruction size. x86_64 instructions are a maximum size of 15 bytes. + + @returns bytes + ''' + + return 15 + + @property + def regs(self): + return ( + "rax", "rbx", "rcx", "rdx", + "rsp", "rbp", "rsi", "rdi", + "rip", "r8", "r9", "r10", + "r11", "r12", "r13", "r14", + "r15", "ss", "cs", "ds", "es", + "fs", "gs", "eflags" + ) + + @property + def archbit(self): + ''' + Architecture maximum register size. x86 is a maximum of 4 bytes. + + @returns bytes + ''' + + return 8 + + def read_insn(self, address: int) -> bytes: + # Due to the variadicc length of x86 instructions + # always assume the maximum size for disassembler to tell + # what it is. + + return self.read_mem(address, self.arch_insn_size) + + @staticmethod + def get_flags(bits: int) -> Mapping[str, bool]: + + return { + "CF" : bits & 0x0001 != 0, # CF, carry flag + "PF" : bits & 0x0004 != 0, # PF, parity flag + "AF" : bits & 0x0010 != 0, # AF, adjust flag + "ZF" : bits & 0x0040 != 0, # ZF, zero flag + "SF" : bits & 0x0080 != 0, # SF, sign flag + "OF" : bits & 0x0800 != 0, # OF, overflow flag + } diff --git a/qiling/debugger/qdb/branch_predictor/__init__.py b/qiling/debugger/qdb/branch_predictor/__init__.py index 67c0578fa..69e9e6d9f 100644 --- a/qiling/debugger/qdb/branch_predictor/__init__.py +++ b/qiling/debugger/qdb/branch_predictor/__init__.py @@ -6,3 +6,4 @@ from .branch_predictor_x86 import BranchPredictorX86 from .branch_predictor_mips import BranchPredictorMIPS from .branch_predictor_arm import BranchPredictorARM, BranchPredictorCORTEX_M +from .branch_predictor_x8664 import BranchPredictorX8664 diff --git a/qiling/debugger/qdb/branch_predictor/branch_predictor_x8664.py b/qiling/debugger/qdb/branch_predictor/branch_predictor_x8664.py new file mode 100644 index 000000000..f50dad11a --- /dev/null +++ b/qiling/debugger/qdb/branch_predictor/branch_predictor_x8664.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + + + +import ast, re + +from .branch_predictor import * +from ..arch import ArchX8664 +from ..misc import check_and_eval + +class BranchPredictorX8664(BranchPredictor, ArchX8664): + """ + predictor for X86 + """ + + class ParseError(Exception): + """ + indicate parser error + """ + pass + + def __init__(self, ql): + super().__init__(ql) + ArchX8664.__init__(self) + + def predict(self): + prophecy = self.Prophecy() + line = self.disasm(self.cur_addr) + + jump_table = { + # conditional jump + + "jo" : (lambda C, P, A, Z, S, O: O == 1), + "jno" : (lambda C, P, A, Z, S, O: O == 0), + + "js" : (lambda C, P, A, Z, S, O: S == 1), + "jns" : (lambda C, P, A, Z, S, O: S == 0), + + "je" : (lambda C, P, A, Z, S, O: Z == 1), + "jz" : (lambda C, P, A, Z, S, O: Z == 1), + + "jne" : (lambda C, P, A, Z, S, O: Z == 0), + "jnz" : (lambda C, P, A, Z, S, O: Z == 0), + + "jb" : (lambda C, P, A, Z, S, O: C == 1), + "jc" : (lambda C, P, A, Z, S, O: C == 1), + "jnae" : (lambda C, P, A, Z, S, O: C == 1), + + "jnb" : (lambda C, P, A, Z, S, O: C == 0), + "jnc" : (lambda C, P, A, Z, S, O: C == 0), + "jae" : (lambda C, P, A, Z, S, O: C == 0), + + "jbe" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1), + "jna" : (lambda C, P, A, Z, S, O: C == 1 or Z == 1), + + "ja" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0), + "jnbe" : (lambda C, P, A, Z, S, O: C == 0 and Z == 0), + + "jl" : (lambda C, P, A, Z, S, O: S != O), + "jnge" : (lambda C, P, A, Z, S, O: S != O), + + "jge" : (lambda C, P, A, Z, S, O: S == O), + "jnl" : (lambda C, P, A, Z, S, O: S == O), + + "jle" : (lambda C, P, A, Z, S, O: Z == 1 or S != O), + "jng" : (lambda C, P, A, Z, S, O: Z == 1 or S != O), + + "jg" : (lambda C, P, A, Z, S, O: Z == 0 or S == O), + "jnle" : (lambda C, P, A, Z, S, O: Z == 0 or S == O), + + "jp" : (lambda C, P, A, Z, S, O: P == 1), + "jpe" : (lambda C, P, A, Z, S, O: P == 1), + + "jnp" : (lambda C, P, A, Z, S, O: P == 0), + "jpo" : (lambda C, P, A, Z, S, O: P == 0), + + # unconditional jump + + "call" : (lambda *_: True), + "jmp" : (lambda *_: True), + + } + + jump_reg_table = { + "jcxz" : (lambda cx: cx == 0), + "jecxz" : (lambda ecx: ecx == 0), + "jrcxz" : (lambda rcx: rcx == 0), + } + + if line.mnemonic in jump_table: + eflags = self.get_flags(self.ql.arch.regs.eflags).values() + prophecy.going = jump_table.get(line.mnemonic)(*eflags) + + elif line.mnemonic in jump_reg_table: + prophecy.going = jump_reg_table.get(line.mnemonic)(self.ql.arch.regs.ecx) + + if prophecy.going: + takeaway_list = ["ptr", "dword", "[", "]"] + + if len(line.op_str.split()) > 1: + new_line = line.op_str.replace(":", "+") + for each in takeaway_list: + new_line = new_line.replace(each, " ") + + new_line = " ".join(new_line.split()) + for each_reg in filter(lambda r: len(r) == 3, self.ql.arch.regs.register_mapping.keys()): + if each_reg in new_line: + new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) + + for each_reg in filter(lambda r: len(r) == 2, self.ql.arch.regs.register_mapping.keys()): + if each_reg in new_line: + new_line = re.sub(each_reg, hex(self.read_reg(each_reg)), new_line) + + + prophecy.where = check_and_eval(new_line) + + elif line.op_str in self.ql.arch.regs.register_mapping: + prophecy.where = self.ql.arch.regs.read(line.op_str) + + else: + prophecy.where = read_int(line.op_str) + else: + prophecy.where = self.cur_addr + line.size + + return prophecy diff --git a/qiling/debugger/qdb/memory.py b/qiling/debugger/qdb/memory.py index 9090dafe5..9dbece6a9 100644 --- a/qiling/debugger/qdb/memory.py +++ b/qiling/debugger/qdb/memory.py @@ -6,7 +6,7 @@ from qiling.const import QL_ARCH from .context import Context -from .arch import ArchCORTEX_M, ArchARM, ArchMIPS, ArchX86 +from .arch import ArchCORTEX_M, ArchARM, ArchMIPS, ArchX86, ArchX8664 from .misc import check_and_eval import re, math @@ -16,6 +16,7 @@ def setup_memory_Manager(ql): arch_type = { QL_ARCH.X86: ArchX86, + QL_ARCH.X8664: ArchX8664, QL_ARCH.MIPS: ArchMIPS, QL_ARCH.ARM: ArchARM, QL_ARCH.CORTEX_M: ArchCORTEX_M, diff --git a/qiling/debugger/qdb/render/__init__.py b/qiling/debugger/qdb/render/__init__.py index d36dfa9ae..78acc1646 100644 --- a/qiling/debugger/qdb/render/__init__.py +++ b/qiling/debugger/qdb/render/__init__.py @@ -6,3 +6,4 @@ from .render_x86 import ContextRenderX86 from .render_mips import ContextRenderMIPS from .render_arm import ContextRenderARM, ContextRenderCORTEX_M +from .render_x8664 import ContextRenderX8664 \ No newline at end of file diff --git a/qiling/debugger/qdb/render/render.py b/qiling/debugger/qdb/render/render.py index b8d7eb08f..393e7707b 100644 --- a/qiling/debugger/qdb/render/render.py +++ b/qiling/debugger/qdb/render/render.py @@ -89,10 +89,18 @@ def render_stack_dump(self, arch_sp: int) -> None: helper function for redering stack dump """ + # Loops over stack range (last 10 addresses) for idx in range(self.stack_num): addr = arch_sp + idx * self.pointersize - if (val := self.try_read_pointer(addr)[0]): - print(f"$sp+0x{idx*self.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{self.unpack(val):08x}", end="") + + ''' + @NOTE: Implemented new class arch_x8664 in order to bugfix issue with only dereferencing 32-bit pointers + on 64-bit emulation passes. + ''' + if (val := self.try_read_pointer(addr)[0]): # defined to be try_read_pointer(addr)[0] - dereferneces pointer + + # @TODO: Bug here where the values on the stack are being displayed in 32-bit format + print(f"RSP + 0x{idx*self.pointersize:02x}│ [0x{addr:08x}] —▸ 0x{self.unpack(val):08x}", end="") # try to dereference wether it's a pointer if (buf := self.try_read_pointer(addr))[0] is not None: @@ -180,8 +188,9 @@ def context_stack(self) -> None: display context stack dump """ + print(f"{self.ql.arch.regs.arch_sp:x}") self.render_stack_dump(self.ql.arch.regs.arch_sp) - + @Render.divider_printer("[ REGISTERS ]") def context_reg(self, saved_states: Mapping["str", int]) -> None: """ diff --git a/qiling/debugger/qdb/render/render_x8664.py b/qiling/debugger/qdb/render/render_x8664.py new file mode 100644 index 000000000..22c687d49 --- /dev/null +++ b/qiling/debugger/qdb/render/render_x8664.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# +# Cross Platform and Multi Architecture Advanced Binary Emulation Framework +# + + + +from .render import * +from ..arch import ArchX8664 + +class ContextRenderX8664(ContextRender, ArchX8664): + """ + Context render for X86_64 + """ + + def __init__(self, ql, predictor): + super().__init__(ql, predictor) + ArchX8664.__init__(self) + + @Render.divider_printer("[ REGISTERS ]") + def context_reg(self, saved_reg_dump): + cur_regs = self.dump_regs() + diff_reg = self.reg_diff(cur_regs, saved_reg_dump) + self.render_regs_dump(cur_regs, diff_reg=diff_reg) + print(color.GREEN, "EFLAGS: [CF: {flags[CF]}, PF: {flags[PF]}, AF: {flags[AF]}, ZF: {flags[ZF]}, SF: {flags[SF]}, OF: {flags[OF]}]".format(flags=self.get_flags(self.ql.arch.regs.eflags)), color.END, sep="") + + @Render.divider_printer("[ DISASM ]") + def context_asm(self): + lines = {} + past_list = [] + + cur_addr = self.cur_addr + while len(past_list) < 10: + line = self.disasm(cur_addr) + past_list.append(line) + cur_addr += line.size + + fd_list = [] + cur_insn = None + for each in past_list: + if each.address > self.cur_addr: + fd_list.append(each) + + elif each.address == self.cur_addr: + cur_insn = each + + """ + only forward and current instruction will be printed, + because we don't have a solid method to disasm backward instructions, + since it's x86 instruction length is variadic + """ + + lines.update({ + "current": cur_insn, + "forward": fd_list, + }) + + self.render_assembly(lines) diff --git a/qiling/debugger/qdb/utils.py b/qiling/debugger/qdb/utils.py index fdbde9d85..ce0aa6c60 100644 --- a/qiling/debugger/qdb/utils.py +++ b/qiling/debugger/qdb/utils.py @@ -16,6 +16,7 @@ from .render import ( ContextRenderX86, + ContextRenderX8664, ContextRenderARM, ContextRenderCORTEX_M, ContextRenderMIPS @@ -23,6 +24,7 @@ from .branch_predictor import ( BranchPredictorX86, + BranchPredictorX8664, BranchPredictorARM, BranchPredictorCORTEX_M, BranchPredictorMIPS, @@ -64,6 +66,7 @@ def setup_branch_predictor(ql): return { QL_ARCH.X86: BranchPredictorX86, + QL_ARCH.X8664: BranchPredictorX8664, QL_ARCH.ARM: BranchPredictorARM, QL_ARCH.CORTEX_M: BranchPredictorCORTEX_M, QL_ARCH.MIPS: BranchPredictorMIPS, @@ -76,6 +79,7 @@ def setup_context_render(ql, predictor): return { QL_ARCH.X86: ContextRenderX86, + QL_ARCH.X8664: ContextRenderX8664, QL_ARCH.ARM: ContextRenderARM, QL_ARCH.CORTEX_M: ContextRenderCORTEX_M, QL_ARCH.MIPS: ContextRenderMIPS,