mirror of https://github.com/pwndbg/pwndbg.git
PowerPC branch condition resolution and emulation (#3426)
* Determine branch conditionals for powerpc (correctly follow flow of execution in disassembly), and enable PowerPC emulation * Remove old comment * Remove another old comment * Update pwndbg/aglib/disasm/ppc.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @disconnect3d --------- Co-authored-by: Disconnect3d <dominik.b.czarnota@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>pull/3429/head
parent
51e03c96c1
commit
4d32831b8f
@ -0,0 +1,121 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
|
||||
from capstone import * # noqa: F403
|
||||
from capstone.ppc_const import * # noqa: F403
|
||||
from typing_extensions import override
|
||||
|
||||
import pwndbg.aglib.disasm.arch
|
||||
from pwndbg.aglib.disasm.instruction import InstructionCondition
|
||||
from pwndbg.aglib.disasm.instruction import PwndbgInstruction
|
||||
from pwndbg.emu.emulator import Emulator
|
||||
|
||||
POWERPC_CONDITIONAL_BRANCHES = {
|
||||
PPC_INS_BC,
|
||||
PPC_INS_ALIAS_BT,
|
||||
PPC_INS_ALIAS_BF,
|
||||
PPC_INS_ALIAS_BTLR,
|
||||
PPC_INS_ALIAS_BFLR,
|
||||
}
|
||||
|
||||
POWERPC_RETURN_INSTRUCTION = {PPC_INS_ALIAS_BLR, PPC_INS_ALIAS_BTLR, PPC_INS_ALIAS_BFLR}
|
||||
|
||||
|
||||
# PowerPC branch instructions are pretty complex and whether or not the branch is taken depends on 3 factors:
|
||||
# 1. bi - index into cr register, a flags register with conditions (less than, greater than, equal, overflow)
|
||||
# 2. bo - a bitfield that modifies the evaluation of the condition
|
||||
# 3. CTR register - a register that, depending on bo, can be read and modified and effects the result of the branch
|
||||
def is_branch_taken(cr: int, ctr: int, bi: int, bo: int) -> bool | None:
|
||||
# Valid values for bo (5 bit value): https://www.ibm.com/docs/en/aix/7.2.0?topic=set-bc-branch-conditional-instruction
|
||||
# The `x` mean it can be either 0 or 1, it is irrelevant to the branch condition (used to hint that the branch is or isn't taken)
|
||||
# 0000x - Decrement CTR. Branch if CTR is not 0 and condition is false
|
||||
# 0001x - Decrement CTR. Branch if CTR is 0 and condition is false
|
||||
# 001xx - Branch if condition is false
|
||||
# 0100x - Decrement CTR. Branch if CTR is not 0 and condition is true
|
||||
# 0101x - Decrement CTR. Branch if CTR is 0 and condition is true
|
||||
# 011xx - Branch if the condition is true.
|
||||
# 1x00x - Decrement CTR. Branch if CTR is not 0.
|
||||
# 1x01x - Decrement CTR. Branch if CTR is 0
|
||||
# 1x1xx - Always branch
|
||||
|
||||
# GDB `cr` register consists of cr0 .... cr7, where cr0 composes the most-significant bit positions.
|
||||
# This is why we flip the offset that we access.
|
||||
check_cr_offset = 31 - bi
|
||||
condition = (cr >> check_cr_offset) & 1 == 1
|
||||
|
||||
if (bo & 0b11110) == 0b00000: # 0000x
|
||||
ctr -= 1
|
||||
return ctr != 0 and not condition
|
||||
elif (bo & 0b11110) == 0b00010: # 0001x
|
||||
ctr -= 1
|
||||
return ctr == 0 and not condition
|
||||
elif (bo & 0b11100) == 0b00100: # 001xx
|
||||
return not condition
|
||||
elif (bo & 0b11110) == 0b01000: # 0100x
|
||||
ctr -= 1
|
||||
return ctr != 0 and condition
|
||||
elif (bo & 0b11110) == 0b01010: # 0101x
|
||||
ctr -= 1
|
||||
return ctr == 0 and condition
|
||||
elif (bo & 0b11100) == 0b01100: # 011xx
|
||||
return condition
|
||||
elif (bo & 0b10110) == 0b10000: # 1x00x
|
||||
ctr -= 1
|
||||
return ctr != 0
|
||||
elif (bo & 0b10110) == 0b10010: # 1x01x
|
||||
ctr -= 1
|
||||
return ctr == 0
|
||||
elif (bo & 0b10100) == 0b10100: # 1x1xx
|
||||
return True
|
||||
|
||||
# This case should never be reached
|
||||
return None
|
||||
|
||||
|
||||
class PowerPCDisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant):
|
||||
saved_ctr: int | None = None
|
||||
|
||||
def __init__(self, architecture) -> None:
|
||||
super().__init__(architecture)
|
||||
|
||||
self.annotation_handlers: Dict[int, Callable[[PwndbgInstruction, Emulator], None]] = {}
|
||||
|
||||
@override
|
||||
def _prepare(self, instruction: PwndbgInstruction, emu: Emulator) -> None:
|
||||
# Prepare is called before emulation.
|
||||
# At this point, we want to read the value of the ctr register.
|
||||
# This is because branch instructions might mutate ctr within the emulator, which the read_register_name may fetch from
|
||||
# The _conditional() function is called after emulation is stepped, so to read the original
|
||||
# value of CTR, we have to read it beforehand.
|
||||
|
||||
if instruction.id in POWERPC_CONDITIONAL_BRANCHES:
|
||||
self.saved_ctr = self._read_register_name(instruction, "ctr", emu)
|
||||
|
||||
@override
|
||||
def _condition(self, instruction: PwndbgInstruction, emu: Emulator) -> InstructionCondition:
|
||||
cr = self._read_register_name(instruction, "cr", emu)
|
||||
|
||||
if cr is None or self.saved_ctr is None:
|
||||
# We can't reason about the value of cr register
|
||||
return InstructionCondition.UNDETERMINED
|
||||
|
||||
if instruction.id in POWERPC_CONDITIONAL_BRANCHES:
|
||||
is_taken = is_branch_taken(
|
||||
cr, self.saved_ctr, instruction.cs_insn.bc.bi, instruction.cs_insn.bc.bo
|
||||
)
|
||||
|
||||
if is_taken is None:
|
||||
return InstructionCondition.UNDETERMINED
|
||||
|
||||
return InstructionCondition.TRUE if is_taken else InstructionCondition.FALSE
|
||||
|
||||
return InstructionCondition.UNDETERMINED
|
||||
|
||||
@override
|
||||
def _resolve_target(self, instruction: PwndbgInstruction, emu: Emulator | None):
|
||||
if instruction.id in POWERPC_RETURN_INSTRUCTION:
|
||||
return self._read_register_name(instruction, "lr", emu)
|
||||
|
||||
return super()._resolve_target(instruction, emu)
|
||||
Loading…
Reference in new issue