Add x86_64 debug support

This commit is contained in:
joshj
2022-08-31 17:10:18 +01:00
parent 5d7a8f0c01
commit 34d49c57d9
9 changed files with 273 additions and 4 deletions
+1
View File
@@ -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
+66
View File
@@ -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
}
@@ -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
@@ -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
+2 -1
View File
@@ -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,
+1
View File
@@ -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
+12 -3
View File
@@ -89,10 +89,18 @@ class Render:
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 @@ class ContextRender(Context, Render):
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:
"""
@@ -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)
+4
View File
@@ -16,6 +16,7 @@ from .misc import read_int
from .render import (
ContextRenderX86,
ContextRenderX8664,
ContextRenderARM,
ContextRenderCORTEX_M,
ContextRenderMIPS
@@ -23,6 +24,7 @@ from .render import (
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,