Skip to content

Commit cfc021d

Browse files
authored
Extract libraries from Docker image (#2479)
* feat: extract libraries from Docker image * docs: update CHANGELOG.md * fix: python2.7 compatibility * address comments * address linter
1 parent 9f92ed0 commit cfc021d

File tree

2 files changed

+91
-1
lines changed

2 files changed

+91
-1
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,15 @@ The table below shows which release corresponds to each branch, and what date th
7979
- [#2444][2444] Add `ELF.close()` to release resources
8080
- [#2413][2413] libcdb: improve the search speed of `search_by_symbol_offsets` in local libc-database
8181
- [#2470][2470] Fix waiting for gdb under WSL2
82+
- [#2479][2479] Support extracting libraries from Docker image in `pwn template`
8283

8384
[2471]: https://github.com/Gallopsled/pwntools/pull/2471
8485
[2358]: https://github.com/Gallopsled/pwntools/pull/2358
8586
[2457]: https://github.com/Gallopsled/pwntools/pull/2457
8687
[2444]: https://github.com/Gallopsled/pwntools/pull/2444
8788
[2413]: https://github.com/Gallopsled/pwntools/pull/2413
8889
[2470]: https://github.com/Gallopsled/pwntools/pull/2470
90+
[2479]: https://github.com/Gallopsled/pwntools/pull/2479
8991

9092
## 4.14.0 (`beta`)
9193

pwnlib/commandline/template.py

+89-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
from __future__ import absolute_import
22
from __future__ import division
3+
from __future__ import print_function
34

45
from pwn import *
56
from pwnlib.commandline import common
7+
from pwnlib.util.misc import which, parse_ldd_output, write
68

9+
from sys import stderr
710
from mako.lookup import TemplateLookup, Template
811

912
parser = common.parser_commands.add_parser(
@@ -32,18 +35,100 @@
3235
os.path.join(printable_data_path, "templates", "pwnup.mako"))
3336
parser.add_argument('--no-auto', help='Do not automatically detect missing binaries', action='store_false', dest='auto')
3437

38+
def get_docker_image_libraries():
39+
"""Tries to retrieve challenge libraries from a Docker image built from the Dockerfile in the current working directory.
40+
41+
The libraries are retrieved by parsing the output of running ldd on /bin/sh.
42+
Supports regular Docker images as well as jail images.
43+
"""
44+
with log.progress("Extracting challenge libraries from Docker image") as progress:
45+
if not which("docker"):
46+
progress.failure("docker command not found")
47+
return None, None
48+
# maps jail image name to the root directory of the child image
49+
jail_image_to_chroot_dir = {
50+
"pwn.red/jail": "/srv",
51+
}
52+
dockerfile = open("Dockerfile", "r").read()
53+
jail = None
54+
chroot_dir = "/"
55+
for jail_image in jail_image_to_chroot_dir:
56+
if re.search(r"^FROM %s" % jail_image, dockerfile, re.MULTILINE):
57+
jail = jail_image
58+
chroot_dir = jail_image_to_chroot_dir[jail_image]
59+
break
60+
try:
61+
progress.status("Building image")
62+
image_sha = subprocess.check_output(["docker", "build", "-q", "."], stderr=subprocess.PIPE, shell=False).decode().strip()
63+
64+
progress.status("Retrieving library paths")
65+
ldd_command = ["-c", "chroot %s /bin/sh -c 'ldd /bin/sh'" % chroot_dir]
66+
ldd_output = subprocess.check_output([
67+
"docker",
68+
"run",
69+
"--rm",
70+
"--entrypoint",
71+
"/bin/sh",
72+
] + (["--privileged"] if jail else []) + [
73+
image_sha,
74+
] + ldd_command,
75+
stderr=subprocess.PIPE,
76+
shell=False
77+
).decode()
78+
79+
libc, ld = None, None
80+
libc_basename, ld_basename = None, None
81+
for lib_path in parse_ldd_output(ldd_output):
82+
if "libc." in lib_path:
83+
libc = lib_path
84+
libc_basename = os.path.basename(lib_path)
85+
if "ld-" in lib_path:
86+
ld = lib_path
87+
ld_basename = os.path.basename(lib_path)
88+
89+
if not (libc and ld):
90+
progress.failure("Could not find libraries")
91+
return None, None
92+
93+
progress.status("Copying libraries to current directory")
94+
for filename, basename in zip((libc, ld), (libc_basename, ld_basename)):
95+
cat_command = ["-c", "chroot %s /bin/sh -c '/bin/cat %s'" % (chroot_dir, filename)]
96+
contents = subprocess.check_output([
97+
"docker",
98+
"run",
99+
"--rm",
100+
"--entrypoint",
101+
"/bin/sh",
102+
] + (["--privileged"] if jail else []) + [
103+
image_sha
104+
] + cat_command,
105+
stderr=subprocess.PIPE,
106+
shell=False
107+
)
108+
write(basename, contents)
109+
110+
except subprocess.CalledProcessError as e:
111+
print(e.stderr.decode())
112+
log.error("docker failed with status: %d" % e.returncode)
113+
114+
progress.success("Retrieved libraries from Docker image")
115+
return libc_basename, ld_basename
116+
35117
def detect_missing_binaries(args):
36118
log.info("Automatically detecting challenge binaries...")
37119
# look for challenge binary, libc, and ld in current directory
38120
exe, libc, ld = args.exe, args.libc, None
121+
has_dockerfile = False
39122
other_files = []
40-
for filename in os.listdir():
123+
for filename in os.listdir("."):
41124
if not os.path.isfile(filename):
42125
continue
43126
if not libc and ('libc-' in filename or 'libc.' in filename):
44127
libc = filename
45128
elif not ld and 'ld-' in filename:
46129
ld = filename
130+
elif filename == "Dockerfile":
131+
has_dockerfile = True
47132
else:
48133
if os.access(filename, os.X_OK):
49134
other_files.append(filename)
@@ -52,6 +137,9 @@ def detect_missing_binaries(args):
52137
exe = other_files[0]
53138
elif len(other_files) > 1:
54139
log.warning("Failed to find challenge binary. There are multiple binaries in the current directory: %s", other_files)
140+
141+
if has_dockerfile and exe and not (libc or ld):
142+
libc, ld = get_docker_image_libraries()
55143

56144
if exe != args.exe:
57145
log.success("Found challenge binary %r", exe)

0 commit comments

Comments
 (0)