Enable cross-architecture instruction patching (#3419)

* Enable cross-architecture instruction patching. Use Zig with the patch command, Capstone to disassemble in patch-list.

* remove old comments

* update comment

* rename variable

* Replace pc with . Mark the test as xfail given the qemu-version in CI doesn't support GDB writing to process memory
pull/3422/head^2
OBarronCS 3 weeks ago committed by GitHub
parent aae7ce5324
commit fcaebb752e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -4,20 +4,19 @@ import argparse
from typing import Dict from typing import Dict
from typing import Tuple from typing import Tuple
from pwnlib.asm import asm import pwndbg.aglib.asm
from pwnlib.asm import disasm
import pwndbg.aglib.memory import pwndbg.aglib.memory
import pwndbg.color.context import pwndbg.color.context
import pwndbg.color.memory import pwndbg.color.memory
import pwndbg.color.syntax_highlight import pwndbg.color.syntax_highlight
import pwndbg.commands import pwndbg.commands
import pwndbg.lib.cache import pwndbg.lib.cache
from pwndbg.aglib.disasm.disassembly import get_disassembler
from pwndbg.color import message from pwndbg.color import message
from pwndbg.commands import CommandCategory from pwndbg.commands import CommandCategory
# Keep old patches made so we can revert them # Keep old patches made so we can revert them
patches: Dict[int, Tuple[bytearray, bytearray]] = {} patches: Dict[int, Tuple[bytes, bytes]] = {}
parser = argparse.ArgumentParser(description="Patches given instruction with given code or bytes.") parser = argparse.ArgumentParser(description="Patches given instruction with given code or bytes.")
@ -29,7 +28,7 @@ parser.add_argument("-q", "--quiet", action="store_true", help="don't print anyt
@pwndbg.commands.Command(parser, category=CommandCategory.MISC) @pwndbg.commands.Command(parser, category=CommandCategory.MISC)
@pwndbg.commands.OnlyWhenRunning @pwndbg.commands.OnlyWhenRunning
def patch(address: int, ins: str, quiet: bool) -> None: def patch(address: int, ins: str, quiet: bool) -> None:
new_mem = asm(ins) new_mem = pwndbg.aglib.asm.asm(ins)
old_mem = pwndbg.aglib.memory.read(address, len(new_mem)) old_mem = pwndbg.aglib.memory.read(address, len(new_mem))
@ -82,8 +81,14 @@ def patch_list() -> None:
print(pwndbg.color.context.banner("Patches:")) print(pwndbg.color.context.banner("Patches:"))
for addr, (old, new) in patches.items(): for addr, (old, new) in patches.items():
old_insns = disasm(old, byte=False, offset=False) cs = get_disassembler(pwndbg.aglib.arch.get_capstone_constants(addr))
new_insns = disasm(new, byte=False, offset=False)
old_insns = "\n".join(
[f"{x.mnemonic} {x.op_str}".strip() for x in cs.disasm(old, offset=addr)]
)
new_insns = "\n".join(
[f"{x.mnemonic} {x.op_str}".strip() for x in cs.disasm(new, offset=addr)]
)
colored_addr = pwndbg.color.memory.get(addr) colored_addr = pwndbg.color.memory.get(addr)

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import gdb import gdb
import pytest
from capstone.aarch64_const import AARCH64_INS_BL from capstone.aarch64_const import AARCH64_INS_BL
import pwndbg.aglib.disasm.disassembly import pwndbg.aglib.disasm.disassembly
@ -774,6 +775,68 @@ def test_aarch64_banned_instructions(qemu_assembly_run):
assert dis == expected assert dis == expected
AARCH64_CROSS_ARCH_PATCH_INSTRUCTIONS = f"""
{AARCH64_PREAMBLE}
{AARCH64_GRACEFUL_EXIT}
"""
@pytest.mark.xfail(
reason="qemu-user 8.2.2 (version on Ubuntu24.04) does not support GDB writing to memory. This succeeds on newer versions of qemu. Remove the xfail when qemu is upgraded."
)
def test_aarch64_cross_arch_patch(qemu_assembly_run):
"""
Make sure the `patch` command, which delegates to Zig to compile, works
"""
qemu_assembly_run(AARCH64_CROSS_ARCH_PATCH_INSTRUCTIONS, "aarch64")
dis = gdb.execute("context disasm", to_string=True)
dis = pwndbg.color.strip(dis)
expected_before = (
"LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n"
"─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────\n"
" ► 0x1010120 <_start> mov x0, #0 X0 => 0\n"
" 0x1010124 <_start+4> mov x8, #0x5d X8 => 0x5d\n"
" 0x1010128 <_start+8> svc #0 <SYS_exit>\n"
" 0x101012c <_start+12> nop \n"
" 0x1010130 <_start+16> nop \n"
" 0x1010134 <_start+20> nop \n"
" 0x1010138 <_start+24> nop \n"
" 0x101013c <_start+28> nop \n"
" 0x1010140 <_start+32> nop \n"
" 0x1010144 <_start+36> nop \n"
" 0x1010148 <_start+40> nop \n"
"────────────────────────────────────────────────────────────────────────────────\n"
)
assert dis == expected_before
gdb.execute("patch $pc 'nop; nop; nop'")
dis = gdb.execute("context disasm", to_string=True)
dis = pwndbg.color.strip(dis)
expected_after = (
"LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA\n"
"─────────────────────[ DISASM / aarch64 / set emulate on ]──────────────────────\n"
" ► 0x1010120 <_start> nop \n"
" 0x1010124 <_start+4> nop \n"
" 0x1010128 <_start+8> nop \n"
" 0x101012c <_start+12> nop \n"
" 0x1010130 <_start+16> nop \n"
" 0x1010134 <_start+20> nop \n"
" 0x1010138 <_start+24> nop \n"
" 0x101013c <_start+28> nop \n"
" 0x1010140 <_start+32> nop \n"
" 0x1010144 <_start+36> nop \n"
" 0x1010148 <_start+40> nop \n"
"────────────────────────────────────────────────────────────────────────────────\n"
)
assert dis == expected_after
REFERENCE_BINARY = get_binary("reference-binary.aarch64.out") REFERENCE_BINARY = get_binary("reference-binary.aarch64.out")

Loading…
Cancel
Save