Express context saved to the stack + Arm Cortex M Exception return address resolution (#2807)

* Add generic structure to express saved context on the stack. Add handler for Arm Cortex M exception return

* Rename to SavedRegisterFrame, recreate command

* add description

* lint

* Generate docs

* Update pwndbg/aglib/saved_register_frames.py

Co-authored-by: Disconnect3d <dominik.b.czarnota@gmail.com>

* Clean up

---------

Co-authored-by: Disconnect3d <dominik.b.czarnota@gmail.com>
pull/2915/head
OBarronCS 8 months ago committed by GitHub
parent 357738c53c
commit 491800e5a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -113,6 +113,7 @@
## Memory
- [distance](memory/distance.md) - Print the distance between the two arguments, or print the offset to the address's page base.
- [dump-register-frame](memory/dump-register-frame.md) - Display the registers saved to memory for a certain frame type
- [gdt](memory/gdt.md) - Decode X86-64 GDT entries at address
- [go-dump](memory/go-dump.md) - Dumps a Go value of a given type at a specified address.
- [go-type](memory/go-type.md) - Dumps a Go runtime reflection type at a specified address.

@ -0,0 +1,34 @@
<!-- THIS PART OF THIS FILE IS AUTOGENERATED. DO NOT MODIFY IT. See scripts/generate_docs.sh -->
# dump-register-frame
## Description
Display the registers saved to memory for a certain frame type
## Usage:
```bash
usage: dump-register-frame [-h] [-p] {armcm-exception} [address]
```
## Positional Arguments
|Positional Argument|Help|
| :--- | :--- |
|`frame_type`|The type of frame to print|
|`address`|The address to read the frame from|
## Optional Arguments
|Short|Long|Default|Help|
| :--- | :--- | :--- | :--- |
|`-h`|`--help`||show this help message and exit|
|`-p`|`--print`||Show addresses of frame values (default: %(default)s)|
<!-- END OF AUTOGENERATED PART. Do not modify this line or the line below, they mark the end of the auto-generated part of the file. If you want to extend the documentation in a way which cannot easily be done by adding to the command help description, write below the following line. -->
<!-- ------------\>8---- ----\>8---- ----\>8------------ -->

@ -15,6 +15,7 @@ import pwndbg.aglib.arch
import pwndbg.aglib.disasm.arch
import pwndbg.aglib.memory
import pwndbg.aglib.regs
import pwndbg.aglib.saved_register_frames
import pwndbg.lib.disasm.helpers as bit_math
from pwndbg.aglib.disasm.instruction import EnhancedOperand
from pwndbg.aglib.disasm.instruction import InstructionCondition
@ -274,6 +275,14 @@ class ArmDisassemblyAssistant(pwndbg.aglib.disasm.arch.DisassemblyAssistant):
# and instead the CPU puts it into the Thumb mode register bit.
# This means we have to clear the least significant bit of the target.
target = target & ~1
if pwndbg.aglib.arch.name == "armcm" and target & 0xFF00_0000 == 0xFF00_0000:
# If the top 8-bits of the return address are 0xFF, this indicates we are returning from an exception,
# where the return address has been saved onto the stack
return pwndbg.aglib.saved_register_frames.ARM_CORTEX_M_EXCEPTION_STACK.read_saved_register(
"pc"
)
return target
# Currently not used

@ -0,0 +1,50 @@
from __future__ import annotations
from dataclasses import dataclass
import pwndbg.aglib.memory
@dataclass
class SavedRegisterFrame:
"""
A list of registers that have been saved to process memory for later restoration.
For example, on syscall entry, the process registers are saved to the kernel stack.
"""
# List of (offset, register name), sorted from smallest to largest offset
frame_layout: list[tuple[int, str]]
offsets: dict[str, int]
def __init__(self, register_offsets: dict[str, int]):
self.offsets = register_offsets
self.frame_layout = sorted(((y, x) for (x, y) in register_offsets.items()))
def read_saved_register(self, reg: str, sp: int = None) -> int | None:
if sp is None:
sp = pwndbg.aglib.regs.sp
try:
mem = pwndbg.aglib.memory.read(sp + self.offsets[reg], pwndbg.aglib.arch.ptrsize)
except pwndbg.dbg_mod.Error:
return None
return pwndbg.aglib.arch.unpack(mem)
# Basic exception stack frame defined here - https://developer.arm.com/documentation/107706/0100/Exceptions-and-interrupts-overview/Stack-frames
ARM_CORTEX_M_EXCEPTION_STACK_FRAME_OFFSETS = {
"r0": 0x0,
"r1": 0x4,
"r2": 0x8,
"r3": 0xC,
"r12": 0x10,
"lr": 0x14,
"pc": 0x18,
"xpsr": 0x1C,
}
ARM_CORTEX_M_EXCEPTION_STACK = SavedRegisterFrame(ARM_CORTEX_M_EXCEPTION_STACK_FRAME_OFFSETS)

@ -783,6 +783,7 @@ def load_commands() -> None:
import pwndbg.commands.retaddr
import pwndbg.commands.rizin
import pwndbg.commands.rop
import pwndbg.commands.saved_register_frames
import pwndbg.commands.search
import pwndbg.commands.sigreturn
import pwndbg.commands.slab

@ -0,0 +1,85 @@
from __future__ import annotations
import argparse
import pwndbg
import pwndbg.aglib.memory
import pwndbg.chain
import pwndbg.color.context as C
import pwndbg.commands
from pwndbg.aglib.saved_register_frames import ARM_CORTEX_M_EXCEPTION_STACK
from pwndbg.aglib.saved_register_frames import SavedRegisterFrame
from pwndbg.commands import CommandCategory
from pwndbg.commands.sigreturn import print_value
def print_saved_register_frame(
context: SavedRegisterFrame, address: int = None, print_address=False
):
address = pwndbg.aglib.regs.sp if address is None else address
ptr_size = pwndbg.aglib.arch.ptrsize
frame_layout = context.frame_layout
# Offset to the stack pointer where the frame values really begins. Start reading memory there.
# Can be negative, 0, or positive
frame_start_offset = frame_layout[0][0]
read_size = frame_layout[-1][0] - frame_start_offset + ptr_size
mem = pwndbg.aglib.memory.read(address + frame_start_offset, read_size)
for stack_offset, reg in frame_layout:
# Subtract the offset of start of frame, to get the correct offset into "mem"
mem_offset = stack_offset - frame_start_offset
regname = C.register(reg.ljust(4).upper())
value = pwndbg.aglib.arch.unpack(mem[mem_offset : mem_offset + ptr_size])
if reg in pwndbg.aglib.regs.flags: # eflags or cpsr
reg_flags = pwndbg.aglib.regs.flags[reg]
desc = C.format_flags(value, reg_flags)
else:
desc = pwndbg.chain.format(value)
print_value(f"{regname} {desc}", address + stack_offset, print_address)
VALID_FRAME_TYPES = {
"armcm-exception": ARM_CORTEX_M_EXCEPTION_STACK,
"armcm-exception2": ARM_CORTEX_M_EXCEPTION_STACK,
}
parser = argparse.ArgumentParser(
description="Display the registers saved to memory for a certain frame type"
)
parser.add_argument(
"frame_type", choices=tuple(VALID_FRAME_TYPES), type=str, help="The type of frame to print"
)
parser.add_argument(
"address", nargs="?", default=None, type=int, help="The address to read the frame from"
)
parser.add_argument(
"-p",
"--print",
dest="print_address",
action="store_true",
default=False,
help="Show addresses of frame values",
)
@pwndbg.commands.ArgparsedCommand(parser, category=CommandCategory.MEMORY)
@pwndbg.commands.OnlyWhenRunning
def dump_register_frame(frame_type: str, address: int = None, print_address=False) -> None:
register_frame = VALID_FRAME_TYPES.get(frame_type)
if register_frame is None:
print(f"Invalid frame type: {frame_type} (valid: {','.join(VALID_FRAME_TYPES.keys())})")
return
print_saved_register_frame(register_frame, address, print_address)
Loading…
Cancel
Save