From ee832c80d281c903efdba0306d090dcacd267518 Mon Sep 17 00:00:00 2001 From: Disconnect3d Date: Mon, 20 Feb 2023 18:49:44 +0100 Subject: [PATCH] Fix plt and gotplt commands (#1576) * Fix plt and gotplt commands * Add plt gotplt commands tests * Fix got and plt commands and test them * Revert accidental change * Extend system path * Hopefully fix PATH problems once and for all? * fix import * remove redundant part --- Dockerfile | 6 +- gdbinit.py | 9 ++- pwndbg/commands/elf.py | 17 +++++- pwndbg/commands/got.py | 6 +- pwndbg/gdblib/proc.py | 14 +++++ pwndbg/gdblib/symbol.py | 13 ++-- tests/gdb-tests/tests/binaries/makefile | 11 +++- tests/gdb-tests/tests/test_commands_elf.py | 70 ++++++++++++++++++++++ 8 files changed, 127 insertions(+), 19 deletions(-) create mode 100644 tests/gdb-tests/tests/test_commands_elf.py diff --git a/Dockerfile b/Dockerfile index 55a30b881..319547768 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,11 +38,9 @@ RUN sed -i "s/^git submodule/#git submodule/" ./setup.sh && \ ADD ./setup-dev.sh /pwndbg/ RUN ./setup-dev.sh -RUN echo "source /pwndbg/gdbinit.py" >> ~/.gdbinit.py && \ - echo "PYTHON_MINOR=$(python3 -c "import sys;print(sys.version_info.minor)")" >> /root/.bashrc && \ - echo "PYTHON_PATH=\"/usr/local/lib/python3.${PYTHON_MINOR}/dist-packages/bin\"" >> /root/.bashrc && \ - echo "export PATH=$PATH:$PYTHON_PATH" >> /root/.bashrc +RUN echo "source /pwndbg/gdbinit.py" >> ~/.gdbinit.py ADD . /pwndbg/ RUN git submodule update --init --recursive + diff --git a/gdbinit.py b/gdbinit.py index 5b8cec996..7ead758df 100644 --- a/gdbinit.py +++ b/gdbinit.py @@ -1,11 +1,14 @@ import cProfile import glob import locale +import os import sys import time from os import environ from os import path +import pkg_resources + _profiler = cProfile.Profile() _start_time = None @@ -75,11 +78,15 @@ directory, file = path.split(__file__) directory = path.expanduser(directory) directory = path.abspath(directory) - +# Add gdb-pt-dump directory to sys.path so it can be imported gdbpt = path.join(directory, "gdb-pt-dump") sys.path.append(directory) sys.path.append(gdbpt) +# Add the dir where Pwntools binaries might be into PATH +pwntools_bin_dir = os.path.join(pkg_resources.get_distribution("pwntools").location, "bin") +os.environ["PATH"] = os.environ.get("PATH") + os.pathsep + pwntools_bin_dir + # warn if the user has different encoding than utf-8 encoding = locale.getpreferredencoding() diff --git a/pwndbg/commands/elf.py b/pwndbg/commands/elf.py index a96487612..3c152bf44 100755 --- a/pwndbg/commands/elf.py +++ b/pwndbg/commands/elf.py @@ -68,9 +68,22 @@ def print_symbols_in_section(section_name, filter_text="") -> None: start, end = get_section_bounds(section_name) if start is None: - print(message.error("Could not find section")) + print(message.error(f"Could not find section {section_name}")) return + elf_header = pwndbg.gdblib.elf.exe() + + # If we started the binary and it has PIE, rebase it + if pwndbg.gdblib.proc.alive: + bin_base_addr = pwndbg.gdblib.proc.binary_base_addr + + # Rebase the start and end addresses if needed + if start < bin_base_addr: + start += bin_base_addr + end += bin_base_addr + + print(message.notice(f"Section {section_name} {start:#x}-{end:#x}:")) + symbols = get_symbols_in_region(start, end, filter_text) if not symbols: @@ -85,7 +98,7 @@ def get_symbols_in_region(start, end, filter_text=""): ptr_size = pwndbg.gdblib.typeinfo.pvoid.sizeof addr = start while addr < end: - name = pwndbg.gdblib.symbol.get(addr) + name = pwndbg.gdblib.symbol.get(addr, gdb_only=True) if name != "" and "+" not in name and filter_text in name: symbols.append((name, addr)) addr += ptr_size diff --git a/pwndbg/commands/got.py b/pwndbg/commands/got.py index c7f25939e..d5f184f39 100644 --- a/pwndbg/commands/got.py +++ b/pwndbg/commands/got.py @@ -27,16 +27,14 @@ def got(name_filter="") -> None: return if "PIE enabled" in pie_status: - bin_base = pwndbg.gdblib.elf.exe().address + bin_base = pwndbg.gdblib.proc.binary_base_addr relro_color = message.off if "Partial" in relro_status: relro_color = message.warn elif "Full" in relro_status: relro_color = message.on - print( - "\nGOT protection: %s | GOT functions: %d\n " % (relro_color(relro_status), len(jmpslots)) - ) + print("GOT protection: %s | GOT functions: %d" % (relro_color(relro_status), len(jmpslots))) for line in jmpslots: address, info, rtype, value, name = line.split()[:5] diff --git a/pwndbg/gdblib/proc.py b/pwndbg/gdblib/proc.py index 61528d641..8c646ea42 100644 --- a/pwndbg/gdblib/proc.py +++ b/pwndbg/gdblib/proc.py @@ -9,11 +9,13 @@ import sys from types import ModuleType from typing import Any from typing import Callable +from typing import Tuple import gdb import pwndbg.gdblib.qemu import pwndbg.lib.memoize +import pwndbg.lib.memory class module(ModuleType): @@ -89,6 +91,18 @@ class module(ModuleType): """ return gdb.current_progspace().filename + @property + @pwndbg.lib.memoize.reset_on_start + @pwndbg.lib.memoize.reset_on_stop + def binary_base_addr(self) -> int: + return self.binary_vmmap[0].start + + @property + @pwndbg.lib.memoize.reset_on_start + @pwndbg.lib.memoize.reset_on_stop + def binary_vmmap(self) -> Tuple[pwndbg.lib.memory.Page, ...]: + return tuple(p for p in pwndbg.gdblib.vmmap.get() if p.objfile == self.exe) + def OnlyWhenRunning(self, func): @functools.wraps(func) def wrapper(*a, **kw): diff --git a/pwndbg/gdblib/symbol.py b/pwndbg/gdblib/symbol.py index 1d3d00248..077b22fd3 100644 --- a/pwndbg/gdblib/symbol.py +++ b/pwndbg/gdblib/symbol.py @@ -61,14 +61,15 @@ if "/usr/lib/debug" not in _get_debug_file_directory(): @pwndbg.lib.memoize.reset_on_objfile def get(address: int, gdb_only=False) -> str: """ - Retrieve the name for the symbol located at `address` + Retrieve the name for the symbol located at `address` - either from GDB or from IDA sync + Passing `gdb_only=True` """ - # Fast path - if address < pwndbg.gdblib.memory.MMAP_MIN_ADDR or address >= ((1 << 64) - 1): - return "" + # Note: we do not return "" on `address < pwndbg.gdblib.memory.MMAP_MIN_ADDR` + # because this may be used to find out the symbol name on PIE binaries that weren't started yet + # and then their symbol addresses can be found by GDB on their (non-rebased) offsets - # Don't look up stack addresses - if pwndbg.gdblib.stack.find(address): + # Fast path: GDB's `info symbol` returns 'Numeric constant too large' here + if address >= ((1 << 64) - 1): return "" # This sucks, but there's not a GDB API for this. diff --git a/tests/gdb-tests/tests/binaries/makefile b/tests/gdb-tests/tests/binaries/makefile index 2cd78bb58..2b005d1de 100644 --- a/tests/gdb-tests/tests/binaries/makefile +++ b/tests/gdb-tests/tests/binaries/makefile @@ -38,8 +38,9 @@ GLIBC_2_33=$(PWD)/glibcs/2.33 .PHONY : all clean -all: $(LINKED) $(LINKED_ASM) $(COMPILED_GO) +CUSTOM_TARGETS = reference_bin_pie.out reference_bin_nopie.out +all: $(LINKED) $(LINKED_ASM) $(COMPILED_GO) $(CUSTOM_TARGETS) %.out : %.c @echo "[+] Building '$@'" @@ -97,4 +98,10 @@ clean : @rm -f $(COMPILED) $(LINKED) $(COMPILED_ASM) $(LINKED_ASM) $(COMPILED_GO) -reference-binary.out: EXTRA_FLAGS := -Dexample=1 +reference_bin_pie.out: reference-binary.c + @echo "[+] Building reference_bin_pie.out" + ${ZIGCC} -fpie -o reference_bin_pie.out reference-binary.c + +reference_bin_nopie.out: reference-binary.c + @echo "[+] Building reference_bin_nopie.out" + ${ZIGCC} -fno-pie -o reference_bin_nopie.out reference-binary.c diff --git a/tests/gdb-tests/tests/test_commands_elf.py b/tests/gdb-tests/tests/test_commands_elf.py new file mode 100644 index 000000000..522afc4bc --- /dev/null +++ b/tests/gdb-tests/tests/test_commands_elf.py @@ -0,0 +1,70 @@ +import re + +import gdb +import pytest + +import tests + +NO_SECTS_BINARY = tests.binaries.get("gosample.x86") +PIE_BINARY_WITH_PLT = "reference_bin_pie.out" +NOPIE_BINARY_WITH_PLT = "reference_bin_nopie.out" + + +def test_commands_plt_gotplt_got_when_no_sections(start_binary): + start_binary(NO_SECTS_BINARY) + + # elf.py commands + assert gdb.execute("plt", to_string=True) == "Could not find section .plt\n" + assert gdb.execute("gotplt", to_string=True) == "Could not find section .got.plt\n" + + # got.py command + assert gdb.execute("got", to_string=True) == "NO JUMP_SLOT entries available in the GOT\n" + + +@pytest.mark.parametrize( + "binary_name,is_pie", ((PIE_BINARY_WITH_PLT, True), (NOPIE_BINARY_WITH_PLT, False)) +) +def test_command_plt(binary_name, is_pie): + binary = tests.binaries.get(binary_name) + gdb.execute(f"file {binary}") + + out = gdb.execute("plt", to_string=True).splitlines() + + assert len(out) == 2 + assert re.match(r"Section \.plt 0x[0-9a-f]+-0x[0-9a-f]+:", out[0]) + assert re.match(r"0x[0-9a-f]+: puts@plt", out[1]) + + gdb.execute("starti") + + out2 = gdb.execute("plt", to_string=True).splitlines() + + if is_pie: + assert out != out2 + else: + assert out == out2 + + assert len(out2) == 2 + assert re.match(r"Section \.plt 0x[0-9a-f]+-0x[0-9a-f]+:", out2[0]) + assert re.match(r"0x[0-9a-f]+: puts@plt", out2[1]) + + +@pytest.mark.parametrize( + "binary_name,is_pie", ((PIE_BINARY_WITH_PLT, True), (NOPIE_BINARY_WITH_PLT, False)) +) +def test_command_got(binary_name, is_pie): + binary = tests.binaries.get(binary_name) + gdb.execute(f"file {binary}") + + out = gdb.execute("got", to_string=True).splitlines() + + assert out == ["got: The program is not being run."] + + gdb.execute("starti") + + out2 = gdb.execute("got", to_string=True).splitlines() + + assert out != out2 + + assert len(out2) == 2 + assert out2[0] == "GOT protection: Full RELRO | GOT functions: 1" + assert re.match(r"\[0x[0-9a-f]+\] puts@GLIBC_[0-9.]+ -> .*", out2[1])