From 6a38ded24e385cb49b09d8b623035e677ad12ad0 Mon Sep 17 00:00:00 2001 From: Alan Li <61896187+lebr0nli@users.noreply.github.com> Date: Wed, 7 Feb 2024 19:38:13 +0800 Subject: [PATCH] Fix the bug when showing the state of i386 GOT (#2017) * Fix the bug when showing the state of i386 GOT * Skip the test if binary can't be run --- pwndbg/commands/got.py | 22 +++++++++++++------ tests/gdb-tests/tests/binaries/makefile | 6 +++++- tests/gdb-tests/tests/test_commands_elf.py | 25 ++++++++++++++-------- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/pwndbg/commands/got.py b/pwndbg/commands/got.py index 500d75398..30b3873b9 100644 --- a/pwndbg/commands/got.py +++ b/pwndbg/commands/got.py @@ -136,15 +136,23 @@ def _got(path: str, accept_readonly: bool, symbol_filter: str) -> None: # Parse the output of readelf line by line for category, lines in got_entry.items(): for line in lines: - # line might be something like: - # 00000000001ec018 0000000000000025 R_X86_64_IRELATIVE a0480 - # or something like: - # 00000000001ec030 0000020a00000007 R_X86_64_JUMP_SLOT 000000000009ae80 realloc@@GLIBC_2.2.5 + 0 - offset, _, rtype, *rest = line.split()[:5] - if len(rest) == 1: - value = rest[0] + # There are 5 fields in the output of readelf: + # "Offset", "Info", "Type", "Sym. Value", and "Symbol's Name" + # We only care about "Offset", "Sym. Value" and "Symbol's Name" here + offset, _, _, *rest = line.split()[:5] + if len(rest) < 2: + # "Sym. Value" or "Symbol's Name" are not present in this case + # The output of readelf might look like this (missing both value and name): + # 00004e88 00000008 R_386_RELATIVE + # or something like this (only missing name): + # 00000000001ec018 0000000000000025 R_X86_64_IRELATIVE a0480 + # TODO: Is it possible that we are missing the value but not the name? + value = rest[0] if rest else "" name = "" else: + # Every fields are present in this case + # The output of readelf might look like this: + # 00000000001ec030 0000020a00000007 R_X86_64_JUMP_SLOT 000000000009ae80 realloc@@GLIBC_2.2.5 + 0 value, name = rest address = int(offset, 16) + bin_base_offset # TODO/FIXME: This check might not work correctly if we failed to get the correct vmmap result diff --git a/tests/gdb-tests/tests/binaries/makefile b/tests/gdb-tests/tests/binaries/makefile index 853c3dcc2..b7e7cb00b 100644 --- a/tests/gdb-tests/tests/binaries/makefile +++ b/tests/gdb-tests/tests/binaries/makefile @@ -38,7 +38,7 @@ GLIBC_2_33=$(PWD)/glibcs/2.33 .PHONY : all clean -CUSTOM_TARGETS = reference_bin_pie.out reference_bin_nopie.out symbol_1600_and_752.out initialized_heap_x64.out initialized_heap_i386_big.out linked_lists.out +CUSTOM_TARGETS = reference_bin_pie.out reference_bin_nopie.out reference_bin_nopie.i386.out symbol_1600_and_752.out initialized_heap_x64.out initialized_heap_i386_big.out linked_lists.out all: $(LINKED) $(LINKED_ASM) $(COMPILED_GO) $(CUSTOM_TARGETS) @@ -146,5 +146,9 @@ reference_bin_nopie.out: reference-binary.c @echo "[+] Building reference_bin_nopie.out" ${ZIGCC} -fno-pie -o reference_bin_nopie.out reference-binary.c +reference_bin_nopie.i386.out: reference-binary.c + @echo "[+] Building reference_bin_nopie.i386.out" + ${ZIGCC} -fpie -target i386-linux-gnu -o reference_bin_nopie.i386.out reference-binary.c + symbol_1600_and_752.out: symbol_1600_and_752.cpp ${CXX} -O0 -ggdb -Wno-pmf-conversions symbol_1600_and_752.cpp -o symbol_1600_and_752.out diff --git a/tests/gdb-tests/tests/test_commands_elf.py b/tests/gdb-tests/tests/test_commands_elf.py index f48f88fe1..ec335878f 100644 --- a/tests/gdb-tests/tests/test_commands_elf.py +++ b/tests/gdb-tests/tests/test_commands_elf.py @@ -11,6 +11,7 @@ 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" +NOPIE_I386_BINARY_WITH_PLT = "reference_bin_nopie.i386.out" def test_commands_plt_gotplt_got_when_no_sections(start_binary): @@ -105,12 +106,18 @@ def test_command_got_for_target_binary(binary_name, is_pie): assert re.match(r"\[0x[0-9a-f]+\] puts@GLIBC_[0-9.]+ -> .*", out[4]) -def test_command_got_for_target_binary_and_loaded_library(): - binary = tests.binaries.get(NOPIE_BINARY_WITH_PLT) +@pytest.mark.parametrize( + "binary_name", (NOPIE_BINARY_WITH_PLT, NOPIE_I386_BINARY_WITH_PLT), ids=["x86-64", "i386"] +) +def test_command_got_for_target_binary_and_loaded_library(binary_name): + binary = tests.binaries.get(binary_name) gdb.execute(f"file {binary}") gdb.execute("break main") - gdb.execute("starti") + try: + gdb.execute("starti") + except gdb.error: + pytest.skip("Test not supported on this platform.") # Before loading libc, we can't find .got.plt of libc out = gdb.execute("got -p libc", to_string=True).splitlines() @@ -120,7 +127,7 @@ def test_command_got_for_target_binary_and_loaded_library(): assert out[2] == "" assert out[3] == "No shared library matching the path filter found." assert out[4] == "Available shared libraries:" - assert out[5].endswith("/ld-linux-x86-64.so.2") + assert out[5].endswith(("/ld-linux-x86-64.so.2", "/ld-linux.so.2")) gdb.execute("continue") @@ -175,12 +182,12 @@ def test_command_got_for_target_binary_and_loaded_library(): assert re.match(r"\[0x[0-9a-f]+\] .*ABS.* -> .*", out[6 + i]) # Try filtering out path with "l", which should match every library - # First should be ld-linux-x86-64.so.2 + # First should be ld-linux(-x86-64)?.so.2 out = gdb.execute("got -p l", to_string=True).splitlines() assert out[0] == "Filtering by lib/objfile path: l" assert out[1] == "Filtering out read-only entries (display them with -r or --show-readonly)" assert out[2] == "" - assert re.match(r"State of the GOT of .*/ld-linux-x86-64.so.2:", out[3]) + assert re.match(r"State of the GOT of .*/ld-linux(-x86-64)?.so.2:", out[3]) m = re.match( r"GOT protection: (?:Partial|Full) RELRO \| Found (\d+) GOT entries passing the filter", out[4], @@ -212,8 +219,8 @@ def test_command_got_for_target_binary_and_loaded_library(): assert out[4] == "" out = out[5:] - # Second should be ld-linux-x86-64.so.2 - assert re.match(r"State of the GOT of .*/ld-linux-x86-64.so.2:", out[0]) + # Second should be ld-linux(-x86-64)?.so.2 + assert re.match(r"State of the GOT of .*/ld-linux(-x86-64)?.so.2:", out[0]) m = re.match( r"GOT protection: (?:Partial|Full) RELRO \| Found (\d+) GOT entries passing the filter", out[1], @@ -243,7 +250,7 @@ def test_command_got_for_target_binary_and_loaded_library(): assert out[3] == "GOT protection: Full RELRO | Found 1 GOT entries passing the filter" assert re.match(r"\[0x[0-9a-f]+\] puts@GLIBC_[0-9.]+ -> .*", out[4]) assert out[5] == "" - assert re.match(r"State of the GOT of .*/ld-linux-x86-64.so.2:", out[6]) + assert re.match(r"State of the GOT of .*/ld-linux(-x86-64)?.so.2:", out[6]) assert re.match( r"GOT protection: (?:Partial|Full) RELRO \| Found 0 GOT entries passing the filter", out[7] )