Hopefully fix the bug when using LD_PRELOAD to load libc (#1602)

* Fix the bug when using LD_PRELOAD to load libc

The heap heuristics will try to find `libc.so.6` in the output of `info sharedlibrary`, but if we load libc with `LD_PRELOAD`, the filename of the libc might not be `libc.so.6`.

* Add test for `glibc.get_libc_filename_from_info_sharedlibrary`
pull/1607/head
Alan Li 3 years ago committed by GitHub
parent e642461941
commit 5ecd5d000f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -68,11 +68,38 @@ def _get_version() -> Optional[Tuple[int, ...]]:
@pwndbg.lib.memoize.reset_on_start
@pwndbg.lib.memoize.reset_on_objfile
def get_libc_filename_from_info_sharedlibrary() -> Optional[str]:
"""
Get the filename of the libc by parsing the output of `info sharedlibrary`.
"""
# Try to parse the output of `info sharedlibrary`:
# pwndbg> |info sharedlibrary| grep libc
# 0x00007f9ade418700 0x00007f9ade58f47d Yes ./libc.so.6
# Or:
# pwndbg> |info sharedlibrary| grep libc
# 0x00007f9ade418700 0x00007f9ade58f47d Yes (*) ./libc.so.6
possible_libc_path = []
for line in pwndbg.gdblib.info.sharedlibrary().splitlines()[1:]:
filename = line.split(maxsplit=3)[-1].lstrip("(*)").lstrip()
# Is it possible that the libc is not called `libc.so.6`?
if os.path.basename(filename) == "libc.so.6":
return filename
if line.startswith("("):
# footer line:
# (*): Shared library is missing debugging information.
break
path = line.split(maxsplit=3)[-1].lstrip("(*)").lstrip()
basename = os.path.basename(
path[7:] if path.startswith("target:") else path
) # "target:" prefix is for remote debugging
if basename == "libc.so.6":
# The default filename of libc should be libc.so.6, so if we found it, we just return it directly.
return path
elif re.search(r"^libc6?[-_\.]", basename):
# Maybe user loaded the libc with LD_PRELOAD.
# Some common libc names: libc-2.36.so, libc6_2.36-0ubuntu4_amd64.so, libc.so
possible_libc_path.append(
path
) # We don't return it, maybe there is a libc.so.6 and this match is just a false positive.
# TODO: This might fail if user use LD_PRELOAD to load libc with a weird name or there are multiple shared libraries match the pattern.
# (But do we really need to support this case? Maybe we can wait until users really need it :P.)
if possible_libc_path:
return possible_libc_path[0] # just return the first match for now :)
return None

@ -0,0 +1,56 @@
import os
import shutil
import tempfile
import gdb
import pytest
import pwndbg.gdblib.info
import pwndbg.glibc
import tests
# We used the same binary as heap tests since it will use libc, and many functions are mainly for debugging the heap
HEAP_MALLOC_CHUNK = tests.binaries.get("heap_malloc_chunk.out")
@pytest.mark.parametrize(
"have_debugging_information", [True, False], ids=["does-not-have-(*)", "have-(*)"]
)
def test_parsing_info_sharedlibrary_to_find_libc_filename(start_binary, have_debugging_information):
# Check if we can find the libc if nothing special happens
if not have_debugging_information:
# Make sure the (*) in the output of `info sharedlibrary` won't affect the result
gdb.execute("set debug-file-directory")
start_binary(HEAP_MALLOC_CHUNK)
gdb.execute("break break_here")
gdb.execute("continue")
if not have_debugging_information:
assert "(*)" in pwndbg.gdblib.info.sharedlibrary()
libc_path = pwndbg.glibc.get_libc_filename_from_info_sharedlibrary()
assert libc_path is not None
# Create 3 copies of the libc with the filenames: libc-2.36.so, libc6_2.36-0ubuntu4_amd64.so, libc.so
# Note: The version in the above filename doesn't matter, just some tests for the common libc names we might use with LD_PRELOAD
test_libc_names = ["libc-2.36.so", "libc6_2.36-0ubuntu4_amd64.so", "libc.so"]
with tempfile.TemporaryDirectory() as tmp_dir:
for test_libc_name in test_libc_names:
test_libc_path = os.path.join(tmp_dir, test_libc_name)
shutil.copy(libc_path, test_libc_path)
gdb.execute(f"set environment LD_PRELOAD={test_libc_path}")
start_binary(HEAP_MALLOC_CHUNK)
gdb.execute("break break_here")
gdb.execute("continue")
# Check if we can find the libc loaded by LD_PRELOAD
if not have_debugging_information:
assert "(*)" in pwndbg.gdblib.info.sharedlibrary()
assert pwndbg.glibc.get_libc_filename_from_info_sharedlibrary() == test_libc_path
# Unfortunatly, if we used LD_PRELOAD to load libc, we might cannot find the libc's filename
# In this case, the function should return None instead of crashing
test_libc_path = os.path.join(tmp_dir, "a_weird_name_that_does_not_look_like_a_1ibc.so")
shutil.copy(libc_path, test_libc_path)
gdb.execute(f"set environment LD_PRELOAD={test_libc_path}")
start_binary(HEAP_MALLOC_CHUNK)
gdb.execute("break break_here")
gdb.execute("continue")
assert pwndbg.glibc.get_libc_filename_from_info_sharedlibrary() is None
Loading…
Cancel
Save