Skip to content

Commit 3e4e8b7

Browse files
peace-makerArusekk
andauthored
Add --libc libc.so argument to pwn template (Gallopsled#2212)
* Add ELF.set_runpath() 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. * Add ELF.set_interpreter() 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. * Add convenience wrapper to set runpath & interpreter A helper function to patch the ELF such that it uses the dynamic loader and other libraries in the given folder. * Add method to download libraries matching a libc 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). * Add --libc argument to pwnup template 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. * Work around Python 2 tarfile not supporting xz * Fix crash when libc wasn't found on libc.rip * Update README * Use subprocess.check_output instead of pwnlib.process * Special case Python 2 instead of 3 * Only catch Exceptions instead of everything Co-authored-by: Arusekk <[email protected]> * Check launchpad.net for Ubuntu libcs This mimics the way io12/pwninit obtains the ld.so. If the download from libc.rip fails, try launchpad.net. * Hide comment in template when --quiet * Please confused pylint in PY2 context Co-authored-by: Arusekk <[email protected]> --------- Co-authored-by: Arusekk <[email protected]>
1 parent 0696c5c commit 3e4e8b7

File tree

8 files changed

+414
-58
lines changed

8 files changed

+414
-58
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ jobs:
7474
binutils-sparc64-linux-gnu \
7575
gcc-multilib \
7676
libc6-dbg \
77-
elfutils
77+
elfutils \
78+
patchelf
7879
7980
- name: Testing Corefiles
8081
run: |

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,13 @@ The table below shows which release corresponds to each branch, and what date th
7171
- [#2117][2117] Add -p (--prefix) and -s (--separator) arguments to `hex` command
7272
- [#2221][2221] Add shellcraft.sleep template wrapping SYS_nanosleep
7373
- [#2219][2219] Fix passing arguments on the stack in shellcraft syscall template
74+
- [#2212][2212] Add `--libc libc.so` argument to `pwn template` command
7475

7576
[2202]: https://github.com/Gallopsled/pwntools/pull/2202
7677
[2117]: https://github.com/Gallopsled/pwntools/pull/2117
7778
[2221]: https://github.com/Gallopsled/pwntools/pull/2221
7879
[2219]: https://github.com/Gallopsled/pwntools/pull/2219
80+
[2212]: https://github.com/Gallopsled/pwntools/pull/2212
7981

8082
## 4.11.0 (`beta`)
8183

docs/source/elf/elf.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from pwn import *
44
from glob import glob
55
from pwnlib.elf.maps import CAT_PROC_MAPS_EXIT
6+
import shutil
67

78
:mod:`pwnlib.elf.elf` --- ELF Files
89
===========================================================

pwnlib/commandline/template.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
parser.add_argument('--port', help='Remote port / SSH port', type=int)
1919
parser.add_argument('--user', help='SSH Username')
2020
parser.add_argument('--pass', '--password', help='SSH Password', dest='password')
21+
parser.add_argument('--libc', help='Path to libc binary to use')
2122
parser.add_argument('--path', help='Remote path of file on SSH server')
2223
parser.add_argument('--quiet', help='Less verbose template comments', action='store_true')
2324
parser.add_argument('--color', help='Print the output in color', choices=['never', 'always', 'auto'], default='auto')
@@ -53,6 +54,7 @@ def main(args):
5354
args.port,
5455
args.user,
5556
args.password,
57+
args.libc,
5658
args.path,
5759
args.quiet)
5860

pwnlib/data/templates/pwnup.mako

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<%page args="binary, host=None, port=None, user=None, password=None, remote_path=None, quiet=False"/>\
1+
<%page args="binary, host=None, port=None, user=None, password=None, libc=None, remote_path=None, quiet=False"/>\
22
<%
33
import os
44
import sys
@@ -31,6 +31,7 @@ elif host and not port:
3131
remote_path = remote_path or exe
3232
password = password or 'secret1234'
3333
binary_repr = repr(binary)
34+
libc_repr = repr(libc)
3435
%>\
3536
#!/usr/bin/env python3
3637
# -*- coding: utf-8 -*-
@@ -83,6 +84,35 @@ if not args.LOCAL:
8384
shell.set_working_directory(symlink=True)
8485
%endif
8586

87+
%if libc:
88+
%if not quiet:
89+
# Use the specified remote libc version unless explicitly told to use the
90+
# local system version with the `LOCAL_LIBC` argument.
91+
# ./exploit.py LOCAL LOCAL_LIBC
92+
%endif
93+
if args.LOCAL_LIBC:
94+
libc = exe.libc
95+
%if host:
96+
elif args.LOCAL:
97+
%else:
98+
else:
99+
%endif
100+
library_path = libcdb.download_libraries(${libc_repr})
101+
if library_path:
102+
%if ctx.binary:
103+
exe = context.binary = ELF.patch_custom_libraries(${binary_repr}, library_path)
104+
%else:
105+
exe = ELF.patch_custom_libraries(exe, library_path)
106+
%endif
107+
libc = exe.libc
108+
else:
109+
libc = ELF(${libc_repr})
110+
%if host:
111+
else:
112+
libc = ELF(${libc_repr})
113+
%endif
114+
%endif
115+
86116
%if host:
87117
def start_local(argv=[], *a, **kw):
88118
'''Execute the target binary locally'''

pwnlib/elf/elf.py

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
from pwnlib.util import misc
8383
from pwnlib.util import packing
8484
from pwnlib.util.fiddling import unhex
85-
from pwnlib.util.misc import align, align_down
85+
from pwnlib.util.misc import align, align_down, which
8686
from pwnlib.util.sh_string import sh_string
8787

8888
log = getLogger(__name__)
@@ -2246,3 +2246,122 @@ def disable_nx(self):
22462246
return
22472247

22482248
log.error("Could not find PT_GNU_STACK, stack should already be executable")
2249+
2250+
@staticmethod
2251+
def set_runpath(exepath, runpath):
2252+
r"""set_runpath(str, str) -> ELF
2253+
2254+
Patches the RUNPATH of the ELF to the given path using the `patchelf utility <https://github.com/NixOS/patchelf>`_.
2255+
2256+
The dynamic loader will look for any needed shared libraries in the given path first,
2257+
before trying the system library paths. This is useful to run a binary with a different
2258+
libc binary.
2259+
2260+
Arguments:
2261+
exepath(str): Path to the binary to patch.
2262+
runpath(str): Path containing the needed libraries.
2263+
2264+
Returns:
2265+
A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
2266+
2267+
Example:
2268+
2269+
>>> tmpdir = tempfile.mkdtemp()
2270+
>>> ls_path = os.path.join(tmpdir, 'ls')
2271+
>>> _ = shutil.copy(which('ls'), ls_path)
2272+
>>> e = ELF.set_runpath(ls_path, './libs')
2273+
>>> e.runpath == b'./libs'
2274+
True
2275+
"""
2276+
if not which('patchelf'):
2277+
log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
2278+
return None
2279+
try:
2280+
subprocess.check_output(['patchelf', '--set-rpath', runpath, exepath], stderr=subprocess.STDOUT)
2281+
except subprocess.CalledProcessError as e:
2282+
log.failure('Patching RUNPATH failed (%d): %r', e.returncode, e.stdout)
2283+
return ELF(exepath, checksec=False)
2284+
2285+
@staticmethod
2286+
def set_interpreter(exepath, interpreter_path):
2287+
r"""set_interpreter(str, str) -> ELF
2288+
2289+
Patches the interpreter of the ELF to the given binary using the `patchelf utility <https://github.com/NixOS/patchelf>`_.
2290+
2291+
When running the binary, the new interpreter will be used to load the ELF.
2292+
2293+
Arguments:
2294+
exepath(str): Path to the binary to patch.
2295+
interpreter_path(str): Path to the ld.so dynamic loader.
2296+
2297+
Returns:
2298+
A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
2299+
2300+
Example:
2301+
>>> tmpdir = tempfile.mkdtemp()
2302+
>>> ls_path = os.path.join(tmpdir, 'ls')
2303+
>>> _ = shutil.copy(which('ls'), ls_path)
2304+
>>> e = ELF.set_interpreter(ls_path, '/tmp/correct_ld.so')
2305+
>>> e.linker == b'/tmp/correct_ld.so'
2306+
True
2307+
"""
2308+
# patch the interpreter
2309+
if not which('patchelf'):
2310+
log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
2311+
return None
2312+
try:
2313+
subprocess.check_output(['patchelf', '--set-interpreter', interpreter_path, exepath], stderr=subprocess.STDOUT)
2314+
except subprocess.CalledProcessError as e:
2315+
log.failure('Patching interpreter failed (%d): %r', e.returncode, e.stdout)
2316+
return ELF(exepath, checksec=False)
2317+
2318+
@staticmethod
2319+
def patch_custom_libraries(exe_path, custom_library_path, create_copy=True, suffix='_remotelibc'):
2320+
r"""patch_custom_libraries(str, str, bool, str) -> ELF
2321+
2322+
Looks for the interpreter binary in the given path and patches the binary to use
2323+
it if available. Also patches the RUNPATH to the given path using the `patchelf utility <https://github.com/NixOS/patchelf>`_.
2324+
2325+
Arguments:
2326+
exe_path(str): Path to the binary to patch.
2327+
custom_library_path(str): Path to a folder containing the libraries.
2328+
create_copy(bool): Create a copy of the binary and apply the patches to the copy.
2329+
suffix(str): Suffix to append to the filename when creating the copy to patch.
2330+
2331+
Returns:
2332+
A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
2333+
2334+
Example:
2335+
2336+
>>> tmpdir = tempfile.mkdtemp()
2337+
>>> linker_path = os.path.join(tmpdir, 'ld-mock.so')
2338+
>>> write(linker_path, b'loader')
2339+
>>> ls_path = os.path.join(tmpdir, 'ls')
2340+
>>> _ = shutil.copy(which('ls'), ls_path)
2341+
>>> e = ELF.patch_custom_libraries(ls_path, tmpdir)
2342+
>>> e.runpath.decode() == tmpdir
2343+
True
2344+
>>> e.linker.decode() == linker_path
2345+
True
2346+
"""
2347+
if not which('patchelf'):
2348+
log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
2349+
return None
2350+
2351+
# Create a copy of the ELF to patch instead of the original file.
2352+
if create_copy:
2353+
import shutil
2354+
patched_path = exe_path + suffix
2355+
shutil.copy2(exe_path, patched_path)
2356+
exe_path = patched_path
2357+
2358+
# Set interpreter in ELF to the one in the library path.
2359+
interpreter_name = [filename for filename in os.listdir(custom_library_path) if filename.startswith('ld-')]
2360+
if interpreter_name:
2361+
interpreter_path = os.path.realpath(os.path.join(custom_library_path, interpreter_name[0]))
2362+
ELF.set_interpreter(exe_path, interpreter_path)
2363+
else:
2364+
log.warn("Couldn't find ld.so in library path. Interpreter not set.")
2365+
2366+
# Set RUNPATH to library path in order to find other libraries.
2367+
return ELF.set_runpath(exe_path, custom_library_path)

0 commit comments

Comments
 (0)