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

Gallopsled / pwntools / 14218909491

02 Apr 2025 11:51AM UTC coverage: 73.932% (-0.3%) from 74.2%
14218909491

Pull #2554

github

web-flow
Merge 174041ca8 into e5cd224fb
Pull Request #2554: Use format strings and raw strings

3813 of 6426 branches covered (59.34%)

32 of 44 new or added lines in 15 files covered. (72.73%)

571 existing lines in 7 files now uncovered.

13361 of 18072 relevant lines covered (73.93%)

0.74 hits per line

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

87.18
/pwnlib/commandline/shellcraft.py
1
from __future__ import absolute_import
1✔
2
from __future__ import division
1✔
3

4
import argparse
1✔
5
import os
1✔
6
import sys
1✔
7
import types
1✔
8

9
import pwnlib.args
1✔
10
pwnlib.args.free_form = False
1✔
11

12
from pwn import *
1✔
13
from pwnlib.commandline import common
1✔
14
from pwnlib.util.fiddling import hexstr
1✔
15

16

17
#  ____  _          _ _                 __ _
18
# / ___|| |__   ___| | | ___ _ __ __ _ / _| |_
19
# \___ \| '_ \ / _ \ | |/ __| '__/ _` | |_| __|
20
#  ___) | | | |  __/ | | (__| | | (_| |  _| |_
21
# |____/|_| |_|\___|_|_|\___|_|  \__,_|_|  \__|
22

23
p = common.parser_commands.add_parser(
1✔
24
    'shellcraft',
25
    help = 'Microwave shellcode -- Easy, fast and delicious',
26
    description = 'Microwave shellcode -- Easy, fast and delicious',
27
)
28

29

30
p.add_argument(
1✔
31
    '-?', '--show',
32
    action = 'store_true',
33
    help = 'Show shellcode documentation',
34
)
35

36
p.add_argument(
1✔
37
    '-o', '--out',
38
    metavar = 'file',
39
    type = argparse.FileType('wb'),
40
    default = getattr(sys.stdout, 'buffer', sys.stdout),
41
    help = 'Output file (default: stdout)',
42
)
43

44
p.add_argument(
1✔
45
    '-f', '--format',
46
    metavar = 'format',
47
    choices = ['r', 'raw',
48
               's', 'str', 'string',
49
               'c',
50
               'h', 'hex',
51
               'a', 'asm', 'assembly',
52
               'p',
53
               'i', 'hexii',
54
               'e', 'elf',
55
               'd', 'escaped',
56
               'default'],
57
    default = 'default',
58
    help = 'Output format (default: hex), choose from {e}lf, {r}aw, {s}tring, {c}-style array, {h}ex string, hex{i}i, {a}ssembly code, {p}reprocssed code, escape{d} hex string',
59
)
60

61
p.add_argument(
1✔
62
    'shellcode',
63
    nargs = '*',
64
    help = 'The shellcodes you want.  shellcode [args ...] [+ shellcode [args ...]]',
65
    type = str
66
)
67

68
p.add_argument(
1✔
69
    '-d',
70
    '--debug',
71
    help='Debug the shellcode with GDB',
72
    action='store_true'
73
)
74

75
p.add_argument(
1✔
76
    '--delim',
77
    help='Set the delimiter between multilple shellcodes',
78
    default='+'
79
)
80

81
p.add_argument(
1✔
82
    '-b',
83
    '--before',
84
    help='Insert a debug trap before the code',
85
    action='store_true'
86
)
87

88
p.add_argument(
1✔
89
    '-a',
90
    '--after',
91
    help='Insert a debug trap after the code',
92
    action='store_true'
93
)
94

95
p.add_argument(
1✔
96
    '-v', '--avoid',
97
    action='append',
98
    help = 'Encode the shellcode to avoid the listed bytes'
99
)
100

101
p.add_argument(
1✔
102
    '-n', '--newline',
103
    dest='avoid',
104
    action='append_const',
105
    const='\n',
106
    help = 'Encode the shellcode to avoid newlines'
107
)
108

109
p.add_argument(
1✔
110
    '-z', '--zero',
111
    dest='avoid',
112
    action='append_const',
113
    const='\x00',
114
    help = 'Encode the shellcode to avoid NULL bytes'
115
)
116

117
p.add_argument(
1✔
118
    '-r',
119
    '--run',
120
    help="Run output",
121
    action='store_true'
122
)
123

124
p.add_argument(
1✔
125
    '--color',
126
    help="Color output",
127
    action='store_true',
128
    default=sys.stdout.isatty()
129
)
130

131
p.add_argument(
1✔
132
    '--no-color',
133
    help="Disable color output",
134
    action='store_false',
135
    dest='color'
136
)
137

138
p.add_argument(
1✔
139
    '--syscalls',
140
    help="List syscalls",
141
    action='store_true'
142
)
143

144
p.add_argument(
1✔
145
    '--address',
146
    help="Load address",
147
    default=None
148
)
149

150
p.add_argument(
1✔
151
    '-l', '--list',
152
    action='store_true',
153
    help='List available shellcodes, optionally provide a filter'
154
)
155

156
p.add_argument(
1✔
157
    '-s', '--shared',
158
    action='store_true',
159
    help='Generated ELF is a shared library'
160
)
161

162
def get_template(shellcodes):
1✔
163
    funcs = []
1✔
164
    for shellcode in shellcodes:
1✔
165
        func = shellcraft
1✔
166
        cur_name = shellcode[0]
1✔
167
        args = []
1✔
168
        if len(shellcode) > 1:
1✔
169
            args = shellcode[1:]
1✔
170
        for attr in cur_name.split('.'):
1✔
171
            func = getattr(func, attr)
1✔
172
        funcs.append((cur_name, func, args))
1✔
173
    return funcs
1✔
174

175
def is_not_a_syscall_template(name):
1✔
176
    template_src = shellcraft._get_source(name)
1✔
177
    return '/syscalls' not in template_src
1✔
178

179
def main(args):
1✔
180
    delim = '+'
1✔
181
    if args.delim:
1!
182
        delim = args.delim.strip()
1✔
183

184
    shellcodes = []
1✔
185
    if args.shellcode:
1✔
186
        current = []
1✔
187
        for s in args.shellcode:
1✔
188
            if s.strip() == delim:
1✔
189
                shellcodes.append(current)
1✔
190
                current = []
1✔
191
            else:
192
                current.append(s)
1✔
193
        if len(current) > 0:
1!
194
            shellcodes.append(current)
1✔
195

196
    if args.list:
1✔
197
        templates = shellcraft.templates
1✔
198

199
        if args.shellcode:
1✔
200
            template_array = []
1✔
201
            for s in shellcodes:
1✔
202
                template_array.extend(list(filter(lambda a: s[0] in a, templates)))
1✔
203
            templates = template_array
1✔
204
        elif not args.syscalls:
1✔
205
            templates = list(filter(is_not_a_syscall_template, templates))
1✔
206

207
        print('\n'.join(templates))
1✔
208
        exit()
1✔
209

210
    if not args.shellcode:
1!
211
        common.parser.print_usage()
×
212
        exit()
×
213

214
    try:
1✔
215
        funcs = get_template(shellcodes)
1✔
216
    except AttributeError:
×
217
        log.error("Unknown shellcraft template %r. Use --list to see available shellcodes." % args.shellcode)
×
218

219
    if args.show:
1✔
220
        for (name, func, _args) in funcs:
1✔
221
            # remove doctests
222
            doc = []
1✔
223
            in_doctest = False
1✔
224
            block_indent = None
1✔
225
            caption = None
1✔
226
            lines = func.__doc__.splitlines()
1✔
227
            i = 0
1✔
228
            if len(funcs) > 1:
1✔
229
                print('%s:' % name)
1✔
230
            while i < len(lines):
1✔
231
                line = lines[i]
1✔
232
                if line.lstrip().startswith('>>>'):
1✔
233
                    # this line starts a doctest
234
                    in_doctest = True
1✔
235
                    block_indent = None
1✔
236
                    if caption:
1✔
237
                        # delete back up to the caption
238
                        doc = doc[:caption - i]
1✔
239
                        caption = None
1✔
240
                elif line == '':
1✔
241
                    # skip blank lines
242
                    pass
1✔
243
                elif in_doctest:
1✔
244
                    # indentation marks the end of a doctest
245
                    indent = len(line) - len(line.lstrip())
1✔
246
                    if block_indent is None:
1!
247
                        if not line.lstrip().startswith('...'):
1!
248
                            block_indent = indent
1✔
249
                    elif indent < block_indent:
×
250
                        in_doctest = False
×
251
                        block_indent = None
×
252
                        # re-evalutate this line
253
                        continue
×
254
                elif line.endswith(':'):
1✔
255
                    # save index of caption
256
                    caption = i
1✔
257
                else:
258
                    # this is not blank space and we're not in a doctest, so the
259
                    # previous caption (if any) was not for a doctest
260
                    caption = None
1✔
261

262
                if not in_doctest:
1✔
263
                    doc.append(line)
1✔
264
                i += 1
1✔
265
            print('\n'.join(doc).rstrip())
1✔
266
            if len(funcs) > 1:
1✔
267
                print('')
1✔
268
        exit()
1✔
269

270
    code_array = []
1✔
271
    for (name, func, func_args) in funcs:
1✔
272
        defargs = len(func.__defaults__ or ())
1✔
273
        reqargs = func.__code__.co_argcount - defargs
1✔
274

275
        if len(func_args) < reqargs:
1!
276
            if defargs > 0:
×
277
                log.critical('%s takes at least %d arguments' % (name, reqargs))
×
278
                sys.exit(1)
×
279
            else:
280
                log.critical('%s takes exactly %d arguments' % (name, reqargs))
×
281
                sys.exit(1)
×
282

283
        # Captain uglyness saves the day!
284
        for i, val in enumerate(func_args):
1✔
285
            try:
1✔
286
                func_args[i] = util.safeeval.expr(val)
1✔
287
            except ValueError:
1✔
288
                pass
1✔
289

290
        # And he strikes again!
291
        list(map(common.context_arg, name.split('.')))
1✔
292
        code_array.append(func(*func_args))
1✔
293

294
    code = "".join(code_array)
1✔
295

296
    if args.before:
1✔
297
        code = shellcraft.trap() + code
1✔
298
    if args.after:
1✔
299
        code = code + shellcraft.trap()
1✔
300

301
    if args.format in ['a', 'asm', 'assembly']:
1✔
302
        if args.color:
1!
303
            from pygments import highlight
1✔
304
            from pygments.formatters import TerminalFormatter
1✔
305
            from pwnlib.lexer import PwntoolsLexer
1✔
306

307
            code = highlight(code, PwntoolsLexer(), TerminalFormatter())
1✔
308

309
        print(code)
1✔
310
        exit()
1✔
311
    if args.format == 'p':
1✔
312
        print(cpp(code))
1✔
313
        exit()
1✔
314

315
    assembly = code
1✔
316

317
    vma = args.address
1✔
318
    if vma:
1!
319
        vma = pwnlib.util.safeeval.expr(vma)
×
320

321
    if args.format in ['e','elf']:
1✔
322
        args.format = 'default'
1✔
323
        try: os.fchmod(args.out.fileno(), 0o700)
1✔
324
        except OSError: pass
1✔
325

326

327
        if not args.avoid:
1!
328
            code = read(make_elf_from_assembly(assembly, vma=vma, shared=args.shared))
1✔
329
        else:
330
            code = asm(assembly)
×
331
            code = encode(code, args.avoid)
×
332
            code = make_elf(code, vma=vma, shared=args.shared)
×
333
            # code = read(make_elf(encode(asm(code), args.avoid)))
334
    else:
335
        code = encode(asm(assembly), args.avoid)
1✔
336

337
    if args.format == 'default':
1✔
338
        if args.out.isatty():
1!
339
            args.format = 'hex'
×
340
        else:
341
            args.format = 'raw'
1✔
342

343
    arch = name.split('.')[0]
1✔
344

345
    if args.debug:
1!
346
        if not args.avoid:
×
347
            proc = gdb.debug_assembly(assembly, arch=arch, vma=vma)
×
348
        else:
349
            proc = gdb.debug_shellcode(code, arch=arch, vma=vma)
×
350
        proc.interactive()
×
351
        sys.exit(0)
×
352

353
    if args.run:
1✔
354
        proc = run_shellcode(code, arch=arch)
1✔
355
        proc.interactive()
1✔
356
        sys.exit(0)
1✔
357

358
    if args.format in ['s', 'str', 'string']:
1✔
359
        code = hexstr(code)
1✔
360
    elif args.format == 'c':
1✔
361
        code = '{' + ', '.join(map(hex, bytearray(code))) + '}' + '\n'
1✔
362
    elif args.format in ['h', 'hex']:
1!
363
        code = pwnlib.util.fiddling.enhex(code) + '\n'
×
364
    elif args.format in ['i', 'hexii']:
1✔
365
        code = hexii(code) + '\n'
1✔
366
    elif args.format in ['d', 'escaped']:
1!
NEW
367
        code = ''.join(fr'\x{c:02x}' for c in code) + '\n'
×
368
    if not sys.stdin.isatty():
1!
369
        args.out.write(getattr(sys.stdin, 'buffer', sys.stdin).read())
1✔
370

371
    if not hasattr(code, 'decode'):
1✔
372
        code = code.encode()
1✔
373
    args.out.write(code)
1✔
374

375
if __name__ == '__main__':
1✔
376
    pwnlib.commandline.common.main(__file__, main)
1✔
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