Skip to content

Commit

Permalink
new nop command features
Browse files Browse the repository at this point in the history
  • Loading branch information
therealdreg committed Jul 20, 2023
1 parent 577ad02 commit efa316c
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 48 deletions.
19 changes: 12 additions & 7 deletions docs/commands/nop.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,26 @@
The `nop` command allows you to easily patch instructions with nops.

```
nop [LOCATION] [--n NUM_ITEMS] [--b]
nop [LOCATION] [--i ITEMS] [--f] [--n] [--b]
```

`LOCATION` address/symbol to patch
`LOCATION` address/symbol to patch (by default this command replace whole instruction(s))

`--n NUM_ITEMS` Instead of writing one instruction/nop, patch the specified number of
instructions/nops (full instruction size by default)
`--i ITEMS` number of items to insert (default 1)

`--f` Force patch when the final instruction/nop can be broken

`--n` Instead replace whole instruction(s), insert the number specified by ITEMS-value of nop(s)-instruction(s)

`--b` Instead replace whole instruction(s), fill with nop(s)-instruction(s) the number specified by ITEMS-value bytes

`--b` Instead of writing full instruction size, patch the specified number of nops

```bash
gef➤ nop
gef➤ nop $pc+3
gef➤ nop --n 2 $pc+3
gef➤ nop --i 2 $pc+3
gef➤ nop --b
gef➤ nop --b $pc+3
gef➤ nop --b --n 2 $pc+3
gef➤ nop --f --b --i 2 $pc+3
gef➤ nop --n --i 2 $pc+3
```
63 changes: 46 additions & 17 deletions gef.py
Original file line number Diff line number Diff line change
Expand Up @@ -6025,54 +6025,83 @@ class NopCommand(GenericCommand):
aware."""

_cmdline_ = "nop"
_syntax_ = ("{_cmdline_} [LOCATION] [--n NUM_ITEMS] [--b]"
"\n\tLOCATION\taddress/symbol to patch"
"\t--n NUM_ITEMS\tInstead of writing one instruction/nop, patch the specified number of instructions/nops (full instruction size by default)"
"\t--b\tInstead of writing full instruction size, patch the specified number of nops")
_example_ = f"{_cmdline_} $pc"

_syntax_ = ("{_cmdline_} [LOCATION] [--i ITEMS] [--f] [--n] [--b]"
"\n\tLOCATION\taddress/symbol to patch (by default this command replace whole instruction(s))"
"\t--i ITEMS\tnumber of items to insert (default 1)"
"\t--f\tForce patch when the final instruction/nop can be broken"
"\t--n\tInstead replace whole instruction(s), insert the number specified by ITEMS-value of nop(s)-instruction(s)"
"\t--b\tInstead replace whole instruction(s), fill with nop(s)-instruction(s) the number specified by ITEMS-value bytes")
_example_ = [f"{_cmdline_}",
f"{_cmdline_} $pc+3",
f"{_cmdline_} --n 2 $pc+3",
f"{_cmdline_} --i 2 $pc+3",
f"{_cmdline_} --b",
f"{_cmdline_} --b $pc+3",
f"{_cmdline_} --b --n 2 $pc+3",]
f"{_cmdline_} --f --b --i 2 $pc+3"
f"{_cmdline_} --n --i 2 $pc+3",]

def __init__(self) -> None:
super().__init__(complete=gdb.COMPLETE_LOCATION)
return

@only_if_gdb_running
@parse_arguments({"address": "$pc"}, {"--n": 0, "--b": False})
@parse_arguments({"address": "$pc"}, {"--i": 1, "--b": True, "--f": True, "--n": True})
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.n or 1
as_nops_flags = not args.b
num_items = args.i or 1
fill_bytes = args.b
fill_nops = args.n
force_flag = args.f or False
fill_instructions = False if fill_nops or fill_bytes else True

if fill_nops + fill_bytes + fill_instructions != 1:
err("only is possible specify --b or --n at same time")

total_bytes = 0
if as_nops_flags:
if fill_bytes:
total_bytes = num_items
elif fill_nops:
total_bytes = num_items * len(nop)
else:
try:
last_addr = gdb_get_nth_next_instruction_address(address, num_items)
except:
err(f"Cannot patch instruction at {address:#x}: MAYBE reaching unmapped area")
err(f"Cannot patch instruction at {address:#x} reaching unmapped area")
return
total_bytes = (last_addr - address) + gef_get_instruction_at(last_addr).size()

if total_bytes % len(nop):
warn(f"Patching {total_bytes} bytes at {address:#x} will result in a partially patched instruction and may break disassembly")
if len(nop) > total_bytes or total_bytes % len(nop):
warn(f"Patching {total_bytes} bytes at {address:#x} will result in LAST-NOP (byte nr {total_bytes % len(nop):#x}) broken and may cause a crash or break disassembly")
if not force_flag:
err("you must use --f to allow this kind of patch")
return

target_end_address = address + total_bytes
curr_ins = gef_current_instruction(address)
while curr_ins.address + curr_ins.size() < target_end_address:
if not Address(value=curr_ins.address + 1).valid:
err(f"Cannot patch instruction at {address:#x}: reaching unmapped area")
return
curr_ins = gef_next_instruction(curr_ins.address)

nops = bytearray(nop * (total_bytes // len(nop)))
final_ins_end_addr = curr_ins.address + curr_ins.size()

if final_ins_end_addr != target_end_address:
warn(f"Patching {total_bytes} bytes at {address:#x} will result in LAST-INSTRUCTION ({curr_ins.address:#x}) broken and may cause a crash or break disassembly")
if not force_flag:
err("you must use --f to allow this kind of patch")
return

nops = bytearray(nop * total_bytes) # this array will be bigger than needed when arch nop is > 1 but who cares
end_address = Address(value=address + total_bytes - 1)
if not end_address.valid:
err(f"Cannot patch instruction at {address:#x}: reaching unmapped area")
err(f"Cannot patch instruction at {address:#x}: reaching unmapped area: {end_address:#x}")
return

ok(f"Patching {total_bytes} bytes from {address:#x}")
gef.memory.write(address, nops, total_bytes)

return


Expand Down
Loading

0 comments on commit efa316c

Please sign in to comment.