@ -5,6 +5,7 @@ from collections import defaultdict
from enum import Enum
from typing import Dict
from typing import List
from typing import Protocol
from typing import Set
# Reverse lookup tables for debug printing
@ -40,6 +41,10 @@ from capstone.sparc import SPARC_INS_JMP
from capstone . sparc import SPARC_INS_JMPL
from capstone . x86 import X86_INS_JMP
from capstone . x86 import X86Op
from typing_extensions import override
import pwndbg . dbg
from pwndbg . dbg import DisassembledInstruction
# Architecture specific instructions that mutate the instruction pointer unconditionally
# The Capstone RET and CALL groups are also used to filter CALL and RET types when we check for unconditional jumps,
@ -115,22 +120,71 @@ CAPSTONE_ARCH_MAPPING_STRING = {
}
# Interface for enhanced instructions - there are two implementations defined in this file
class PwndbgInstruction ( Protocol ) :
cs_insn : CsInsn
address : int
size : int
mnemonic : str
op_str : str
groups : Set [ int ]
id : int
operands : List [ EnhancedOperand ]
asm_string : str
next : int
target : int
target_string : str | None
target_const : bool | None
condition : InstructionCondition
declare_conditional : bool | None
declare_is_unconditional_jump : bool
force_unconditional_jump_target : bool
annotation : str | None
annotation_padding : int | None
syscall : int | None
syscall_name : str | None
causes_branch_delay : bool
split : SplitType
emulated : bool
@property
def call_like ( self ) - > bool : . . .
@property
def jump_like ( self ) - > bool : . . .
@property
def has_jump_target ( self ) - > bool : . . .
@property
def is_conditional_jump ( self ) - > bool : . . .
@property
def is_unconditional_jump ( self ) - > bool : . . .
@property
def is_conditional_jump_taken ( self ) - > bool : . . .
@property
def bytes ( self ) - > bytearray : . . .
def op_find ( self , op_type : int , position : int ) - > EnhancedOperand : . . .
def op_count ( self , op_type : int ) - > int : . . .
# This class is used to provide context to an instructions execution, used both
# in the disasm view output (see 'pwndbg.color.disasm.instruction()'), as well as for
# Pwndbg commands like "nextcall" that need to know the instructions target to set breakpoints
class PwndbgInstruction :
def __init__ ( self , cs_insn : CsInsn | None ) - > None :
# The information in this class is backed by metadata from Capstone
class PwndbgInstructionImpl ( PwndbgInstruction ) :
def __init__ ( self , cs_insn : CsInsn ) - > None :
self . cs_insn : CsInsn = cs_insn
"""
The underlying Capstone instruction , if present .
Ideally , only the enhancement code will access the ' cs_insn ' property
The underlying Capstone instruction objec t.
Only the enhancement code should access the ' cs_insn ' property
"""
# None if Capstone don't support the arch being disassembled
# See "make_simple_instruction" function
if cs_insn is None :
return
self . address : int = cs_insn . address
self . size : int = cs_insn . size
@ -173,7 +227,7 @@ class PwndbgInstruction:
# in pwndbg.aglib.disasm.arch.py
# ***********
self . asm_string : str = " %-06s %s " % ( self . mnemonic , self . op_str )
self . asm_string : str = f" { self . mnemonic : <6 } { self . op_str } "
"""
The full string representing the instruction - ` mov rdi , rsp ` with appropriate padding .
@ -555,29 +609,100 @@ class EnhancedOperand:
return f " [ { info } ] "
def make_simple_instruction ( address : int ) - > PwndbgInstruction :
"""
Instantiate a PwndbgInstruction for an architecture that Capstone / pwndbg doesn ' t support (as defined in the CapstoneArch structure)
"""
ins = pwndbg . dbg . selected_inferior ( ) . disasm ( address )
asm = ins [ " asm " ] . split ( maxsplit = 1 )
# Represents a disassembled instruction
# Conforms to the PwndbgInstruction interface
class ManualPwndbgInstruction ( PwndbgInstruction ) :
def __init__ ( self , address : int ) - > None :
"""
This class provides an implementation of PwndbgInstruction for cases where the architecture
at hand is not supported by the Capstone disassembler . The backing information is sourced from
GDB / LLDB ' s built-in disassemblers.
Instances of this class do not go through the ' enhancement ' process due to lacking important information provided by Capstone .
As a result of this , some of the methods raise NotImplementedError , because if they are called it indicates a bug elsewhere in the codebase .
"""
ins : DisassembledInstruction = pwndbg . dbg . selected_inferior ( ) . disasm ( address )
asm = ins [ " asm " ] . split ( maxsplit = 1 )
# The enhancement code assumes this value exists.
# However, a ManualPwndbgInstruction should never be used in the enhancement code.
self . cs_insn : CsInsn = None
self . address = address
self . size = ins [ " length " ]
self . mnemonic = asm [ 0 ] . strip ( )
self . op_str = asm [ 1 ] . strip ( ) if len ( asm ) > 1 else " "
self . groups = set ( )
# Set Capstone ID to -1
self . id = - 1
self . operands = [ ]
pwn_ins = PwndbgInstruction ( None )
pwn_ins . address = address
pwn_ins . size = ins [ " length " ]
self . asm_string = f " { self . mnemonic : <6 } { self . op_str } "
pwn_ins . mnemonic = asm [ 0 ] . strip ( )
pwn_ins . op_str = asm [ 1 ] . strip ( ) if len ( asm ) > 1 else " "
self . next = address + self . size
self . target = self . next
self . target_string = None
self . target_const = None
pwn_ins . next = address + pwn_ins . size
pwn_ins . target = pwn_ins . next
self . condition = InstructionCondition . UNDETERMINED
pwn_ins . groups = [ ]
self . declare_conditional = None
self . declare_is_unconditional_jump = False
self . force_unconditional_jump_target = False
pwn_ins . condition = InstructionCondition . UNDETERMINED
self . annotation = None
pwn_ins . annotation = None
self . annotation_padding = None
pwn_ins . operands = [ ]
self . syscall = None
self . syscall_name = None
return pwn_ins
self . causes_branch_delay = False
self . split = SplitType . NO_SPLIT
self . emulated = False
@property
def bytes ( self ) - > bytearray :
# GDB simply doesn't provide us with the raw bytes.
# However, it is important that this returns a valid bytearray,
# since the disasm code indexes this for nearpc-num-opcode-bytes.
return bytearray ( )
@property
def call_like ( self ) - > bool :
return False
@property
def jump_like ( self ) - > bool :
return False
@property
def has_jump_target ( self ) - > bool :
return False
@property
def is_conditional_jump ( self ) - > bool :
return False
@property
def is_unconditional_jump ( self ) - > bool :
return False
@property
def is_conditional_jump_taken ( self ) - > bool :
return False
@override
def op_find ( self , op_type : int , position : int ) - > EnhancedOperand :
# raise NotImplementedError, because if this is called it indicates a bug elsewhere in the codebase.
# ManualPwndbgInstruction should not go through the enhancement process, where this would be called.
raise NotImplementedError
@override
def op_count ( self , op_type : int ) - > int :
raise NotImplementedError