diff --git a/pwndbg/commands/__init__.py b/pwndbg/commands/__init__.py index 95d606fbc..bcf0d4557 100644 --- a/pwndbg/commands/__init__.py +++ b/pwndbg/commands/__init__.py @@ -460,6 +460,7 @@ def load_commands(): import pwndbg.commands.config import pwndbg.commands.context import pwndbg.commands.cpsr + import pwndbg.commands.cyclic import pwndbg.commands.cymbol import pwndbg.commands.dt import pwndbg.commands.dumpargs diff --git a/pwndbg/commands/cyclic.py b/pwndbg/commands/cyclic.py new file mode 100644 index 000000000..476a71f90 --- /dev/null +++ b/pwndbg/commands/cyclic.py @@ -0,0 +1,86 @@ +import argparse +import string + +import gdb +from pwnlib.util.cyclic import cyclic +from pwnlib.util.cyclic import cyclic_find + +import pwndbg.commands +import pwndbg.gdblib.arch +from pwndbg.color import message + +parser = argparse.ArgumentParser(description="Cyclic pattern creator/finder") + +parser.add_argument( + "-a", + "--alphabet", + metavar="charset", + default=string.ascii_lowercase, + type=str.encode, + help="The alphabet to use in the cyclic pattern", +) + +parser.add_argument( + "-n", + "--length", + metavar="length", + type=int, + help="Size of the unique subsequences (defaults to the pointer size for the current arch)", +) + +group = parser.add_mutually_exclusive_group(required=False) +group.add_argument( + "-l", + "-o", + "--offset", + "--lookup", + dest="lookup", + metavar="lookup_value", + type=str, + help="Do a lookup instead of printing the sequence (accepts constant values as well as expressions)", +) + +group.add_argument( + "count", + type=int, + nargs="?", + default=None, + help="Number of characters to print from the sequence (default: print the entire sequence)", +) + + +@pwndbg.commands.ArgparsedCommand(parser, command_name="cyclic") +def cyclic_cmd(alphabet, length, lookup, count): + if length: + # Convert from gdb.Value + length = int(length) + else: + length = pwndbg.gdblib.arch.ptrsize + + if lookup: + lookup = pwndbg.commands.fix(lookup, sloppy=True) + + if type(lookup) in [gdb.Value, int]: + lookup = int(lookup).to_bytes(length, pwndbg.gdblib.arch.endian) + elif type(lookup) is str: + lookup = bytes(lookup, "utf-8") + + if len(lookup) != length: + print(message.error(f"Lookup pattern must be {length} bytes")) + return + + print(message.notice(f"Lookup value: {str(lookup)}")) + + if any(c not in alphabet for c in lookup): + print(message.error("Pattern contains characters not present in the alphabet")) + return + + offset = cyclic_find(lookup, alphabet, length) + + if offset == -1: + print(message.error("Given lookup pattern does not exist in the sequence")) + else: + print(message.success(offset)) + else: + sequence = cyclic(count, alphabet, length) + print(sequence.decode()) diff --git a/pwndbg/commands/shell.py b/pwndbg/commands/shell.py index e63b6093f..1e314c4ee 100644 --- a/pwndbg/commands/shell.py +++ b/pwndbg/commands/shell.py @@ -7,7 +7,7 @@ import os import pwndbg.commands import pwndbg.lib.which -pwncmds = ["asm", "constgrep", "cyclic", "disasm", "pwn", "unhex"] +pwncmds = ["asm", "constgrep", "disasm", "pwn", "unhex"] shellcmd_names = [ "awk", "bash", diff --git a/tests/gdb-tests/tests/test_command_cyclic.py b/tests/gdb-tests/tests/test_command_cyclic.py new file mode 100644 index 000000000..d2582eced --- /dev/null +++ b/tests/gdb-tests/tests/test_command_cyclic.py @@ -0,0 +1,57 @@ +import gdb +from pwnlib.util.cyclic import cyclic + +import pwndbg.gdblib.arch +import pwndbg.gdblib.memory +import pwndbg.gdblib.regs +import tests + +REFERENCE_BINARY = tests.binaries.get("reference-binary.out") + + +def test_command_cyclic_value(start_binary): + """ + Tests lookup on a constant value + """ + start_binary(REFERENCE_BINARY) + + ptr_size = pwndbg.gdblib.arch.ptrsize + test_offset = 37 + pattern = cyclic(length=80, n=ptr_size) + val = int.from_bytes(pattern[test_offset : test_offset + ptr_size], pwndbg.gdblib.arch.endian) + out = gdb.execute(f"cyclic -l {hex(val)}", to_string=True) + + assert int(out.split("\n")[1]) == test_offset + + +def test_command_cyclic_register(start_binary): + """ + Tests lookup on a register + """ + start_binary(REFERENCE_BINARY) + + ptr_size = pwndbg.gdblib.arch.ptrsize + test_offset = 45 + pattern = cyclic(length=80, n=ptr_size) + pwndbg.gdblib.regs.rdi = int.from_bytes( + pattern[test_offset : test_offset + ptr_size], pwndbg.gdblib.arch.endian + ) + out = gdb.execute("cyclic -l $rdi", to_string=True) + + assert int(out.split("\n")[1]) == test_offset + + +def test_command_cyclic_address(start_binary): + """ + Tests lookup on a memory address + """ + start_binary(REFERENCE_BINARY) + + addr = pwndbg.gdblib.regs.rsp + ptr_size = pwndbg.gdblib.arch.ptrsize + test_offset = 48 + pattern = cyclic(length=80, n=ptr_size) + pwndbg.gdblib.memory.write(addr, pattern) + out = gdb.execute(f"cyclic -l '{{unsigned long}}{hex(addr + test_offset)}'", to_string=True) + + assert int(out.split("\n")[1]) == test_offset