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

Gallopsled / pwntools / 23494307733

24 Mar 2026 02:19PM UTC coverage: 73.722% (+0.1%) from 73.597%
23494307733

push

github

web-flow
Merge branch 'dev' into safe_link_same_page

3891 of 6538 branches covered (59.51%)

101 of 139 new or added lines in 21 files covered. (72.66%)

2 existing lines in 2 files now uncovered.

13421 of 18205 relevant lines covered (73.72%)

0.74 hits per line

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

87.05
/pwnlib/commandline/shellcraft.py
1
import argparse
1✔
2
import os
1✔
3
import sys
1✔
4
import types
1✔
5

6
import pwnlib.args
1✔
7
pwnlib.args.free_form = False
1✔
8

9
from pwn import *
1✔
10
from pwnlib.commandline import common
1✔
11
from pwnlib.util.fiddling import hexstr
1✔
12

13

14
#  ____  _          _ _                 __ _
15
# / ___|| |__   ___| | | ___ _ __ __ _ / _| |_
16
# \___ \| '_ \ / _ \ | |/ __| '__/ _` | |_| __|
17
#  ___) | | | |  __/ | | (__| | | (_| |  _| |_
18
# |____/|_| |_|\___|_|_|\___|_|  \__,_|_|  \__|
19

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

26

27
p.add_argument(
1✔
28
    '-?', '--show',
29
    action = 'store_true',
30
    help = 'Show shellcode documentation',
31
)
32

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

41
p.add_argument(
1✔
42
    '-f', '--format',
43
    metavar = 'format',
44
    choices = ['r', 'raw',
45
               's', 'str', 'string',
46
               'c',
47
               'h', 'hex',
48
               'a', 'asm', 'assembly',
49
               'p',
50
               'i', 'hexii',
51
               'e', 'elf',
52
               'd', 'escaped',
53
               'default'],
54
    default = 'default',
55
    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',
56
)
57

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

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

72
p.add_argument(
1✔
73
    '--delim',
74
    help='Set the delimiter between multiple shellcodes',
75
    default='+'
76
)
77

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

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

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

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

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

114
p.add_argument(
1✔
115
    '-r',
116
    '--run',
117
    help="Run output",
118
    action='store_true'
119
)
120

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

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

135
p.add_argument(
1✔
136
    '--syscalls',
137
    help="List syscalls",
138
    action='store_true'
139
)
140

141
p.add_argument(
1✔
142
    '--address',
143
    help="Load address",
144
    default=None
145
)
146

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

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

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

172
def is_not_a_syscall_template(name):
1✔
173
    template_src = shellcraft._get_source(name)
1✔
174
    return '/syscalls' not in template_src
1✔
175

176
def main(args):
1✔
177
    delim = '+'
1✔
178
    if args.delim:
1!
179
        delim = args.delim.strip()
1✔
180

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

193
    if args.list:
1✔
194
        templates = shellcraft.templates
1✔
195

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

204
        print('\n'.join(templates))
1✔
205
        exit()
1✔
206

207
    if not args.shellcode:
1!
208
        common.parser.print_usage()
×
209
        exit()
×
210

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

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

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

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

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

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

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

291
    code = "".join(code_array)
1✔
292

293
    if args.before:
1✔
294
        code = shellcraft.trap() + code
1✔
295
    if args.after:
1✔
296
        code = code + shellcraft.trap()
1✔
297

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

304
            code = highlight(code, PwntoolsLexer(), TerminalFormatter())
1✔
305

306
        print(code)
1✔
307
        exit()
1✔
308
    if args.format == 'p':
1✔
309
        print(cpp(code))
1✔
310
        exit()
1✔
311

312
    assembly = code
1✔
313

314
    vma = args.address
1✔
315
    if vma:
1!
316
        vma = pwnlib.util.safeeval.expr(vma)
×
317

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

323

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

334
    if args.format == 'default':
1✔
335
        if args.out.isatty():
1!
336
            args.format = 'hex'
×
337
        else:
338
            args.format = 'raw'
1✔
339

340
    arch = name.split('.')[0]
1✔
341

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

350
    if args.run:
1✔
351
        proc = run_shellcode(code, arch=arch)
1✔
352
        proc.interactive()
1✔
353
        sys.exit(0)
1✔
354

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

368
    if not hasattr(code, 'decode'):
1✔
369
        code = code.encode()
1✔
370
    args.out.write(code)
1✔
371

372
if __name__ == '__main__':
1✔
373
    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