Skip to content

Commit

Permalink
add stepover command (#1086)
Browse files Browse the repository at this point in the history
This pull request addresses a limitation in GDB where the "nexti" and "next" commands fail to step over call instructions properly
  • Loading branch information
therealdreg committed Apr 20, 2024
1 parent 6a2ecce commit 13af366
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 0 deletions.
16 changes: 16 additions & 0 deletions docs/commands/stepover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## Command `stepover`

The stepover command simplifies the process of stepping over instructions by continuing to a
temporary breakpoint at the next instruction.

This feature is particularly useful for stepping over call/rep instructions.

Ex: Step over call instruction

```text
stepover
```

```bash
gef➤ stepover
```
31 changes: 31 additions & 0 deletions gef.py
Original file line number Diff line number Diff line change
Expand Up @@ -4498,6 +4498,15 @@ def stop(self) -> bool:
return True


class JustSilentStopBreakpoint(gdb.Breakpoint):
"""When hit, this temporary breakpoint stop the execution."""

def __init__(self, loc: str) -> None:
super().__init__(loc, gdb.BP_BREAKPOINT, temporary=True)
self.silent = True
return


#
# Context Panes
#
Expand Down Expand Up @@ -6157,6 +6166,28 @@ def do_invoke(self, _: List[str], **kwargs: Any) -> None:
return


@register
class StepoverCommand(GenericCommand):
"""Breaks on the instruction immediately following this one. Ex: Step over call instruction"""

_cmdline_ = "stepover"
_syntax_ = (f"{_cmdline_}"
"\n\tBreaks on the instruction immediately following this one. Ex: Step over call instruction.")
_aliases_ = ["so",]
_example_ = [f"{_cmdline_}",]

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

@only_if_gdb_running
def do_invoke(self, _: List[str]) -> None:
target_addr = gef_next_instruction(parse_address("$pc")).address
JustSilentStopBreakpoint("".join(["*", str(target_addr)]))
gdb.execute("continue")
return


@register
class NopCommand(GenericCommand):
"""Patch the instruction(s) pointed by parameters with NOP. Note: this command is architecture
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ nav:
- search-pattern: commands/search-pattern.md
- shellcode: commands/shellcode.md
- stub: commands/stub.md
- stepover: commands/stepover.md
- skipi: commands/skipi.md
- theme: commands/theme.md
- tmux-setup: commands/tmux-setup.md
Expand Down
44 changes: 44 additions & 0 deletions tests/commands/stepover.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
`stepover` command test module
"""

import pytest

from tests.base import RemoteGefUnitTestGeneric
from tests.utils import ARCH, ERROR_INACTIVE_SESSION_MESSAGE, p32, p64, u16, u32


class Stepover(RemoteGefUnitTestGeneric):
"""`stepover` command test module"""

cmd = "stepover"

def test_cmd_stepover_inactive(self):
gdb = self._gdb
res = gdb.execute(f"{self.cmd}", to_string=True)
self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE, res)

@pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}")
def test_cmd_stepover(self):
gdb = self._gdb
gef = self._gef

payload = b"\xe8\x05\x00\x00\x00\x90\x90\x6a\x00\xc3\xb8\x69\x69\x69\x69\xc3"
'''
call movtag <- 'stepover' execution from this point
nop <- 'stepover' should stops here and eax value should be 0x69696969
nop
push 0
ret <- if something fails we want a specific crash
movtag:
mov eax, 0x69696969
ret
'''

gdb.execute("start")
gef.memory.write(gef.arch.pc, payload)
res = gdb.execute(self.cmd, to_string=True)
assert res
mem = u16(gef.memory.read(gef.arch.pc, 2)) # read 2 bytes
self.assertEqual(0x9090, mem) # 2 nops
self.assertEqual(0x69696969, gef.arch.register("$eax"))

0 comments on commit 13af366

Please sign in to comment.