Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --libc libc.so argument to pwn template #2212

Merged
merged 18 commits into from Jul 26, 2023

Conversation

peace-maker
Copy link
Member

This adds a --libc <path/to/libc.so> option to the pwn template pwnup template command which generates a libc variable to use in the script pointing to an ELF object of the passed libc.

In addition to this, it adds code to run the target binary using the provided libc inspired by pwninit. This is done in 3 steps:

  1. Lookup and download the matching ld.so and libraries from the official archive source libc.rip used to get the libc (libc_url).
  2. Try to unstrip the libc using debuginfod.
  3. Modify a copy of the challenge binary to use the right interpreter and RPATH using patchelf.

The generated template code looks like this:

# Use the specified remote libc version unless explicitly told to use the
# local system version with the `LOCAL_LIBC` argument.
# ./exploit.py LOCAL LOCAL_LIBC
if args.LOCAL_LIBC:
    libc = exe.libc
elif args.LOCAL:
    library_path = libcdb.download_libraries('./libc.so.6')
    if library_path:
        exe = context.binary = ELF.patch_custom_libraries('./challenge', library_path)
        libc = exe.libc
    else:
        libc = ELF('./libc.so.6')
else:
    libc = ELF('./libc.so.6')

This is a bit bulky - suggestions welcome!

The default behavior is to use the specified libc instead of the system wide one when running the script locally. You can pass the LOCAL_LIBC argument to start the process with the system libc. The libraries are downloaded on first run of the exploit script and cached for subsequent executions. That way exploits stay portable given the challenge binary and initial libc.

The current implementation only supports .deb and .pkg.tar.* packages and could be improved to search launchpad.net like pwninit does. It might be useful to pass a libc's build id to --libc and save it in the script instead of requiring an initial libc.so binary as well? That can be added later though.

Closes #2076
Supercedes #2082

Shells out to the `patchelf` tool to patch the ELF's RUNPATH. This lets the dynamic loader look for needed shared libraries in the given path first before the system libraries when running the binary.
Shells out to the `patchelf` tool to patch the ELF's PT_INTERP segment. This allows to change the ld.so used when running the binary.
A helper function to patch the ELF such that it uses the dynamic loader and other libraries in the given folder.
Download the matching libraries for the given libc binary and cache them in a local directory using `libcdb.download_libraries()`. The libraries are looked up using libc.rip and fetched from the official package repositories if available.

Only .deb and .pkg.tar.* packages are currently supported (Debian/Ubuntu, Arch).
This generates code into the template which allows you to run the binary using the given libc.

The foreign libc is used by default, but you can choose to run the binary against your system's local libc using the `LOCAL_LIBC` command line argument when executing the exploit script.
@Arusekk
Copy link
Member

Arusekk commented Jul 2, 2023

One useful thought I have is enumerating all libc builds on the host system in some useful order before downloading them from the net (possibly the default system libc and libc from every running docker container/image; it might surely be annoying if it requires sudo, but if containerd is accessible through docker group for example, it can be useful if the original package goes away or the connectivity is unimpressive).

The installation instructions for patchelf should mention installing from distro IMO; many pwntools users are not proficient with their own system. I think I will craft a PR adding more such user-friendly messages soon™.

I also tend to use unshare -cR. ./chal for exploits, setting up a minimal chroot environment without patching anything, but the most recent GDB is still always confused to see a process travel across namespaces and can even crash because of that, so I am not recommending it, just noting for reference. Just wondering, how reliable is the patchelf approach, BTW? Did you also have any such issues looking like GDB: protocol error: Packet 'g' reply too short: ABC123BC1337DEADBEEF and the whole debugging session derailing regardless of active GDB plugins?

@peace-maker
Copy link
Member Author

Looking for all available local libcs sounds good. Maybe your system libc is the one you're trying to run as.

I've seen that bug as well a few times, but not on Ubuntu 22.04 anymore. I think setting the sysroot helped, but not sure.

@peace-maker
Copy link
Member Author

Would you add another dependency to access docker or shell out to the commandline tools?

@Arusekk
Copy link
Member

Arusekk commented Jul 9, 2023

Honestly, however nice, I can live without docker images inspection for now. Let's not bother with implementing it, I do not think the benefit would be that great after all.

From other things, I just found pyliblzma, which looks like a better alternative to pylzma (supposedly works and should be compatible to stdlib lzma from py3), and therefore could be used for py2 without shelling out to xz.
(Although if shelling out, we could use subprocess.check_output() instead of our process().)
We could technically also subclass the relevant class from tarfile module then, and use logic there instead of ifing away every possible archive extension.

I would also prefer to hide py2 hacks inside if six.PY2 rather than hiding the normal code in if six.PY3 (ready for being py4-compatible 😉 ).

@peace-maker
Copy link
Member Author

I agree, harvesting docker images requires more thought. I wouldn't want to have to run my exploit as root e.g.

The pyliblzma package appears to be broken 😞

pwntools@eeac16ac3eb6:~$ sudo apt install liblzma-dev
pwntools@eeac16ac3eb6:~$ pip2 install pyliblzma
pwntools@eeac16ac3eb6:~$ python2.7
Python 2.7.18 (default, Jul  1 2022, 10:30:50) 
[GCC 11.2.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import lzma
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: /home/pwntools/.local/lib/python2.7/site-packages/lzma.so: undefined symbol: lzma_alone_encoder

We'll have to keep using the xz utility then.

@Arusekk
Copy link
Member

Arusekk commented Jul 10, 2023 via email

@peace-maker
Copy link
Member Author

I'm using our base Dockerfile make -C extra/docker base && docker run -it --rm pwntools/pwntools:base bash which is based on jammy.

pwnlib/libcdb.py Outdated Show resolved Hide resolved
peace-maker and others added 2 commits July 10, 2023 09:30
Co-authored-by: Arusekk <arek_koz@o2.pl>
This mimics the way io12/pwninit obtains the ld.so.
If the download from libc.rip fails, try launchpad.net.
@peace-maker peace-maker requested a review from Arusekk July 24, 2023 08:27
@peace-maker peace-maker added this to the 4.12.0 milestone Jul 24, 2023
@peace-maker
Copy link
Member Author

The pylint warning is a false-positive, since filter returns a list in Python 2 instead of an iterable. I could # pylint: disable-next=unsubscriptable-object or wrap it in list(filter..) again to please the linter?

pwnlib/libcdb.py Outdated Show resolved Hide resolved
Co-authored-by: Arusekk <arek_koz@o2.pl>
@Arusekk Arusekk merged commit 3e4e8b7 into Gallopsled:dev Jul 26, 2023
10 of 11 checks passed
@peace-maker peace-maker deleted the download_libc_libraries branch July 26, 2023 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add libc to pwn template
3 participants