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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ jobs:
binutils-sparc64-linux-gnu \
gcc-multilib \
libc6-dbg \
elfutils
elfutils \
patchelf

- name: Testing Corefiles
run: |
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,13 @@ The table below shows which release corresponds to each branch, and what date th
- [#2117][2117] Add -p (--prefix) and -s (--separator) arguments to `hex` command
- [#2221][2221] Add shellcraft.sleep template wrapping SYS_nanosleep
- [#2219][2219] Fix passing arguments on the stack in shellcraft syscall template
- [#2212][2212] Add `--libc libc.so` argument to `pwn template` command

[2202]: https://github.com/Gallopsled/pwntools/pull/2202
[2117]: https://github.com/Gallopsled/pwntools/pull/2117
[2221]: https://github.com/Gallopsled/pwntools/pull/2221
[2219]: https://github.com/Gallopsled/pwntools/pull/2219
[2212]: https://github.com/Gallopsled/pwntools/pull/2212

## 4.11.0 (`beta`)

Expand Down
1 change: 1 addition & 0 deletions docs/source/elf/elf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pwn import *
from glob import glob
from pwnlib.elf.maps import CAT_PROC_MAPS_EXIT
import shutil

:mod:`pwnlib.elf.elf` --- ELF Files
===========================================================
Expand Down
2 changes: 2 additions & 0 deletions pwnlib/commandline/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
parser.add_argument('--port', help='Remote port / SSH port', type=int)
parser.add_argument('--user', help='SSH Username')
parser.add_argument('--pass', '--password', help='SSH Password', dest='password')
parser.add_argument('--libc', help='Path to libc binary to use')
parser.add_argument('--path', help='Remote path of file on SSH server')
parser.add_argument('--quiet', help='Less verbose template comments', action='store_true')
parser.add_argument('--color', help='Print the output in color', choices=['never', 'always', 'auto'], default='auto')
Expand Down Expand Up @@ -53,6 +54,7 @@ def main(args):
args.port,
args.user,
args.password,
args.libc,
args.path,
args.quiet)

Expand Down
32 changes: 31 additions & 1 deletion pwnlib/data/templates/pwnup.mako
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<%page args="binary, host=None, port=None, user=None, password=None, remote_path=None, quiet=False"/>\
<%page args="binary, host=None, port=None, user=None, password=None, libc=None, remote_path=None, quiet=False"/>\
<%
import os
import sys
Expand Down Expand Up @@ -31,6 +31,7 @@ elif host and not port:
remote_path = remote_path or exe
password = password or 'secret1234'
binary_repr = repr(binary)
libc_repr = repr(libc)
%>\
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
Expand Down Expand Up @@ -83,6 +84,35 @@ if not args.LOCAL:
shell.set_working_directory(symlink=True)
%endif

%if libc:
%if not quiet:
# 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
%endif
peace-maker marked this conversation as resolved.
Show resolved Hide resolved
if args.LOCAL_LIBC:
libc = exe.libc
%if host:
elif args.LOCAL:
%else:
else:
%endif
library_path = libcdb.download_libraries(${libc_repr})
if library_path:
%if ctx.binary:
exe = context.binary = ELF.patch_custom_libraries(${binary_repr}, library_path)
%else:
exe = ELF.patch_custom_libraries(exe, library_path)
%endif
libc = exe.libc
else:
libc = ELF(${libc_repr})
%if host:
else:
libc = ELF(${libc_repr})
%endif
%endif

%if host:
def start_local(argv=[], *a, **kw):
'''Execute the target binary locally'''
Expand Down
121 changes: 120 additions & 1 deletion pwnlib/elf/elf.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
from pwnlib.util import misc
from pwnlib.util import packing
from pwnlib.util.fiddling import unhex
from pwnlib.util.misc import align, align_down
from pwnlib.util.misc import align, align_down, which
from pwnlib.util.sh_string import sh_string

log = getLogger(__name__)
Expand Down Expand Up @@ -2246,3 +2246,122 @@ def disable_nx(self):
return

log.error("Could not find PT_GNU_STACK, stack should already be executable")

@staticmethod
def set_runpath(exepath, runpath):
r"""set_runpath(str, str) -> ELF

Patches the RUNPATH of the ELF to the given path using the `patchelf utility <https://github.com/NixOS/patchelf>`_.

The dynamic loader will look for any needed shared libraries in the given path first,
before trying the system library paths. This is useful to run a binary with a different
libc binary.

Arguments:
exepath(str): Path to the binary to patch.
runpath(str): Path containing the needed libraries.

Returns:
A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.

Example:

>>> tmpdir = tempfile.mkdtemp()
>>> ls_path = os.path.join(tmpdir, 'ls')
>>> _ = shutil.copy(which('ls'), ls_path)
>>> e = ELF.set_runpath(ls_path, './libs')
>>> e.runpath == b'./libs'
True
"""
if not which('patchelf'):
log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
return None
try:
subprocess.check_output(['patchelf', '--set-rpath', runpath, exepath], stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
log.failure('Patching RUNPATH failed (%d): %r', e.returncode, e.stdout)
return ELF(exepath, checksec=False)

@staticmethod
def set_interpreter(exepath, interpreter_path):
r"""set_interpreter(str, str) -> ELF

Patches the interpreter of the ELF to the given binary using the `patchelf utility <https://github.com/NixOS/patchelf>`_.

When running the binary, the new interpreter will be used to load the ELF.

Arguments:
exepath(str): Path to the binary to patch.
interpreter_path(str): Path to the ld.so dynamic loader.

Returns:
A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.

Example:
>>> tmpdir = tempfile.mkdtemp()
>>> ls_path = os.path.join(tmpdir, 'ls')
>>> _ = shutil.copy(which('ls'), ls_path)
>>> e = ELF.set_interpreter(ls_path, '/tmp/correct_ld.so')
>>> e.linker == b'/tmp/correct_ld.so'
True
"""
# patch the interpreter
if not which('patchelf'):
log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
return None
try:
subprocess.check_output(['patchelf', '--set-interpreter', interpreter_path, exepath], stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
log.failure('Patching interpreter failed (%d): %r', e.returncode, e.stdout)
return ELF(exepath, checksec=False)

@staticmethod
def patch_custom_libraries(exe_path, custom_library_path, create_copy=True, suffix='_remotelibc'):
r"""patch_custom_libraries(str, str, bool, str) -> ELF

Looks for the interpreter binary in the given path and patches the binary to use
it if available. Also patches the RUNPATH to the given path using the `patchelf utility <https://github.com/NixOS/patchelf>`_.

Arguments:
exe_path(str): Path to the binary to patch.
custom_library_path(str): Path to a folder containing the libraries.
create_copy(bool): Create a copy of the binary and apply the patches to the copy.
suffix(str): Suffix to append to the filename when creating the copy to patch.

Returns:
A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.

Example:

>>> tmpdir = tempfile.mkdtemp()
>>> linker_path = os.path.join(tmpdir, 'ld-mock.so')
>>> write(linker_path, b'loader')
>>> ls_path = os.path.join(tmpdir, 'ls')
>>> _ = shutil.copy(which('ls'), ls_path)
>>> e = ELF.patch_custom_libraries(ls_path, tmpdir)
>>> e.runpath.decode() == tmpdir
True
>>> e.linker.decode() == linker_path
True
"""
if not which('patchelf'):
log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
return None

# Create a copy of the ELF to patch instead of the original file.
if create_copy:
import shutil
patched_path = exe_path + suffix
shutil.copy2(exe_path, patched_path)
exe_path = patched_path

# Set interpreter in ELF to the one in the library path.
interpreter_name = [filename for filename in os.listdir(custom_library_path) if filename.startswith('ld-')]
if interpreter_name:
interpreter_path = os.path.realpath(os.path.join(custom_library_path, interpreter_name[0]))
ELF.set_interpreter(exe_path, interpreter_path)
else:
log.warn("Couldn't find ld.so in library path. Interpreter not set.")

# Set RUNPATH to library path in order to find other libraries.
return ELF.set_runpath(exe_path, custom_library_path)
Loading
Loading