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
This commit is contained in:
OBarronCS 2024-06-26 16:08:40 -07:00 committed by GitHub
parent 41f335bec8
commit 83cc8c57cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 41 additions and 26 deletions

View File

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

View File

@ -2,8 +2,6 @@ from __future__ import annotations
from typing import List
import capstone
import pwndbg.chain
import pwndbg.color.context as C
from pwndbg.color import ColorConfig
@ -11,11 +9,10 @@ from pwndbg.color import ColorParamSpec
from pwndbg.color import ljust_colored
from pwndbg.color import strip
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 PwndbgInstruction
capstone_branch_groups = {capstone.CS_GRP_CALL, capstone.CS_GRP_JUMP}
c = ColorConfig(
"disasm",
[
@ -33,7 +30,7 @@ def one_instruction(ins: PwndbgInstruction) -> str:
if pwndbg.config.highlight_pc and ins.address == pwndbg.gdblib.regs.pc:
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.
if is_call_or_jump:

View File

@ -676,8 +676,6 @@ class Emulator:
debug(DEBUG_MEM_READ, "uc.mem_read(*%r, **%r)", (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):
"""
Emulates instructions starting at the specified address until the
@ -739,7 +737,7 @@ class Emulator:
def until_call(self, pc=None):
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)
return addr, target

View File

@ -17,6 +17,7 @@ import pwndbg.gdblib.typeinfo
import pwndbg.gdblib.vmmap
import pwndbg.lib.config
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 InstructionCondition
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
# 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 = None
emu = None
@ -600,10 +601,7 @@ class DisassemblyAssistant:
# Use emulator to determine the next address:
# 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)
if (
CS_GRP_CALL not in instruction.groups_set
and instruction.condition != InstructionCondition.FALSE
):
if not instruction.call_like and instruction.condition != InstructionCondition.FALSE:
next_addr = jump_emu.pc
# 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
"""
if CS_GRP_CALL in instruction.groups:
if instruction.call_like:
if not call:
return None
elif CS_GRP_JUMP not in instruction.groups:
elif not bool(instruction.groups_set & FORWARD_JUMP_GROUP):
return None
addr = None

View File

@ -1,6 +1,7 @@
from __future__ import annotations
import typing
from collections import defaultdict
from enum import Enum
from typing import Dict
from typing import List
@ -28,6 +29,7 @@ from capstone.arm64 import ARM64_INS_BLR
from capstone.arm64 import ARM64_INS_BR
from capstone.mips import MIPS_INS_B
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_JAL
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},
}
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
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
GENERIC_JUMP_GROUPS = {CS_GRP_JUMP, CS_GRP_BRANCH_RELATIVE}
# All Capstone jumps should have at least one of these 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):
# Conditional instruction, and action is taken
@ -244,6 +257,18 @@ class PwndbgInstruction:
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
def can_change_instruction_pointer(self) -> bool:
"""

View File

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