From 0461d6fe6e3592f333e11ab39947905171ba9011 Mon Sep 17 00:00:00 2001 From: crazy hugsy Date: Sat, 22 Jul 2023 16:31:05 -0700 Subject: [PATCH] Fix hardcoded NOP instructions for ARM/AARCH64 (#971) * fix nop insn of arm & arm64 to `hint #0` * `gef.memory.write` should take an optional param for length * [gef] fixed off by one in gdb_get_nth_next_instruction_address for fixed insn size archs * replicate error message change to tests * deprecating `gdb_get_nth_next_instruction_address()` in favor `gef_instruction_n()` * bit more type hinting --- gef.py | 46 ++++++++++++++----------- tests/commands/nop.py | 80 +++++++++++++++++++++---------------------- 2 files changed, 66 insertions(+), 60 deletions(-) diff --git a/gef.py b/gef.py index 85277be67..21b2f43e0 100644 --- a/gef.py +++ b/gef.py @@ -1193,6 +1193,11 @@ def is_valid(self) -> bool: def size(self) -> int: return len(self.opcodes) + def next(self) -> "Instruction": + address = self.address + self.size() + return gef_get_instruction_at(address) + + @deprecated("Use GefHeapManager.find_main_arena_addr()") def search_for_main_arena() -> int: return GefHeapManager.find_main_arena_addr() @@ -2013,6 +2018,9 @@ def gdb_disassemble(start_pc: int, **kwargs: int) -> Generator[Instruction, None arch = frame.architecture() for insn in arch.disassemble(start_pc, **kwargs): + assert isinstance(insn["addr"], int) + assert isinstance(insn["length"], int) + assert isinstance(insn["asm"], str) address = insn["addr"] asm = insn["asm"].rstrip().split(None, 1) if len(asm) > 1: @@ -2066,19 +2074,15 @@ def gdb_get_nth_previous_instruction_address(addr: int, n: int) -> Optional[int] return None +@deprecated(solution="Use `gef_instruction_n().address`") def gdb_get_nth_next_instruction_address(addr: int, n: int) -> int: - """Return the address (Integer) of the `n`-th instruction after `addr`.""" - # fixed-length ABI - if gef.arch.instruction_length: - return addr + n * gef.arch.instruction_length - - # variable-length ABI - insn = list(gdb_disassemble(addr, count=n))[-1] - return insn.address + """Return the address of the `n`-th instruction after `addr`. """ + return gef_instruction_n(addr, n).address def gef_instruction_n(addr: int, n: int) -> Instruction: - """Return the `n`-th instruction after `addr` as an Instruction object.""" + """Return the `n`-th instruction after `addr` as an Instruction object. Note that `n` is treated as + an positive index, starting from 0 (current instruction address)""" return list(gdb_disassemble(addr, count=n + 1))[n] @@ -2509,8 +2513,7 @@ class ARM(Architecture): "$r7", "$r8", "$r9", "$r10", "$r11", "$r12", "$sp", "$lr", "$pc", "$cpsr",) - # https://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0041c/Caccegih.html - nop_insn = b"\x01\x10\xa0\xe1" # mov r1, r1 + nop_insn = b"\x00\xf0\x20\xe3" # hint #0 return_register = "$r0" flag_register: str = "$cpsr" flags_table = { @@ -2675,6 +2678,7 @@ class AARCH64(ARM): 5: "t32", 4: "m[4]", } + nop_insn = b"\x1f\x20\x03\xd5" # hint #0 function_parameters = ("$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7",) syscall_register = "$x8" syscall_instructions = ("svc $x0",) @@ -6029,8 +6033,8 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: address = parse_address(args.address) num_instructions = args.n - last_addr = gdb_get_nth_next_instruction_address(address, num_instructions) - total_bytes = (last_addr - address) + gef_get_instruction_at(last_addr).size() + last_insn = gef_instruction_n(address, num_instructions-1) + total_bytes = (last_insn.address - address) + last_insn.size() target_addr = address + total_bytes info(f"skipping {num_instructions} instructions ({total_bytes} bytes) from {address:#x} to {target_addr:#x}") @@ -6068,13 +6072,13 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: args : argparse.Namespace = kwargs["arguments"] address = parse_address(args.address) nop = gef.arch.nop_insn - num_items = args.i or 1 - fill_bytes = args.b - fill_nops = args.n - force_flag = args.f or False + num_items = int(args.i) or 1 + fill_bytes = bool(args.b) + fill_nops = bool(args.n) + force_flag = bool(args.f) or False if fill_nops and fill_bytes: - err("only is possible specify --b or --n at same time") + err("--b and --n cannot be specified at the same time.") return total_bytes = 0 @@ -6084,7 +6088,8 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None: total_bytes = num_items * len(nop) else: try: - last_addr = gdb_get_nth_next_instruction_address(address, num_items) + last_insn = gef_instruction_n(address, num_items-1) + last_addr = last_insn.address except: err(f"Cannot patch instruction at {address:#x} reaching unmapped area") return @@ -10274,8 +10279,9 @@ def reset_caches(self) -> None: self.__maps = None return - def write(self, address: int, buffer: ByteString, length: int = 0x10) -> None: + def write(self, address: int, buffer: ByteString, length: Optional[int] = None) -> None: """Write `buffer` at address `address`.""" + length = length or len(buffer) gdb.selected_inferior().write_memory(address, buffer, length) def read(self, addr: int, length: int = 0x10) -> bytes: diff --git a/tests/commands/nop.py b/tests/commands/nop.py index 5cb8c9881..afa60bdf5 100644 --- a/tests/commands/nop.py +++ b/tests/commands/nop.py @@ -24,14 +24,14 @@ def test_cmd_nop_inactive(self): def test_cmd_nop_no_arg(self): res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p32(0xfeebfeeb))", + "pi gef.memory.write(gef.arch.pc, p32(0xfeebfeeb))", after=( self.cmd, - "pi print(gef.memory.read(gef.arch.pc, 4))", + "pi print(gef.memory.read(gef.arch.pc, 4))", ) ) self.assertNoException(res) - self.assertIn(r"\x90\x90\xeb\xfe", res) + self.assertIn(r"\x90\x90\xeb\xfe", res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") @@ -39,30 +39,30 @@ def test_cmd_nop_check_b_and_n_same_time(self): res = gdb_start_silent_cmd(f"{self.cmd} --b --n") self.assertNoException(res) - self.assertIn(r"-b or --n at same time", res) + self.assertIn(r"--b and --n cannot be specified at the same time.", res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_no_arg_break_instruction(self): res = gdb_start_silent_cmd( (r"pi gef.arch.nop_insn=b'\x90\x91\x92'", - "pi gef.memory.write(gef.arch.pc, p32(0xfeebfeeb))"), + "pi gef.memory.write(gef.arch.pc, p32(0xfeebfeeb))"), after=( self.cmd, - "pi print(gef.memory.read(gef.arch.pc, 4))", + "pi print(gef.memory.read(gef.arch.pc, 4))", ) ) self.assertNoException(res) self.assertIn(r"will result in LAST-NOP (byte nr 0x2)", res) - self.assertNotIn(r"\x90\x90\xeb\xfe", res) + self.assertNotIn(r"\x90\x90\xeb\xfe", res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_force_arg_break_instruction(self): res = gdb_start_silent_cmd( (r"pi gef.arch.nop_insn=b'\x90\x91\x92'", - "pi gef.memory.write(gef.arch.pc, p32(0xfeebfeeb))"), + "pi gef.memory.write(gef.arch.pc, p32(0xfeebfeeb))"), after=( f"{self.cmd} --f", @@ -71,36 +71,36 @@ def test_cmd_nop_force_arg_break_instruction(self): ) self.assertNoException(res) self.assertIn(r"will result in LAST-NOP (byte nr 0x2)", res) - self.assertIn(r"\x90\x91\xeb\xfe", res) + self.assertIn(r"\x90\x91\xeb\xfe", res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_i_arg(self): res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc+1, p64(0xfeebfeebfeebfeeb))", + "pi gef.memory.write(gef.arch.pc+1, p64(0xfeebfeebfeebfeeb))", after=( f"{self.cmd} --i 2 $pc+1", - "pi print(gef.memory.read(gef.arch.pc+1, 8))", + "pi print(gef.memory.read(gef.arch.pc+1, 8))", ) ) self.assertNoException(res) - self.assertIn(r"\x90\x90\x90\x90\xeb\xfe\xeb\xfe", res) + self.assertIn(r"\x90\x90\x90\x90\xeb\xfe\xeb\xfe", res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_i_arg_reaching_unmapped_area(self): res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc+1, p64(0xfeebfeebfeebfeeb))", + "pi gef.memory.write(gef.arch.pc+1, p64(0xfeebfeebfeebfeeb))", after=( f"{self.cmd} --i 2000000000000000000000000000000000000 $pc+1", - "pi print(gef.memory.read(gef.arch.pc+1, 8))", + "pi print(gef.memory.read(gef.arch.pc+1, 8))", ) ) self.assertIn(r"reaching unmapped area", res) self.assertNoException(res) - self.assertNotIn(r"\x90\x90\x90\x90\xeb\xfe\xeb\xfe", res) + self.assertNotIn(r"\x90\x90\x90\x90\xeb\xfe\xeb\xfe", res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") @@ -116,10 +116,10 @@ def test_cmd_nop_invalid_end_address(self): @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_nop(self): res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p32(0x9191))", + "pi gef.memory.write(gef.arch.pc, p32(0x9191))", after=( f"{self.cmd} --n", - "pi print(gef.memory.read(gef.arch.pc, 2))", + "pi print(gef.memory.read(gef.arch.pc, 2))", ) ) self.assertIn(r"\x90\x91", res) @@ -129,10 +129,10 @@ def test_cmd_nop_nop(self): @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_nop_break_instruction(self): res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p16(0xfeeb))", + "pi gef.memory.write(gef.arch.pc, p16(0xfeeb))", after=( f"{self.cmd} --n", - "pi print(gef.memory.read(gef.arch.pc, 2))", + "pi print(gef.memory.read(gef.arch.pc, 2))", ) ) self.assertIn(r"will result in LAST-INSTRUCTION", res) @@ -143,10 +143,10 @@ def test_cmd_nop_nop_break_instruction(self): @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_nop_break_instruction_force(self): res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p16(0xfeeb))", + "pi gef.memory.write(gef.arch.pc, p16(0xfeeb))", after=( f"{self.cmd} --n --f", - "pi print(gef.memory.read(gef.arch.pc, 2))", + "pi print(gef.memory.read(gef.arch.pc, 2))", ) ) self.assertIn(r"will result in LAST-INSTRUCTION", res) @@ -157,10 +157,10 @@ def test_cmd_nop_nop_break_instruction_force(self): @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_nop_arg(self): res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))", + "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))", after=( f"{self.cmd} --i 4 --n", - "pi print(gef.memory.read(gef.arch.pc, 8))", + "pi print(gef.memory.read(gef.arch.pc, 8))", ) ) self.assertIn(r"b'\x90\x90\x90\x90\xeb\xfe\xeb\xfe'", res) @@ -171,7 +171,7 @@ def test_cmd_nop_nop_arg(self): def test_cmd_nop_nop_arg_multibnop_breaks(self): res = gdb_start_silent_cmd( (r"pi gef.arch.nop_insn=b'\x90\x91\x92'", - "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))"), + "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))"), after=( f"{self.cmd} --n", @@ -180,14 +180,14 @@ def test_cmd_nop_nop_arg_multibnop_breaks(self): ) self.assertNoException(res) self.assertIn(r"will result in LAST-INSTRUCTION", res) - self.assertIn(r"b'\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe'", res) + self.assertIn(r"b'\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe'", res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_nop_arg_multibnop_breaks_force(self): res = gdb_start_silent_cmd( (r"pi gef.arch.nop_insn=b'\x90\x91\x92'", - "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))"), + "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))"), after=( f"{self.cmd} --n --f", @@ -196,16 +196,16 @@ def test_cmd_nop_nop_arg_multibnop_breaks_force(self): ) self.assertNoException(res) self.assertIn(r"will result in LAST-INSTRUCTION", res) - self.assertIn(r"b'\x90\x91\x92\xfe\xeb\xfe\xeb\xfe'", res) + self.assertIn(r"b'\x90\x91\x92\xfe\xeb\xfe\xeb\xfe'", res) @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_bytes(self): res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p16(0x9191))", + "pi gef.memory.write(gef.arch.pc, p16(0x9191))", after=( f"{self.cmd} --b", - "pi print(gef.memory.read(gef.arch.pc, 2))", + "pi print(gef.memory.read(gef.arch.pc, 2))", ) ) @@ -216,10 +216,10 @@ def test_cmd_nop_bytes(self): @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_bytes_break_instruction(self): res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p16(0xfeeb))", + "pi gef.memory.write(gef.arch.pc, p16(0xfeeb))", after=( f"{self.cmd} --b", - "pi print(gef.memory.read(gef.arch.pc, 2))", + "pi print(gef.memory.read(gef.arch.pc, 2))", ) ) @@ -231,10 +231,10 @@ def test_cmd_nop_bytes_break_instruction(self): @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_bytes_break_instruction_force(self): res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p16(0xfeeb))", + "pi gef.memory.write(gef.arch.pc, p16(0xfeeb))", after=( f"{self.cmd} --b --f", - "pi print(gef.memory.read(gef.arch.pc, 2))", + "pi print(gef.memory.read(gef.arch.pc, 2))", ) ) self.assertIn(r"will result in LAST-INSTRUCTION", res) @@ -245,10 +245,10 @@ def test_cmd_nop_bytes_break_instruction_force(self): @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_bytes_arg(self): res = gdb_start_silent_cmd( - "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))", + "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))", after=( f"{self.cmd} --i 2 --b --f", - "pi print(gef.memory.read(gef.arch.pc, 8))", + "pi print(gef.memory.read(gef.arch.pc, 8))", ) ) self.assertIn(r"b'\x90\x90\xeb\xfe\xeb\xfe\xeb\xfe'", res) @@ -259,11 +259,11 @@ def test_cmd_nop_bytes_arg(self): def test_cmd_nop_bytes_arg_nops_no_fit(self): res = gdb_start_silent_cmd( (r"pi gef.arch.nop_insn=b'\x90\x91\x92'", - "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))"), + "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))"), after=( f"{self.cmd} --i 4 --b", - "pi print(gef.memory.read(gef.arch.pc, 8))", + "pi print(gef.memory.read(gef.arch.pc, 8))", ) ) self.assertIn(r"b'\xeb\xfe\xeb\xfe\xeb\xfe\xeb\xfe'", res) @@ -274,12 +274,12 @@ def test_cmd_nop_bytes_arg_nops_no_fit(self): @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") def test_cmd_nop_bytes_arg_nops_no_fit_force(self): res = gdb_start_silent_cmd( - (r"pi gef.arch.nop_insn=b'\x90\x91\x92'", - "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))"), + (r"pi gef.arch.nop_insn=b'\x90\x91\x92'", + "pi gef.memory.write(gef.arch.pc, p64(0xfeebfeebfeebfeeb))"), after=( f"{self.cmd} --i 5 --b --f", - "pi print(gef.memory.read(gef.arch.pc, 8))", + "pi print(gef.memory.read(gef.arch.pc, 8))", ) ) self.assertIn(r"b'\x90\x91\x92\x90\x91\xfe\xeb\xfe'", res)