Call like instructions (#2261)

* Move syscall number evaluation into instruction.py. This allows us to determine and display future syscalls

* Move string manipulation to color.disasm.py

* lint

* fix padding

* Fix x86 syscall

* disable debug mode

* @override decorator added to methods

* comments

* lint

* Fix x86/x86_64 edge cases with syscall register reading, and add test for emulation off for syscalls

* Tests depend on width of context banner

* Fix strange rebasing error

* Call like instructions

* Add IRET to jump groups, and remove multiple places in codebase where jumps groups are defined (non uniformly)

* remove duplicate test (rebase stuff)

* lint
pull/2230/head
OBarronCS 1 year ago committed by GitHub
parent 41f335bec8
commit 83cc8c57cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -9,7 +9,6 @@ from typing import List
from typing import Tuple from typing import Tuple
import gdb import gdb
from capstone import CS_GRP_CALL
from capstone import CS_GRP_INT from capstone import CS_GRP_INT
import pwndbg.chain import pwndbg.chain
@ -73,7 +72,7 @@ def get(instruction: PwndbgInstruction) -> List[Tuple[pwndbg.lib.functions.Argum
if instruction.address != pwndbg.gdblib.regs.pc: if instruction.address != pwndbg.gdblib.regs.pc:
return [] return []
if CS_GRP_CALL in instruction.groups: if instruction.call_like:
try: try:
abi = pwndbg.lib.abi.ABI.default() abi = pwndbg.lib.abi.ABI.default()
except KeyError: except KeyError:

@ -2,8 +2,6 @@ from __future__ import annotations
from typing import List from typing import List
import capstone
import pwndbg.chain import pwndbg.chain
import pwndbg.color.context as C import pwndbg.color.context as C
from pwndbg.color import ColorConfig from pwndbg.color import ColorConfig
@ -11,11 +9,10 @@ from pwndbg.color import ColorParamSpec
from pwndbg.color import ljust_colored from pwndbg.color import ljust_colored
from pwndbg.color import strip from pwndbg.color import strip
from pwndbg.color.message import on from pwndbg.color.message import on
from pwndbg.gdblib.disasm.instruction import ALL_JUMP_GROUPS
from pwndbg.gdblib.disasm.instruction import InstructionCondition from pwndbg.gdblib.disasm.instruction import InstructionCondition
from pwndbg.gdblib.disasm.instruction import PwndbgInstruction from pwndbg.gdblib.disasm.instruction import PwndbgInstruction
capstone_branch_groups = {capstone.CS_GRP_CALL, capstone.CS_GRP_JUMP}
c = ColorConfig( c = ColorConfig(
"disasm", "disasm",
[ [
@ -33,7 +30,7 @@ def one_instruction(ins: PwndbgInstruction) -> str:
if pwndbg.config.highlight_pc and ins.address == pwndbg.gdblib.regs.pc: if pwndbg.config.highlight_pc and ins.address == pwndbg.gdblib.regs.pc:
asm = C.highlight(asm) asm = C.highlight(asm)
is_call_or_jump = ins.groups_set & capstone_branch_groups is_call_or_jump = ins.groups_set & ALL_JUMP_GROUPS
# Style the instruction mnemonic if it's a call/jump instruction. # Style the instruction mnemonic if it's a call/jump instruction.
if is_call_or_jump: if is_call_or_jump:

@ -676,8 +676,6 @@ class Emulator:
debug(DEBUG_MEM_READ, "uc.mem_read(*%r, **%r)", (a, kw)) debug(DEBUG_MEM_READ, "uc.mem_read(*%r, **%r)", (a, kw))
return self.uc.mem_read(*a, **kw) return self.uc.mem_read(*a, **kw)
jump_types = {C.CS_GRP_CALL, C.CS_GRP_JUMP, C.CS_GRP_RET}
def until_jump(self, pc=None): def until_jump(self, pc=None):
""" """
Emulates instructions starting at the specified address until the Emulates instructions starting at the specified address until the
@ -739,7 +737,7 @@ class Emulator:
def until_call(self, pc=None): def until_call(self, pc=None):
addr, target = self.until_jump(pc) addr, target = self.until_jump(pc)
while target and C.CS_GRP_CALL not in pwndbg.gdblib.disasm.one_raw(addr).groups: while target and not pwndbg.gdblib.disasm.one_raw(addr).call_like:
addr, target = self.until_jump(target) addr, target = self.until_jump(target)
return addr, target return addr, target

@ -17,6 +17,7 @@ import pwndbg.gdblib.typeinfo
import pwndbg.gdblib.vmmap import pwndbg.gdblib.vmmap
import pwndbg.lib.config import pwndbg.lib.config
from pwndbg.emu.emulator import Emulator from pwndbg.emu.emulator import Emulator
from pwndbg.gdblib.disasm.instruction import FORWARD_JUMP_GROUP
from pwndbg.gdblib.disasm.instruction import EnhancedOperand from pwndbg.gdblib.disasm.instruction import EnhancedOperand
from pwndbg.gdblib.disasm.instruction import InstructionCondition from pwndbg.gdblib.disasm.instruction import InstructionCondition
from pwndbg.gdblib.disasm.instruction import PwndbgInstruction from pwndbg.gdblib.disasm.instruction import PwndbgInstruction
@ -222,7 +223,7 @@ class DisassemblyAssistant:
# Disable emulation after CALL instructions. We do it after enhancement, as we can use emulation # Disable emulation after CALL instructions. We do it after enhancement, as we can use emulation
# to determine the call's target address. # to determine the call's target address.
if jump_emu and CS_GRP_CALL in set(instruction.groups): if jump_emu and instruction.call_like:
jump_emu.valid = False jump_emu.valid = False
jump_emu = None jump_emu = None
emu = None emu = None
@ -600,10 +601,7 @@ class DisassemblyAssistant:
# Use emulator to determine the next address: # Use emulator to determine the next address:
# 1. Only use it to determine non-call's (`nexti` should step over calls) # 1. Only use it to determine non-call's (`nexti` should step over calls)
# 2. Make sure we haven't manually set .condition to False (which should override the emulators prediction) # 2. Make sure we haven't manually set .condition to False (which should override the emulators prediction)
if ( if not instruction.call_like and instruction.condition != InstructionCondition.FALSE:
CS_GRP_CALL not in instruction.groups_set
and instruction.condition != InstructionCondition.FALSE
):
next_addr = jump_emu.pc next_addr = jump_emu.pc
# All else fails, take the next instruction in memory # All else fails, take the next instruction in memory
@ -641,10 +639,10 @@ class DisassemblyAssistant:
"call" specifies if we allow this to resolve call instruction targets "call" specifies if we allow this to resolve call instruction targets
""" """
if CS_GRP_CALL in instruction.groups: if instruction.call_like:
if not call: if not call:
return None return None
elif CS_GRP_JUMP not in instruction.groups: elif not bool(instruction.groups_set & FORWARD_JUMP_GROUP):
return None return None
addr = None addr = None

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import typing import typing
from collections import defaultdict
from enum import Enum from enum import Enum
from typing import Dict from typing import Dict
from typing import List from typing import List
@ -28,6 +29,7 @@ from capstone.arm64 import ARM64_INS_BLR
from capstone.arm64 import ARM64_INS_BR from capstone.arm64 import ARM64_INS_BR
from capstone.mips import MIPS_INS_B from capstone.mips import MIPS_INS_B
from capstone.mips import MIPS_INS_BAL from capstone.mips import MIPS_INS_BAL
from capstone.mips import MIPS_INS_BLTZAL
from capstone.mips import MIPS_INS_J from capstone.mips import MIPS_INS_J
from capstone.mips import MIPS_INS_JAL from capstone.mips import MIPS_INS_JAL
from capstone.mips import MIPS_INS_JALR from capstone.mips import MIPS_INS_JALR
@ -64,13 +66,24 @@ UNCONDITIONAL_JUMP_INSTRUCTIONS: Dict[int, Set[int]] = {
CS_ARCH_PPC: {PPC_INS_B, PPC_INS_BA, PPC_INS_BL, PPC_INS_BLA}, CS_ARCH_PPC: {PPC_INS_B, PPC_INS_BA, PPC_INS_BL, PPC_INS_BLA},
} }
BRANCH_AND_LINK_INSTRUCTIONS: Dict[int, Set[int]] = defaultdict(set)
BRANCH_AND_LINK_INSTRUCTIONS[CS_ARCH_MIPS] = {
MIPS_INS_BAL,
MIPS_INS_BLTZAL,
MIPS_INS_JAL,
MIPS_INS_JALR,
}
# Everything that is a CALL or a RET is a unconditional jump # Everything that is a CALL or a RET is a unconditional jump
GENERIC_UNCONDITIONAL_JUMP_GROUPS = {CS_GRP_CALL, CS_GRP_RET} GENERIC_UNCONDITIONAL_JUMP_GROUPS = {CS_GRP_CALL, CS_GRP_RET, CS_GRP_IRET}
# All branch-like instructions - jumps thats are non-call and non-ret - should have one of these two groups in Capstone # All branch-like instructions - jumps thats are non-call and non-ret - should have one of these two groups in Capstone
GENERIC_JUMP_GROUPS = {CS_GRP_JUMP, CS_GRP_BRANCH_RELATIVE} GENERIC_JUMP_GROUPS = {CS_GRP_JUMP, CS_GRP_BRANCH_RELATIVE}
# All Capstone jumps should have at least one of these groups # All Capstone jumps should have at least one of these groups
ALL_JUMP_GROUPS = GENERIC_JUMP_GROUPS | GENERIC_UNCONDITIONAL_JUMP_GROUPS ALL_JUMP_GROUPS = GENERIC_JUMP_GROUPS | GENERIC_UNCONDITIONAL_JUMP_GROUPS
# All non-ret jumps
FORWARD_JUMP_GROUP = {CS_GRP_CALL} | GENERIC_JUMP_GROUPS
class InstructionCondition(Enum): class InstructionCondition(Enum):
# Conditional instruction, and action is taken # Conditional instruction, and action is taken
@ -244,6 +257,18 @@ class PwndbgInstruction:
If the enhancement successfully used emulation for this instruction If the enhancement successfully used emulation for this instruction
""" """
@property
def call_like(self) -> bool:
"""
True if this is a call-like instruction, meaning either it's a CALL or a branch and link.
Checking for the CS_GRP_CALL is insufficient, as there are many "branch and link" instructions that are not labeled as a call
"""
return (
CS_GRP_CALL in self.groups_set
or self.id in BRANCH_AND_LINK_INSTRUCTIONS[self.cs_insn._cs.arch]
)
@property @property
def can_change_instruction_pointer(self) -> bool: def can_change_instruction_pointer(self) -> bool:
""" """

@ -16,8 +16,7 @@ import pwndbg.gdblib.events
import pwndbg.gdblib.proc import pwndbg.gdblib.proc
import pwndbg.gdblib.regs import pwndbg.gdblib.regs
from pwndbg.color import message from pwndbg.color import message
from pwndbg.gdblib.disasm.instruction import ALL_JUMP_GROUPS
jumps = {capstone.CS_GRP_CALL, capstone.CS_GRP_JUMP, capstone.CS_GRP_RET, capstone.CS_GRP_IRET}
interrupts = {capstone.CS_GRP_INT} interrupts = {capstone.CS_GRP_INT}
@ -45,10 +44,9 @@ def next_int(address=None):
ins = pwndbg.gdblib.disasm.one(address) ins = pwndbg.gdblib.disasm.one(address)
while ins: while ins:
ins_groups = set(ins.groups) if ins.groups_set & ALL_JUMP_GROUPS:
if ins_groups & jumps:
return None return None
elif ins_groups & interrupts: elif ins.groups_set & interrupts:
return ins return ins
ins = pwndbg.gdblib.disasm.one(ins.next) ins = pwndbg.gdblib.disasm.one(ins.next)
@ -64,7 +62,7 @@ def next_branch(address=None):
ins = pwndbg.gdblib.disasm.one(address) ins = pwndbg.gdblib.disasm.one(address)
while ins: while ins:
if set(ins.groups) & jumps: if ins.groups_set & ALL_JUMP_GROUPS:
return ins return ins
ins = pwndbg.gdblib.disasm.one(ins.next) ins = pwndbg.gdblib.disasm.one(ins.next)
@ -104,7 +102,7 @@ def next_matching_until_branch(address=None, mnemonic=None, op_str=None):
if mnemonic_match and op_str_match: if mnemonic_match and op_str_match:
return ins return ins
if set(ins.groups) & jumps: if ins.groups_set & ALL_JUMP_GROUPS:
# No matching instruction until the next branch, and we're # No matching instruction until the next branch, and we're
# not trying to match the branch instruction itself. # not trying to match the branch instruction itself.
return None return None
@ -145,7 +143,7 @@ def break_next_call(symbol_regex=None):
break break
# continue if not a call # continue if not a call
if capstone.CS_GRP_CALL not in ins.groups: if not ins.call_like:
continue continue
# return call if we: # return call if we:

Loading…
Cancel
Save