-s/--step First skipping blocks of memory between results

-l/--limit Stop searching after finding N results
-a/--align A match must be aligned at the specified boundary
pull/1894/head
Aaron Adams 2 years ago committed by Disconnect3d
parent f642efbd92
commit d008d14f4b

@ -1 +1 @@
Subproject commit a9b56502f68e8ef7e4086331099bb645583be0a0
Subproject commit 25bae64f45e5e957cab5083a1067acc88ce70ec5

@ -45,9 +45,13 @@ def print_search_hit(address) -> None:
auto_save = pwndbg.gdblib.config.add_param(
"auto-save-search", False, 'automatically pass --save to "search" command'
)
parser = argparse.ArgumentParser(
description="Search memory for byte sequences, strings, pointers, and integer values."
formatter_class=argparse.RawTextHelpFormatter,
description="""Search memory for byte sequences, strings, pointers, and integer values.
By default search results are cached. If you want to cache all results, but only print a subset, use --trunc-out. If you want to cache only a subset of results, and print the results immediately, use --limit. The latter is specially useful if you're searching a huge section of memory.
""",
)
parser.add_argument(
"-t",
@ -105,6 +109,23 @@ parser.add_argument(
"-e", "--executable", action="store_true", help="Search executable segments only"
)
parser.add_argument("-w", "--writable", action="store_true", help="Search writable segments only")
parser.add_argument(
"-s",
"--step",
default=None,
type=str,
help="Step search address forward to next alignment after each hit (ex: 0x1000)",
)
parser.add_argument(
"-l",
"--limit",
default=None,
type=str,
help="Max results before quitting the search. Differs from --trunc-out in that it will not save all search results before quitting",
)
parser.add_argument(
"-a", "--aligned", default=None, type=str, help="Result must be aligned to this byte boundary"
)
parser.add_argument("value", type=str, help="Value to search for")
parser.add_argument(
"mapping_name", type=str, nargs="?", default=None, help="Mapping to search [e.g. libc]"
@ -126,13 +147,29 @@ parser.add_argument(
help="Search only locations returned by previous search with --save",
)
parser.add_argument(
"--trunc-out", action="store_true", default=False, help="Truncate the output to 20 results"
"--trunc-out",
action="store_true",
default=False,
help="Truncate the output to 20 results. Differs from --limit in that it will first save all search results",
)
@pwndbg.commands.ArgparsedCommand(parser, category=CommandCategory.MEMORY)
@pwndbg.commands.OnlyWhenRunning
def search(type, hex, executable, writable, value, mapping_name, save, next, trunc_out) -> None:
def search(
type,
hex,
executable,
writable,
step,
limit,
aligned,
value,
mapping_name,
save,
next,
trunc_out,
) -> None:
global saved
if next and not saved:
print(
@ -155,6 +192,14 @@ def search(type, hex, executable, writable, value, mapping_name, save, next, tru
print(f"invalid input for type hex: {e}")
return
if step:
step = pwndbg.commands.fix_int(step)
if aligned:
aligned = pwndbg.commands.fix_int(aligned)
if limit:
limit = pwndbg.commands.fix_int(limit)
# Convert to an integer if needed, and pack to bytes
if type not in ("string", "bytes"):
value = pwndbg.commands.fix_int(value)
@ -217,7 +262,13 @@ def search(type, hex, executable, writable, value, mapping_name, save, next, tru
# Perform the search
i = 0
for address in pwndbg.search.search(
value, mappings=mappings, executable=executable, writable=writable
value,
mappings=mappings,
executable=executable,
writable=writable,
step=step,
aligned=aligned,
limit=limit,
):
if save:
saved.add(address)

@ -12,7 +12,17 @@ import pwndbg.gdblib.typeinfo
import pwndbg.gdblib.vmmap
def search(searchfor, mappings=None, start=None, end=None, executable=False, writable=False):
def search(
searchfor,
mappings=None,
start=None,
end=None,
step=None,
aligned=None,
limit=None,
executable=False,
writable=False,
):
"""Search inferior memory for a byte sequence.
Arguments:
@ -21,6 +31,9 @@ def search(searchfor, mappings=None, start=None, end=None, executable=False, wri
By default, uses all available mappings.
start(int): First address to search, inclusive.
end(int): Last address to search, exclusive.
step(int): Size of memory region to skip each result
aligned(int): Strict byte alignment for search result
limit(int): Maximum number of results to return
executable(bool): Restrict search to executable pages
writable(bool): Restrict search to writable pages
@ -30,6 +43,7 @@ def search(searchfor, mappings=None, start=None, end=None, executable=False, wri
i = gdb.selected_inferior()
maps = mappings or pwndbg.gdblib.vmmap.get()
found_count = 0
if end and start:
assert start < end, "Last address to search must be greater then first address"
@ -37,7 +51,7 @@ def search(searchfor, mappings=None, start=None, end=None, executable=False, wri
elif start:
maps = [m for m in maps if start in m]
elif end:
maps = [m for m in maps if end - 1 in m]
maps = [m for m in maps if (end - 1) in m]
if executable:
maps = [m for m in maps if m.execute]
@ -45,6 +59,10 @@ def search(searchfor, mappings=None, start=None, end=None, executable=False, wri
if writable:
maps = [m for m in maps if m.write]
if len(maps) == 0:
print("No applicable memory regions found to search in.")
return
for vmmap in maps:
start = vmmap.start
end = vmmap.end
@ -81,10 +99,25 @@ def search(searchfor, mappings=None, start=None, end=None, executable=False, wri
# e.g. -1073733344, which supposed to be 0xffffffffc0002120 in kernel.
start &= 0xFFFFFFFFFFFFFFFF
# Ignore results that don't match required alignment
if aligned and start & (aligned - 1):
start = pwndbg.lib.memory.round_up(start, aligned)
continue
# For some reason, search_memory will return a positive hit
# when it's unable to read memory.
if not pwndbg.gdblib.memory.peek(start):
break
yield start
start += len(searchfor)
found_count += 1
if limit and found_count == limit:
break
if step is not None:
start = pwndbg.lib.memory.round_down(start, step) + step
else:
if aligned:
start = pwndbg.lib.memory.round_up(start + len(searchfor), aligned)
else:
start += len(searchfor)

@ -0,0 +1,31 @@
/* For testing the search command.
*
* We just spray some known patterns into memory
*/
#include <stdlib.h>
void break_here(void) {}
int main(void)
{
void *p;
p = malloc(0x100000);
memset(p, 0x0, 0x100000);
// Pattern we want to find with -i 0x1000
for (int i = 0; i < 0x100000; i += 0x100) {
*(unsigned int *)(p + i) = 0xd00dbeef;
}
// Pattern we want to avoid with -a 0x8
for (int i = 0; i < 0x100000; i += 0x100) {
*(unsigned int *)(p + i + 0x17) = 0xd00dbeef;
}
break_here();
return 0;
}

@ -0,0 +1,160 @@
from __future__ import annotations
import gdb
import tests
SEARCH_BINARY = tests.binaries.get("search_memory.out")
SEARCH_PATTERN = 0xD00DBEEF
def test_command_search_limit(start_binary):
"""
Tests simple search limit
"""
start_binary(SEARCH_BINARY)
gdb.execute("break break_here")
gdb.execute("run")
search_limit = 10
result_str = gdb.execute(
f"search --dword {SEARCH_PATTERN} -l {search_limit} -w", to_string=True
)
result_count = 0
result_value = None
for line in result_str.split("\n"):
if line.startswith("[anon_"):
if not result_value:
result_value = line.split(" ")[2]
result_count += 1
assert result_count == search_limit
assert result_value == hex(SEARCH_PATTERN)
def test_command_search_alignment(start_binary):
"""
Tests aligned search
"""
start_binary(SEARCH_BINARY)
gdb.execute("break break_here")
gdb.execute("run")
alignment = 8
result_str = gdb.execute(f"search --dword {SEARCH_PATTERN} -a {alignment} -w", to_string=True)
for line in result_str.split("\n"):
if line.startswith("[anon_"):
result_address = line.split(" ")[1]
assert int(result_address, 16) % alignment == 0
def test_command_search_step(start_binary):
"""
Tests stepped search
"""
start_binary(SEARCH_BINARY)
gdb.execute("break break_here")
gdb.execute("run")
step = 0x1000
result_str = gdb.execute(f"search --dword {SEARCH_PATTERN} -s {step} -w", to_string=True)
result_count = 0
for line in result_str.split("\n"):
if line.startswith("[anon_"):
result_count += 1
# We allocate 0x100000 bytes
assert result_count == 0x100
def test_command_search_byte_width(start_binary):
"""
Tests 1-byte search
"""
start_binary(SEARCH_BINARY)
gdb.execute("break break_here")
gdb.execute("run")
result_str = gdb.execute(f"search --byte 0xef -w", to_string=True)
result_count = 0
for line in result_str.split("\n"):
if line.startswith("[anon_"):
result_count += 1
assert result_count > 0x100
def test_command_search_word_width(start_binary):
"""
Tests 2-byte word search
"""
start_binary(SEARCH_BINARY)
gdb.execute("break break_here")
gdb.execute("run")
result_str = gdb.execute(f"search --word 0xbeef -w", to_string=True)
result_count = 0
for line in result_str.split("\n"):
if line.startswith("[anon_"):
result_count += 1
assert result_count > 0x100
def test_command_search_dword_width(start_binary):
"""
Tests 4-byte dword search
"""
start_binary(SEARCH_BINARY)
gdb.execute("break break_here")
gdb.execute("run")
result_str = gdb.execute(f"search --dword 0xd00dbeef -w", to_string=True)
result_count = 0
for line in result_str.split("\n"):
if line.startswith("[anon_"):
result_count += 1
assert result_count > 0x100
def test_command_search_qword_width(start_binary):
"""
Tests 8-byte qword search
"""
start_binary(SEARCH_BINARY)
gdb.execute("break break_here")
gdb.execute("run")
result_str = gdb.execute(f"search --dword 0x00000000d00dbeef -w", to_string=True)
result_count = 0
for line in result_str.split("\n"):
if line.startswith("[anon_"):
result_count += 1
assert result_count > 0x100
def test_command_search_rwx(start_binary):
"""
Tests searching for rwx memory only
"""
start_binary(SEARCH_BINARY)
gdb.execute("break break_here")
gdb.execute("run")
result_str = gdb.execute(f"search --dword 0x00000000d00dbeef -w -x", to_string=True)
result_count = 0
for line in result_str.split("\n"):
if line.startswith("[anon_"):
result_count += 1
assert result_count == 0
Loading…
Cancel
Save