Skip to content

Commit eb88970

Browse files
committed
improve abstraction
1 parent abfaa2a commit eb88970

9 files changed

+182
-56
lines changed

ida_plugin/qsynthesis_plugin.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def init(self) -> int:
2727
addon_info.name = self.wanted_name
2828
addon_info.producer = "Quarkslab"
2929
addon_info.version = qsynthesis.__version__
30-
addon_info.url = "https://gitlab.qb/synthesis/qsynthesis"
30+
addon_info.url = "https://github.com/quarkslab/qsynthesis"
3131
addon_info.freeform = "Copyright (c) 2020 - All Rights Reserved"
3232
ida_kernwin.register_addon(addon_info)
3333
self.view = None

qsynthesis/plugin/actions.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from qsynthesis.plugin.dependencies import ida_kernwin, TRITON_ENABLED
2-
from qtracedb.trace import Trace
1+
from qsynthesis.plugin.dependencies import ida_kernwin, TRITON_ENABLED, Trace
32

43

54
class SynthetizerViewHook(ida_kernwin.action_handler_t):

qsynthesis/plugin/arch.py

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
from typing import List, Optional
2+
3+
import capstone
4+
from capstone import x86_const, arm_const, arm64_const, CS_AC_READ, CS_AC_WRITE
5+
from enum import IntEnum
6+
from collections import namedtuple
7+
8+
Reg = namedtuple("Reg", "name")
9+
10+
11+
class Opnd:
12+
def __init__(self, cs_op, arch):
13+
self._cs_op = cs_op
14+
self._arch = arch
15+
16+
@property
17+
def type(self):
18+
return self._cs_op.type
19+
20+
def is_read(self) -> bool:
21+
return bool(self._cs_op.access & CS_AC_READ)
22+
23+
def is_written(self) -> bool:
24+
return bool(self._cs_op.access & CS_AC_WRITE)
25+
26+
def is_register(self) -> bool:
27+
return self.type == self._arch.optypes.REG
28+
29+
def is_memory(self) -> bool:
30+
return self.type == self._arch.optypes.MEM
31+
32+
33+
class Instr:
34+
def __init__(self, cs_ins, arch):
35+
self._cs_ins = cs_ins
36+
self._arch = arch
37+
38+
@property
39+
def bytes(self):
40+
return bytes(self._cs_ins.bytes)
41+
42+
def __str__(self):
43+
return self._cs_ins.mnemonic + " " + self._cs_ins.op_str
44+
45+
@property
46+
def operands(self):
47+
return [Opnd(x, self._arch) for x in self._cs_ins.operands]
48+
49+
50+
class Arch:
51+
_CSD = None
52+
53+
def disasm(cls, asm: bytes, addr: int) -> List[Instr]:
54+
cls._CSD.detail = True
55+
return [Instr(x, cls) for x in cls._CSD.disasm(asm, addr)]
56+
57+
def disasm_one(cls, asm: bytes, addr: int) -> Instr:
58+
r = cls.disasm(asm, addr)
59+
return r[0] if r else None
60+
61+
62+
63+
class _ArchX86(Arch):
64+
NAME = "x86"
65+
INS_PTR = Reg('eip')
66+
STK_PTR = Reg('esp')
67+
_CSD = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_32)
68+
nop_instruction = b"\x90"
69+
70+
class optypes(IntEnum):
71+
INVALID = x86_const.X86_OP_INVALID
72+
IMM = x86_const.X86_OP_IMM
73+
REG = x86_const.X86_OP_REG
74+
MEM = x86_const.X86_OP_MEM
75+
76+
77+
class _ArchX64(_ArchX86):
78+
NAME = "x86_64"
79+
INS_PTR = Reg('rip')
80+
STK_PTR = Reg('rsp')
81+
_CSD = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
82+
83+
84+
85+
class _ArchARM(Arch):
86+
NAME = "ARM"
87+
INS_PTR = Reg('pc')
88+
STK_PTR = Reg('sp')
89+
_CSD = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM)
90+
nop_instruction = b"\x00\xf0\x20\xe3"
91+
92+
class optypes(IntEnum):
93+
INVALID = arm_const.ARM_OP_INVALID
94+
REG = arm_const.ARM_OP_REG
95+
IMM = arm_const.ARM_OP_IMM
96+
MEM = arm_const.ARM_OP_MEM
97+
FP = arm_const.ARM_OP_FP
98+
CIMM = arm_const.ARM_OP_CIMM
99+
PIMM = arm_const.ARM_OP_PIMM
100+
SETEND = arm_const.ARM_OP_SETEND
101+
SYSREG = arm_const.ARM_OP_SYSREG
102+
103+
104+
class _ArchARM64(Arch):
105+
NAME = "AARCH64"
106+
INS_PTR = Reg('x28')
107+
STK_PTR = Reg('sp')
108+
_CSD = capstone.Cs(capstone.CS_ARCH_ARM64, capstone.CS_MODE_ARM)
109+
nop_instruction = b"\x1f\x20\x03\xd5"
110+
111+
class optypes(IntEnum):
112+
INVALID = arm64_const.ARM64_OP_INVALID
113+
REG = arm64_const.ARM64_OP_REG
114+
IMM = arm64_const.ARM64_OP_IMM
115+
MEM = arm64_const.ARM64_OP_MEM
116+
FP = arm64_const.ARM64_OP_FP
117+
CIMM = arm64_const.ARM64_OP_CIMM
118+
REG_MRS = arm64_const.ARM64_OP_REG_MRS
119+
REG_MSR = arm64_const.ARM64_OP_REG_MSR
120+
PSTATE = arm64_const.ARM64_OP_PSTATE
121+
SYS = arm64_const.ARM64_OP_SYS
122+
PREFETCH = arm64_const.ARM64_OP_PREFETCH
123+
BARRIER = arm64_const.ARM64_OP_BARRIER
124+
125+
126+
ArchX86 = _ArchX86()
127+
ArchX64 = _ArchX64()
128+
ArchARM = _ArchARM()
129+
ArchARM64 = _ArchARM64()
130+
131+
132+
class ArchsManager:
133+
@staticmethod
134+
def get_supported_regs(arch: Arch) -> List[Reg]:
135+
if isinstance(arch, _ArchX64):
136+
return [Reg('RAX'), Reg('RBX'), Reg('RCX'), Reg('RDX'), Reg('RDI'), Reg('RSI'), Reg('RBP'), Reg('RSP'),
137+
Reg('RIP'), Reg('EFLAGS'), Reg('R8'), Reg('R9'), Reg('R10'), Reg('R11'), Reg('R12'), Reg('R13'),
138+
Reg('R14'), Reg('R15')]
139+
elif isinstance(arch, _ArchX86):
140+
return [Reg('EAX'), Reg('EBX'), Reg('ECX'), Reg('EDX'), Reg('EDI'), Reg('ESI'), Reg('EBP'), Reg('ESP'), Reg('EIP'), Reg('EFLAGS')]
141+
elif isinstance(arch, _ArchARM):
142+
return [Reg('R0'), Reg('R1'), Reg('R2'), Reg('R3'), Reg('R4'), Reg('R5'), Reg('R6'), Reg('R7'), Reg('R8'),
143+
Reg('R9'), Reg('R10'), Reg('R11'), Reg('R12'), Reg('R13'), Reg('R14'), Reg('R15'), Reg('CPSR')]
144+
elif isinstance(arch, _ArchARM64):
145+
return [Reg('X0'), Reg('X1'), Reg('X2'), Reg('X3'), Reg('X4'), Reg('X5'), Reg('X6'), Reg('X7'), Reg('X8'),
146+
Reg('X9'), Reg('X10'), Reg('X11'), Reg('X12'), Reg('X13'), Reg('X14'), Reg('X15'), Reg('X16'),
147+
Reg('X17'), Reg('X18'), Reg('X19'), Reg('X20'), Reg('X21'), Reg('X22'), Reg('R23'), Reg('X24'),
148+
Reg('X25'), Reg('X26'), Reg('X27'), Reg('X28'), Reg('X29'), Reg('X30')]
149+
else:
150+
assert False

qsynthesis/plugin/ast_viewer.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
# built-in modules
22
from typing import List
33

4-
# third-party
5-
from qtracedb.archs.arch import Instr
6-
74
# qsynthesis modules
8-
from qsynthesis.plugin.dependencies import ida_graph
5+
from qsynthesis.plugin.dependencies import ida_graph, Instr
96
from qsynthesis.tritonast import TritonAst
107

118

qsynthesis/plugin/dependencies.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,20 @@ class PluginForm:
4444

4545
try:
4646
import qtracedb
47+
from qtracedb import DatabaseManager
48+
from qtracedb.trace import Trace, InstrCtx
49+
from qtracedb.archs.arch import Instr, Arch
50+
from qtracedb.manager import ArchsManager
51+
from qtracedb.archs.x86 import ArchX86, ArchX64
52+
from qtracedb.archs.arm import ArchARM
53+
from qtracedb.archs.arm64 import ArchARM64
4754
QTRACEDB_ENABLED = True
48-
except ImportError:
55+
except ImportError as e:
4956
QTRACEDB_ENABLED = False
57+
from .arch import Arch, ArchX64, ArchARM, ArchARM64, ArchX86, Instr, ArchsManager
58+
# Set dummy vars
59+
DatabaseManager, Trace, InstrCtx = None, None, None
60+
5061

5162
try:
5263
import triton

qsynthesis/plugin/popup_actions.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def set_text_widget(self, ea: Addr) -> None:
3434
the Qsynthesis view.
3535
3636
:param ea: address to set
37-
:return: None
37+
:return: Nones
3838
"""
3939
self.widget.from_line.setText(f"{ea:#x}")
4040

@@ -138,8 +138,8 @@ def __init__(self, widget: 'SynthesizerView'):
138138
def activate(self, ctx) -> bool:
139139
"""
140140
Upon clicking (or shortcut key typed) retrieve the current operand
141-
with qtracedb Instruction object and set various widget variable
142-
to be able to retrieve the information later on.
141+
object and set various widget variable to be able to retrieve the
142+
information later on.
143143
144144
:return: True if the operand is value and has successfully been retrieved
145145
"""

qsynthesis/plugin/processor.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,10 @@
77

88
# third-party modules
99
from triton import ARCH
10-
from qtracedb.archs.arch import Arch
11-
from qtracedb.archs.x86 import ArchX86, ArchX64
12-
from qtracedb.archs.arm import ArchARM
13-
from qtracedb.archs.arm64 import ArchARM64
1410

1511
# qsynthesis modules
1612
from qsynthesis.plugin.dependencies import ida_idp, ida_idaapi
13+
from qsynthesis.plugin.dependencies import ArchX86, ArchX64, ArchARM, ArchARM64
1714

1815

1916
class ProcessorType(IntEnum):
@@ -61,7 +58,7 @@ def processor_to_triton_arch() -> ARCH:
6158
assert False
6259

6360

64-
def processor_to_qtracedb_arch() -> Arch:
61+
def processor_to_arch():
6562
"""
6663
Get the current IDB processor as a Qtrace-DB Arch object
6764

qsynthesis/plugin/view.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,16 @@
55

66
# third-party modules
77
from PyQt5 import QtWidgets, QtCore, QtGui # provided by IDA
8-
from qtracedb import DatabaseManager
9-
from qtracedb.trace import Trace, InstrCtx
10-
from qtracedb.archs.arch import Instr
11-
from qtracedb.archs import ArchsManager
128

139
# qsynthesis modules
10+
from qsynthesis.plugin.dependencies import DatabaseManager, Trace, InstrCtx, Instr, Arch, ArchsManager
1411
from qsynthesis.plugin.dependencies import ida_kernwin, IDA_ENABLED, QTRACEIDA_ENABLED, QTRACEDB_ENABLED
15-
from qsynthesis.plugin.dependencies import ida_bytes, ida_nalt, ida_ua, ida_funcs, ida_gdl, ida_loader
12+
from qsynthesis.plugin.dependencies import ida_bytes, ida_nalt, ida_ua, ida_funcs, ida_gdl, ida_loader, ida_lines
1613
from qsynthesis.tables import InputOutputOracleLevelDB, InputOutputOracleREST
1714
from qsynthesis.algorithms import TopDownSynthesizer, PlaceHolderSynthesizer
1815
from qsynthesis.utils.symexec import SimpleSymExec
1916
from qsynthesis.tritonast import ReassemblyError
20-
from qsynthesis.plugin.processor import processor_to_triton_arch, Arch, Processor, ProcessorType, processor_to_qtracedb_arch
17+
from qsynthesis.plugin.processor import processor_to_triton_arch, Processor, ProcessorType, processor_to_arch
2118
from qsynthesis.plugin.ast_viewer import AstViewer, BasicBlockViewer
2219
from qsynthesis.plugin.popup_actions import SynthetizeFromHere, SynthetizeToHere, SynthetizeOperand
2320
from qsynthesis.plugin.ui.synthesis_ui import Ui_synthesis_view
@@ -162,7 +159,7 @@ def __init__(self, qtrace: Optional['QtraceIDA']):
162159
# If working on its own
163160
self._dbm = None
164161
self._trace = None
165-
self._arch = processor_to_qtracedb_arch() # By default initialize it with current architecture
162+
self._arch = processor_to_arch() # By default initialize it with current architecture
166163

167164
# Expresssion highlighted
168165
self.highlighted_addr = {} # addr -> backed_color
@@ -852,7 +849,7 @@ def reassemble_clicked(self) -> None:
852849
try:
853850
if patch_fun:
854851
addrs = self.get_dependency_addresses()
855-
asm_bytes = self.synth_ast.reassemble(dst_reg, self.arch)
852+
asm_bytes = self.synth_ast.reassemble(dst_reg, self.arch.NAME)
856853
if snap: # Create a snapshot of the database
857854
ss = ida_loader.snapshot_t()
858855
ss.desc = f"Reassembly of {dst_reg} at {self.stop_addr:#x}"
@@ -863,7 +860,8 @@ def reassemble_clicked(self) -> None:
863860
self.patch_reassembly(addrs, asm_bytes)
864861
else:
865862
# Reassemble to instruction and show it in a View
866-
insts = self.synth_ast.reassemble_to_insts(dst_reg, self.arch)
863+
raw_insts = self.synth_ast.reassemble(dst_reg, self.arch.NAME)
864+
insts = self.arch.disasm(raw_insts, 0x0)
867865
bb_viewer = BasicBlockViewer("Reassembly", insts)
868866
bb_viewer.Show()
869867
except ReassemblyError as e:
@@ -1076,11 +1074,13 @@ def selected_expr_register(self) -> Tuple[bool, Optional[str]]:
10761074
elif self.target_type == TargetType.MEMORY:
10771075
return False, None
10781076
elif self.target_type == TargetType.OPERAND:
1077+
10791078
opc = ida_bytes.get_bytes(self.stop_addr, ida_bytes.get_item_size(self.stop_addr))
10801079
inst = self.arch.disasm_one(opc, self.stop_addr)
10811080
op = inst.operands[self.op_num]
10821081
if op.is_register():
1083-
return True, op.register.name
1082+
op_name = ida_lines.tag_remove(ida_ua.print_operand(self.stop_addr, self.op_num))
1083+
return True, op_name # op.register.name
10841084
else:
10851085
return False, None
10861086

qsynthesis/tritonast.py

+2-30
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@
88
# Third-party modules
99
from triton import TritonContext, AST_NODE, SYMBOLIC, ARCH
1010

11-
# Qtrace imports
12-
from qtracedb.archs.manager import ArchsManager
13-
from qtracedb.archs.arch import Arch, Instr
14-
1511
# QSynthesis imports
1612
from qsynthesis.types import AstNode, List, Tuple, Generator, Dict, Union, Optional, AstType, SymVarMap, \
1713
SymbolicVariable, Char, IOVector, Input, Output
@@ -679,7 +675,7 @@ def visit_replacement(self, update: bool = True) -> Generator['TritonAst', 'Trit
679675
self._inplace_replace(new_expr_to_send)
680676
return
681677

682-
def reassemble(self, dst_reg: str, target_arch: Optional[Union['Arch', str]] = None) -> bytes:
678+
def reassemble(self, dst_reg: str, target_arch: Optional[str] = None) -> bytes:
683679
"""
684680
Reassemble the TritonAst in assembly. ``dst_reg`` is the destination register of the
685681
result of the computation of the AST. Parameter ``target_arch`` is either a QtraceDB
@@ -717,10 +713,9 @@ def my_asm_binary(arybo_expr, dst_regs, inps, target):
717713
m = {ARCH.X86: "x86", ARCH.X86_64: "x86_64", ARCH.ARM32: "arm", ARCH.AARCH64: "aarch64"}
718714
arch_name = m[self.ctx.getArchitecture()]
719715
else:
720-
arch_name = target_arch if isinstance(target_arch, str) else target_arch.NAME.lower()
716+
arch_name = target_arch.lower()
721717
arybo_expr = tritonast2arybo(self.expr, use_exprs=True, use_esf=False)
722718
inps = {x.getName(): (x.getAlias(), x.getBitSize()) for x in self.symvars}
723-
#return asm_binary(arybo_expr, (dst_reg, self.size), inps, f"{arch_name}-unknown-unknwon")
724719
return my_asm_binary(arybo_expr, (dst_reg, self.size), inps, f"{arch_name}-unknown-unknwon")
725720
else:
726721
raise ReassemblyError("Can only reassemble if variable are registers (at the moment)")
@@ -731,29 +726,6 @@ def my_asm_binary(arybo_expr, dst_regs, inps, target):
731726
except Exception as e:
732727
raise ReassemblyError(f"Something went wrong during reassembly: {e}")
733728

734-
def reassemble_to_insts(self, dst_reg: str, target_arch: Optional[Union[Arch, str]] = None) -> List[Instr]:
735-
"""
736-
Similar to :meth:`TritonAst.reassemble` but returns Instruction object for each instruction
737-
reassembled.
738-
739-
:param dst_reg: destination register as lowercase string
740-
:param target_arch: target architecture in which to reassemble the AST
741-
:returns: bytes of the AST reassembled in the given architecture
742-
:raises: ReassemblyError
743-
744-
.. warning:: This method requires the ``arybo`` library that can be installed with
745-
(pip3 install arybo).
746-
"""
747-
748-
# Note: let all exception being raised above if any
749-
asm = self.reassemble(dst_reg, target_arch)
750-
if target_arch is None:
751-
m = {ARCH.X86: "x86", ARCH.X86_64: "x86_64", ARCH.ARM32: "arm", ARCH.AARCH64: "aarch64"}
752-
arch = ArchsManager.get_arch(m[self.ctx.getArchitecture()])
753-
else:
754-
arch = ArchsManager.get_arch(target_arch) if isinstance(target_arch, str) else target_arch
755-
return arch.disasm(asm, 0x0)
756-
757729
def make_graph(self) -> 'Graph':
758730
"""
759731
Generate a graph object representing the AST

0 commit comments

Comments
 (0)