• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Gallopsled / pwntools / 13600950642

01 Mar 2025 04:10AM UTC coverage: 74.211% (+3.2%) from 71.055%
13600950642

Pull #2546

github

web-flow
Merge 77df40314 into 60cff2437
Pull Request #2546: ssh: Allow passing `disabled_algorithms` keyword argument from `ssh` to paramiko

3812 of 6380 branches covered (59.75%)

0 of 1 new or added line in 1 file covered. (0.0%)

1243 existing lines in 37 files now uncovered.

13352 of 17992 relevant lines covered (74.21%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

20.16
/pwnlib/commandline/template.py
1
from __future__ import absolute_import
1✔
2
from __future__ import division
1✔
3
from __future__ import print_function
1✔
4

5
from pwn import *
1✔
6
from pwnlib.commandline import common
1✔
7
from pwnlib.util.misc import which, parse_ldd_output, write
1✔
8

9
from sys import stderr
1✔
10
from mako.lookup import TemplateLookup, Template
1✔
11

12
parser = common.parser_commands.add_parser(
1✔
13
    'template',
14
    help = 'Generate an exploit template',
15
    description = 'Generate an exploit template. If no arguments are given, '
16
                    'the current directory is searched for an executable binary and ' 
17
                    'libc. If only one binary is found, it is assumed to be the '
18
                    'challenge binary.'
19
)
20

21
# change path to hardcoded one when building the documentation
22
printable_data_path = "pwnlib/data" if 'sphinx' in sys.modules else pwnlib.data.path
1✔
23

24
parser.add_argument('exe', nargs='?', help='Target binary. If not given, the current directory is searched for an executable binary.')
1✔
25
parser.add_argument('--host', help='Remote host / SSH server')
1✔
26
parser.add_argument('--port', help='Remote port / SSH port', type=int)
1✔
27
parser.add_argument('--user', help='SSH Username')
1✔
28
parser.add_argument('--pass', '--password', help='SSH Password', dest='password')
1✔
29
parser.add_argument('--libc', help='Path to libc binary to use. If not given, the current directory is searched for a libc binary.')
1✔
30
parser.add_argument('--path', help='Remote path of file on SSH server')
1✔
31
parser.add_argument('--quiet', help='Less verbose template comments', action='store_true')
1✔
32
parser.add_argument('--color', help='Print the output in color', choices=['never', 'always', 'auto'], default='auto')
1✔
33
parser.add_argument('--template', help='Path to a custom template. Tries to use \'~/.config/pwntools/templates/pwnup.mako\', if it exists. '
1✔
34
                                   'Check \'%s\' for the default template shipped with pwntools.' % 
35
                                        os.path.join(printable_data_path, "templates", "pwnup.mako"))
36
parser.add_argument('--no-auto', help='Do not automatically detect missing binaries', action='store_false', dest='auto')
1✔
37

38
def get_docker_image_libraries():
1✔
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

117
def detect_missing_binaries(args):
1✔
118
    log.info("Automatically detecting challenge binaries...")
×
119
    # look for challenge binary, libc, and ld in current directory
120
    exe, libc, ld = args.exe, args.libc, None
×
121
    has_dockerfile = False
×
122
    other_files = []
×
123
    for filename in os.listdir("."):
×
124
        if not os.path.isfile(filename):
×
125
            continue
×
126
        if not libc and ('libc-' in filename or 'libc.' in filename):
×
127
            libc = filename
×
128
        elif not ld and 'ld-' in filename:
×
129
            ld = filename
×
130
        elif filename == "Dockerfile":
×
131
            has_dockerfile = True
×
132
        else:
133
            if os.access(filename, os.X_OK):
×
134
                other_files.append(filename)
×
135
    if not exe:
×
136
        if len(other_files) == 1:
×
137
            exe = other_files[0]
×
138
        elif len(other_files) > 1:
×
139
            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()
×
143

144
    if exe != args.exe:
×
145
        log.success("Found challenge binary %r", exe)
×
146
    if libc != args.libc:
×
147
        log.success("Found libc binary %r", libc)
×
148
    return exe, libc
×
149

150
def main(args):
1✔
151

152
    lookup = TemplateLookup(
×
153
        directories      = [
154
            os.path.expanduser('~/.config/pwntools/templates/'),
155
            os.path.join(pwnlib.data.path, 'templates')
156
        ],
157
        module_directory = None
158
    )
159

160
    # For the SSH scenario, check that the binary is at the
161
    # same path on the remote host.
162
    if args.user:
×
163
        if not (args.path or args.exe):
×
164
            log.error("Must specify --path or a exe")
×
165

166
        with ssh(args.user, args.host, args.port or 22, args.password or None) as s:
×
167
            try:
×
168
                remote_file = args.path or args.exe
×
169
                s.download(remote_file)
×
170
            except Exception:
×
171
                log.warning("Could not download file %r, opening a shell", remote_file)
×
172
                s.interactive()
×
173
                return
×
174

175
        if not args.exe:
×
176
            args.exe = os.path.basename(args.path)
×
177

178
    if args.auto and (args.exe is None or args.libc is None):
×
179
        args.exe, args.libc = detect_missing_binaries(args)
×
180
    
181
    if args.template:
×
182
        template = Template(filename=args.template) # Failing on invalid file is ok
×
183
    else:
184
        template = lookup.get_template('pwnup.mako')
×
185
    
186
    output = template.render(args.exe,
×
187
                             args.host,
188
                             args.port,
189
                             args.user,
190
                             args.password,
191
                             args.libc,
192
                             args.path,
193
                             args.quiet)
194

195
    # Fix Mako formatting bs
196
    output = re.sub('\n\n\n', '\n\n', output)
×
197

198
    # Colorize the output if it's a TTY
199
    if args.color == 'always' or (args.color == 'auto' and sys.stdout.isatty()):
×
200
        from pygments import highlight
×
201
        from pygments.formatters import TerminalFormatter
×
202
        from pygments.lexers.python import PythonLexer
×
203
        output = highlight(output, PythonLexer(), TerminalFormatter())
×
204

205
    print(output)
×
206

207
    # If redirected to a file, make the resulting script executable
208
    if not sys.stdout.isatty():
×
209
        try: os.fchmod(sys.stdout.fileno(), 0o700)
×
210
        except OSError: pass
×
211

212
if __name__ == '__main__':
1!
213
    pwnlib.commandline.common.main(__file__, main)
×
214
    
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc