Fixup unconditional branch detection, do not annotate conditional instructions which are not taken. Add 3 new arm tests (#3358)

pull/3360/head
OBarronCS 2 months ago committed by GitHub
parent c2ed988f19
commit a1e5c13abf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -362,7 +362,7 @@ class AArch64DisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant)
if instruction.id == AARCH64_INS_B: if instruction.id == AARCH64_INS_B:
# The B instruction can be made conditional by the condition codes # The B instruction can be made conditional by the condition codes
if instruction.cs_insn.cc in (AArch64CC_Invalid, AArch64CC_AL): if instruction.cs_insn.cc in (AArch64CC_Invalid, AArch64CC_AL):
instruction.declare_conditional = False instruction.declare_is_unconditional_jump = True
else: else:
flags = super()._read_register_name(instruction, "cpsr", emu) flags = super()._read_register_name(instruction, "cpsr", emu)
if flags is not None: if flags is not None:

@ -249,7 +249,10 @@ class DisassemblyAssistant:
# Set the .target and .next fields # Set the .target and .next fields
self._enhance_next(instruction, emu, jump_emu) self._enhance_next(instruction, emu, jump_emu)
if bool(pwndbg.config.disasm_annotations): if (
bool(pwndbg.config.disasm_annotations)
and instruction.condition != InstructionCondition.FALSE
):
self._set_annotation_string(instruction, emu) self._set_annotation_string(instruction, emu)
# 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

@ -261,7 +261,7 @@ class ArmDisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant):
# These condition codes indicate unconditionally/condition is not relevant # These condition codes indicate unconditionally/condition is not relevant
if instruction.cs_insn.cc in (ARM_CC_AL, ARMCC_UNDEF): if instruction.cs_insn.cc in (ARM_CC_AL, ARMCC_UNDEF):
if instruction.id in (ARM_INS_B, ARM_INS_BL, ARM_INS_BLX, ARM_INS_BX, ARM_INS_BXJ): if instruction.id in (ARM_INS_B, ARM_INS_BL, ARM_INS_BLX, ARM_INS_BX, ARM_INS_BXJ):
instruction.declare_conditional = False instruction.declare_is_unconditional_jump = True
return InstructionCondition.UNDETERMINED return InstructionCondition.UNDETERMINED
value = self._read_register_name(instruction, self.flags_reg, emu) value = self._read_register_name(instruction, self.flags_reg, emu)

@ -23,6 +23,7 @@ from capstone.arm import ARM_INS_TBH
from capstone.loongarch import LOONGARCH_INS_ALIAS_JR from capstone.loongarch import LOONGARCH_INS_ALIAS_JR
from capstone.loongarch import LOONGARCH_INS_B from capstone.loongarch import LOONGARCH_INS_B
from capstone.loongarch import LOONGARCH_INS_BL from capstone.loongarch import LOONGARCH_INS_BL
from capstone.loongarch import LOONGARCH_INS_CALL36
from capstone.loongarch import LOONGARCH_INS_JIRL from capstone.loongarch import LOONGARCH_INS_JIRL
from capstone.mips import MIPS_INS_ALIAS_B from capstone.mips import MIPS_INS_ALIAS_B
from capstone.mips import MIPS_INS_ALIAS_BAL from capstone.mips import MIPS_INS_ALIAS_BAL
@ -44,6 +45,8 @@ from capstone.riscv import RISCV_INS_C_JALR
from capstone.riscv import RISCV_INS_C_JR from capstone.riscv import RISCV_INS_C_JR
from capstone.riscv import RISCV_INS_JAL from capstone.riscv import RISCV_INS_JAL
from capstone.riscv import RISCV_INS_JALR from capstone.riscv import RISCV_INS_JALR
from capstone.sparc import SPARC_INS_ALIAS_CALL
from capstone.sparc import SPARC_INS_CALL
from capstone.sparc import SPARC_INS_JMPL from capstone.sparc import SPARC_INS_JMPL
from capstone.systemz import SYSTEMZ_INS_B from capstone.systemz import SYSTEMZ_INS_B
from capstone.systemz import SYSTEMZ_INS_BAL from capstone.systemz import SYSTEMZ_INS_BAL
@ -55,6 +58,7 @@ from capstone.systemz import SYSTEMZ_INS_J
from capstone.systemz import SYSTEMZ_INS_JL from capstone.systemz import SYSTEMZ_INS_JL
from capstone.x86 import X86_INS_CALL from capstone.x86 import X86_INS_CALL
from capstone.x86 import X86_INS_JMP from capstone.x86 import X86_INS_JMP
from capstone.x86 import X86_INS_RET
from capstone.x86 import X86Op from capstone.x86 import X86Op
from typing_extensions import override from typing_extensions import override
@ -62,10 +66,8 @@ import pwndbg.dbg
from pwndbg.dbg import DisassembledInstruction from pwndbg.dbg import DisassembledInstruction
# Architecture specific instructions that mutate the instruction pointer unconditionally # 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,
# so we don't need to manually specify those for each architecture
UNCONDITIONAL_JUMP_INSTRUCTIONS: Dict[int, Set[int]] = { UNCONDITIONAL_JUMP_INSTRUCTIONS: Dict[int, Set[int]] = {
CS_ARCH_X86: {X86_INS_CALL, X86_INS_JMP}, CS_ARCH_X86: {X86_INS_CALL, X86_INS_RET, X86_INS_JMP},
CS_ARCH_MIPS: { CS_ARCH_MIPS: {
MIPS_INS_J, MIPS_INS_J,
MIPS_INS_JR, MIPS_INS_JR,
@ -77,7 +79,7 @@ UNCONDITIONAL_JUMP_INSTRUCTIONS: Dict[int, Set[int]] = {
MIPS_INS_B, MIPS_INS_B,
MIPS_INS_ALIAS_B, MIPS_INS_ALIAS_B,
}, },
CS_ARCH_SPARC: {SPARC_INS_JMPL}, CS_ARCH_SPARC: {SPARC_INS_CALL, SPARC_INS_ALIAS_CALL, SPARC_INS_JMPL},
CS_ARCH_ARM: { CS_ARCH_ARM: {
ARM_INS_TBB, ARM_INS_TBB,
ARM_INS_TBH, ARM_INS_TBH,
@ -107,6 +109,7 @@ UNCONDITIONAL_JUMP_INSTRUCTIONS: Dict[int, Set[int]] = {
LOONGARCH_INS_BL, LOONGARCH_INS_BL,
LOONGARCH_INS_JIRL, LOONGARCH_INS_JIRL,
LOONGARCH_INS_ALIAS_JR, LOONGARCH_INS_ALIAS_JR,
LOONGARCH_INS_CALL36,
}, },
} }
@ -119,12 +122,11 @@ BRANCH_AND_LINK_INSTRUCTIONS[CS_ARCH_MIPS] = {
MIPS_INS_JALR, 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, 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 | {CS_GRP_CALL, CS_GRP_RET, CS_GRP_IRET}
# All non-ret jumps # All non-ret jumps
FORWARD_JUMP_GROUP = {CS_GRP_CALL} | GENERIC_JUMP_GROUPS FORWARD_JUMP_GROUP = {CS_GRP_CALL} | GENERIC_JUMP_GROUPS
@ -179,7 +181,6 @@ class PwndbgInstruction(Protocol):
target_string: str | None target_string: str | None
target_const: bool | None target_const: bool | None
condition: InstructionCondition condition: InstructionCondition
declare_conditional: bool | None
declare_is_unconditional_jump: bool declare_is_unconditional_jump: bool
force_unconditional_jump_target: bool force_unconditional_jump_target: bool
annotation: str | None annotation: str | None
@ -332,21 +333,6 @@ class PwndbgInstructionImpl(PwndbgInstruction):
FALSE if the instruction has a conditional action, and we know it is not taken. FALSE if the instruction has a conditional action, and we know it is not taken.
""" """
self.declare_conditional: bool | None = None
"""
This field is used to declare if the instruction is a conditional instruction.
In most cases, we can determine this purely based on the instruction ID, and this field is irrelevent.
However, in some arches, like Arm, the same instruction can be made conditional by certain instruction attributes.
Ex:
Arm, `bls` instruction. This is encoded as a `b` under the code, with an additional condition code field.
In this case, sometimes a `b` instruction is unconditional (always branches), in other cases it is conditional.
We use this field to disambiguate these cases.
True if we manually determine this instruction is a conditional instruction
False if it's not a conditional instruction
None if we don't have a determination (most cases)
"""
self.declare_is_unconditional_jump: bool = False self.declare_is_unconditional_jump: bool = False
""" """
This field is used to declare that this instruction is an unconditional jump. This field is used to declare that this instruction is an unconditional jump.
@ -467,8 +453,7 @@ class PwndbgInstructionImpl(PwndbgInstruction):
This does not imply that we have resolved the .target This does not imply that we have resolved the .target
""" """
return ( return (
self.declare_conditional is not False self.declare_is_unconditional_jump is False
and self.declare_is_unconditional_jump is False
and bool(self.groups & GENERIC_JUMP_GROUPS) and bool(self.groups & GENERIC_JUMP_GROUPS)
and self.id not in UNCONDITIONAL_JUMP_INSTRUCTIONS[self.cs_insn._cs.arch] and self.id not in UNCONDITIONAL_JUMP_INSTRUCTIONS[self.cs_insn._cs.arch]
) )
@ -485,10 +470,8 @@ class PwndbgInstructionImpl(PwndbgInstruction):
This does not imply that we have resolved the .target This does not imply that we have resolved the .target
""" """
return ( return (
bool(self.groups & GENERIC_UNCONDITIONAL_JUMP_GROUPS) self.declare_is_unconditional_jump
or self.id in UNCONDITIONAL_JUMP_INSTRUCTIONS[self.cs_insn._cs.arch] or self.id in UNCONDITIONAL_JUMP_INSTRUCTIONS[self.cs_insn._cs.arch]
or self.declare_is_unconditional_jump
or self.declare_conditional is False
) )
@property @property
@ -564,7 +547,6 @@ class PwndbgInstructionImpl(PwndbgInstruction):
Operands: [{operands_str}] Operands: [{operands_str}]
Conditional jump: {self.is_conditional_jump}. Taken: {self.is_conditional_jump_taken} Conditional jump: {self.is_conditional_jump}. Taken: {self.is_conditional_jump_taken}
Unconditional jump: {self.is_unconditional_jump} Unconditional jump: {self.is_unconditional_jump}
Declare conditional: {self.declare_conditional}
Declare unconditional jump: {self.declare_is_unconditional_jump} Declare unconditional jump: {self.declare_is_unconditional_jump}
Force jump target: {self.force_unconditional_jump_target} Force jump target: {self.force_unconditional_jump_target}
Can change PC: {self.has_jump_target} Can change PC: {self.has_jump_target}
@ -737,7 +719,6 @@ class ManualPwndbgInstruction(PwndbgInstruction):
self.condition = InstructionCondition.UNDETERMINED self.condition = InstructionCondition.UNDETERMINED
self.declare_conditional = None
self.declare_is_unconditional_jump = False self.declare_is_unconditional_jump = False
self.force_unconditional_jump_target = False self.force_unconditional_jump_target = False

@ -837,3 +837,153 @@ def test_arm_it_block_cached_thumb_mode(qemu_assembly_run):
) )
assert dis == expected assert dis == expected
ARM_CONDITIONAL_INSTRUCTIONS = f"""
{ARM_PREAMBLE}
cmp r0, #0
ldrbne r2, [r0]
movne r0, #1
cmpne r2, #0
bxne lr
nop
nop
nop
nop
nop
nop
"""
def test_arm_conditional_instructions(qemu_assembly_run):
qemu_assembly_run(ARM_CONDITIONAL_INSTRUCTIONS, "arm")
dis = gdb.execute("context disasm", to_string=True)
dis = pwndbg.color.strip(dis)
expected = (
"LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n"
"──────────────────[ DISASM / arm / arm mode / set emulate on ]──────────────────\n"
" ► 0x200b4 <_start> cmp r0, #0 0 - 0 CPSR => 0x60000010 [ n Z C v q j t e a i f ]\n"
" 0x200b8 <_start+4> ✘ ldrbne r2, [r0]\n"
" 0x200bc <_start+8> ✘ movne r0, #1\n"
" 0x200c0 <_start+12> ✘ cmpne r2, #0\n"
" 0x200c4 <_start+16> ✘ bxne lr <0>\n"
" \n"
" 0x200c8 <_start+20> nop \n"
" 0x200cc <_start+24> nop \n"
" 0x200d0 <_start+28> nop \n"
" 0x200d4 <_start+32> nop \n"
" 0x200d8 <_start+36> nop \n"
" 0x200dc <_start+40> nop \n"
"────────────────────────────────────────────────────────────────────────────────\n"
)
assert dis == expected
ARM_CONDITIONAL_INSTRUCTIONS_SUCCESSFUL_BRANCH = f"""
{ARM_PREAMBLE}
LDR lr, =jump_here
cmp r0, #0
ldrbne r2, [r0]
movne r0, #1
cmpne r2, #0
bxeq lr
nop
nop
nop
nop
nop
nop
nop
nop
jump_here:
nop
nop
nop
nop
nop
nop
nop
"""
def test_arm_conditional_instructions_successful_branch(qemu_assembly_run):
qemu_assembly_run(ARM_CONDITIONAL_INSTRUCTIONS_SUCCESSFUL_BRANCH, "arm")
dis = gdb.execute("context disasm", to_string=True)
dis = pwndbg.color.strip(dis)
expected = (
"LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n"
"──────────────────[ DISASM / arm / arm mode / set emulate on ]──────────────────\n"
" ► 0x200b4 <_start> ldr lr, [pc, #0x4c] R14, [jump_here+28] => 0x200ec (jump_here) ◂— nop \n"
" 0x200b8 <_start+4> cmp r0, #0 0 - 0 CPSR => 0x60000010 [ n Z C v q j t e a i f ]\n"
" 0x200bc <_start+8> ✘ ldrbne r2, [r0]\n"
" 0x200c0 <_start+12> ✘ movne r0, #1\n"
" 0x200c4 <_start+16> ✘ cmpne r2, #0\n"
" 0x200c8 <_start+20> ✔ bxeq lr <jump_here>\n"
"\n"
" 0x200ec <jump_here> nop \n"
" 0x200f0 <jump_here+4> nop \n"
" 0x200f4 <jump_here+8> nop \n"
" 0x200f8 <jump_here+12> nop \n"
" 0x200fc <jump_here+16> nop \n"
"────────────────────────────────────────────────────────────────────────────────\n"
)
assert dis == expected
ARM_CONDITIONAL_INSTRUCTIONS_CALL = f"""
{ARM_PREAMBLE}
cmp r0, #0
bleq func
nop
nop
nop
nop
nop
nop
nop
nop
nop
func:
mov pc, lr
"""
def test_arm_conditional_call(qemu_assembly_run):
qemu_assembly_run(ARM_CONDITIONAL_INSTRUCTIONS_CALL, "arm")
dis = gdb.execute("context disasm", to_string=True)
dis = pwndbg.color.strip(dis)
expected = (
"LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n"
"──────────────────[ DISASM / arm / arm mode / set emulate on ]──────────────────\n"
" ► 0x200b4 <_start> cmp r0, #0 0 - 0 CPSR => 0x60000010 [ n Z C v q j t e a i f ]\n"
" 0x200b8 <_start+4> ✔ bleq func <func>\n"
" \n"
" 0x200bc <_start+8> nop \n"
" 0x200c0 <_start+12> nop \n"
" 0x200c4 <_start+16> nop \n"
" 0x200c8 <_start+20> nop \n"
" 0x200cc <_start+24> nop \n"
" 0x200d0 <_start+28> nop \n"
" 0x200d4 <_start+32> nop \n"
" 0x200d8 <_start+36> nop \n"
" 0x200dc <_start+40> nop \n"
"────────────────────────────────────────────────────────────────────────────────\n"
)
assert dis == expected

Loading…
Cancel
Save