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

Gallopsled / pwntools / 3797606986

pending completion
3797606986

push

github-actions

GitHub
Merge branch 'dev' into rop_raw_list

3931 of 6599 branches covered (59.57%)

102 of 102 new or added lines in 15 files covered. (100.0%)

12074 of 16876 relevant lines covered (71.55%)

0.72 hits per line

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

84.06
/pwnlib/asm.py
1
# -*- coding: utf-8 -*-
2
r"""
1✔
3
Utilities for assembling and disassembling code.
4

5
Architecture Selection
6
------------------------
7

8
    Architecture, endianness, and word size are selected by using :mod:`pwnlib.context`.
9

10
    Any parameters which can be specified to ``context`` can also be specified as
11
    keyword arguments to either :func:`asm` or :func:`disasm`.
12

13
Assembly
14
------------------------
15

16
    To assemble code, simply invoke :func:`asm` on the code to assemble.
17

18
        >>> asm('mov eax, 0')
19
        b'\xb8\x00\x00\x00\x00'
20

21
    Additionally, you can use constants as defined in the :mod:`pwnlib.constants`
22
    module.
23

24
        >>> asm('mov eax, SYS_execve')
25
        b'\xb8\x0b\x00\x00\x00'
26

27
    Finally, :func:`asm` is used to assemble shellcode provided by ``pwntools``
28
    in the :mod:`shellcraft` module.
29

30
        >>> asm(shellcraft.nop())
31
        b'\x90'
32

33
Disassembly
34
------------------------
35

36
    To disassemble code, simply invoke :func:`disasm` on the bytes to disassemble.
37

38
    >>> disasm(b'\xb8\x0b\x00\x00\x00')
39
    '   0:   b8 0b 00 00 00          mov    eax, 0xb'
40

41
"""
42
from __future__ import absolute_import
1✔
43
from __future__ import division
1✔
44

45
import errno
1✔
46
import os
1✔
47
import platform
1✔
48
import re
1✔
49
import shutil
1✔
50
import subprocess
1✔
51
import tempfile
1✔
52
from collections import defaultdict
1✔
53
from glob import glob
1✔
54
from os import environ
1✔
55
from os import path
1✔
56

57
from pwnlib import atexit
1✔
58
from pwnlib import shellcraft
1✔
59
from pwnlib.context import LocalContext
1✔
60
from pwnlib.context import context
1✔
61
from pwnlib.log import getLogger
1✔
62

63
log = getLogger(__name__)
1✔
64

65
__all__ = ['asm', 'cpp', 'disasm', 'make_elf', 'make_elf_from_assembly']
1✔
66

67
_basedir = path.split(__file__)[0]
1✔
68
_incdir  = path.join(_basedir, 'data', 'includes')
1✔
69

70
def dpkg_search_for_binutils(arch, util):
1✔
71
    """Use dpkg to search for any available assemblers which will work.
72

73
    Returns:
74
        A list of candidate package names.
75

76
    ::
77

78
        >>> pwnlib.asm.dpkg_search_for_binutils('aarch64', 'as')
79
        ['binutils-aarch64-linux-gnu']
80
    """
81

82
    # Example output:
83
    # $ dpkg -S 'arm*linux*-as'
84
    # binutils-arm-linux-gnu: /usr/bin/arm-linux-gnu-as
85
    # binutils-arm-linux-gnueabihf: /usr/bin/arm-linux-gnueabihf-as
86
    # binutils-arm-linux-gnueabihf: /usr/x86_64-linux-gnu/arm-linux-gnueabihf/include/dis-asm.h
87
    # binutils-arm-linux-gnu: /usr/x86_64-linux-gnu/arm-linux-gnu/include/dis-asm.h
88
    packages = []
1✔
89

90
    try:
1✔
91
        filename = 'bin/%s*linux*-%s' % (arch, util)
1✔
92
        output = subprocess.check_output(['dpkg','-S',filename], universal_newlines = True)
1✔
93
        for line in output.strip().splitlines():
1✔
94
            package, path = line.split(':', 1)
1✔
95
            packages.append(package)
1✔
96
    except OSError:
×
97
        pass
×
98
    except subprocess.CalledProcessError:
×
99
        pass
×
100

101
    return packages
1✔
102

103
def print_binutils_instructions(util, context):
1✔
104
    """On failure to find a binutils utility, inform the user of a way
105
    they can get it easily.
106

107
    Doctest:
108

109
        >>> context.clear(arch = 'amd64')
110
        >>> pwnlib.asm.print_binutils_instructions('as', context)
111
        Traceback (most recent call last):
112
        ...
113
        PwnlibException: Could not find 'as' installed for ContextType(arch = 'amd64', bits = 64, endian = 'little')
114
        Try installing binutils for this architecture:
115
        $ sudo apt-get install binutils
116
    """
117
    # This links to our instructions on how to manually install binutils
118
    # for several architectures.
119
    instructions = 'https://docs.pwntools.com/en/stable/install/binutils.html'
1✔
120

121
    # However, if we can directly provide a useful command, go for it.
122
    binutils_arch = {
1✔
123
        'amd64': 'x86_64',
124
        'arm':   'armeabi',
125
        'thumb': 'armeabi',
126
    }.get(context.arch, context.arch)
127

128
    packages = dpkg_search_for_binutils(binutils_arch, util)
1✔
129

130
    if packages:
1!
131
        instructions = '$ sudo apt-get install %s' % packages[0]
1✔
132

133
    log.error("""
1✔
134
Could not find %(util)r installed for %(context)s
135
Try installing binutils for this architecture:
136
%(instructions)s
137
""".strip() % locals())
138

139

140
def check_binutils_version(util):
1✔
141
    if util_versions[util]:
1✔
142
        return util_versions[util]
1✔
143
    result = subprocess.check_output([util, '--version','/dev/null'],
1✔
144
                                     stderr=subprocess.STDOUT, universal_newlines=True)
145
    if 'clang' in result:
1!
146
        log.warn_once('Your binutils is clang-based and may not work!\n'
×
147
            'Try installing with: https://docs.pwntools.com/en/stable/install/binutils.html\n'
148
            'Reported version: %r', result.strip())
149
    version = re.search(r' (\d+\.\d+)', result).group(1)
1✔
150
    util_versions[util] = version = tuple(map(int, version.split('.')))
1✔
151
    return version
1✔
152

153

154
@LocalContext
1✔
155
def which_binutils(util, check_version=False):
1✔
156
    """
157
    Finds a binutils in the PATH somewhere.
158
    Expects that the utility is prefixed with the architecture name.
159

160
    Examples:
161

162
        >>> import platform
163
        >>> which_binutils = pwnlib.asm.which_binutils
164
        >>> which_binutils('as', arch=platform.machine())
165
        '.../bin/...as'
166
        >>> which_binutils('as', arch='arm') #doctest: +ELLIPSIS
167
        '.../bin/arm-...-as'
168
        >>> which_binutils('as', arch='powerpc') #doctest: +ELLIPSIS
169
        '.../bin/powerpc...-as'
170
        >>> which_binutils('as', arch='msp430') #doctest: +SKIP
171
        ...
172
        Traceback (most recent call last):
173
        ...
174
        Exception: Could not find 'as' installed for ContextType(arch = 'msp430')
175
    """
176
    arch = context.arch
1✔
177

178
    # Fix up pwntools vs Debian triplet naming, and account
179
    # for 'thumb' being its own pwntools architecture.
180
    arches = [arch] + {
1✔
181
        'thumb':  ['arm',    'aarch64'],
182
        'i386':   ['x86_64', 'amd64'],
183
        'i686':   ['x86_64', 'amd64'],
184
        'amd64':  ['x86_64', 'i386'],
185
        'mips64': ['mips'],
186
        'powerpc64': ['powerpc'],
187
        'sparc64': ['sparc'],
188
    }.get(arch, [])
189

190
    # If one of the candidate architectures matches the native
191
    # architecture, use that as a last resort.
192
    machine = platform.machine()
1✔
193
    machine = 'i386' if machine == 'i686' else machine
1✔
194
    try:
1✔
195
        with context.local(arch = machine):
1✔
196
            if context.arch in arches:
1✔
197
                arches.append(None)
1✔
198
    except AttributeError:
×
199
        log.warn_once("Your local binutils won't be used because architecture %r is not supported." % machine)
×
200

201
    utils = [util]
1✔
202

203
    # hack for homebrew-installed binutils on mac
204
    if platform.system() == 'Darwin':
1!
205
        utils = ['g'+util, util]
×
206

207
    for arch in arches:
1!
208
        for gutil in utils:
1✔
209
            # e.g. objdump
210
            if arch is None:
1!
211
                patterns = [gutil]
×
212

213
            # e.g. aarch64-linux-gnu-objdump, avr-objdump
214
            else:
215
                patterns = ['%s*linux*-%s' % (arch, gutil),
1✔
216
                            '%s*-elf-%s' % (arch, gutil),
217
                            '%s-none*-%s' % (arch, gutil),
218
                            '%s-%s' % (arch, gutil)]
219

220
            for pattern in patterns:
1✔
221
                for dir in environ['PATH'].split(':'):
1✔
222
                    for res in sorted(glob(path.join(dir, pattern))):
1✔
223
                        if check_version:
1✔
224
                            ver = check_binutils_version(res)
1✔
225
                            return res, ver
1✔
226
                        return res
1✔
227

228
    # No dice!
229
    print_binutils_instructions(util, context)
×
230

231
util_versions = defaultdict(tuple)
1✔
232

233
def _assembler():
1✔
234
    gas, version = which_binutils('as', check_version=True)
1✔
235
    if version < (2, 19):
1!
236
        log.warn_once('Your binutils version is too old and may not work!\n'
×
237
            'Try updating with: https://docs.pwntools.com/en/stable/install/binutils.html\n'
238
            'Reported version: %r', version)
239

240
    E = {
1✔
241
        'big':    '-EB',
242
        'little': '-EL'
243
    }[context.endianness]
244

245
    B = '-%s' % context.bits
1✔
246

247
    assemblers = {
1✔
248
        'i386'   : [gas, B],
249
        'amd64'  : [gas, B],
250

251
        # Most architectures accept -EL or -EB
252
        'thumb'  : [gas, '-mthumb', E],
253
        'arm'    : [gas, E],
254
        'aarch64': [gas, E],
255
        'mips'   : [gas, E, B],
256
        'mips64' : [gas, E, B],
257
        'sparc':   [gas, E, B],
258
        'sparc64': [gas, E, B],
259

260
        # Powerpc wants -mbig or -mlittle, and -mppc32 or -mppc64
261
        'powerpc':   [gas, '-m%s' % context.endianness, '-mppc%s' % context.bits],
262
        'powerpc64': [gas, '-m%s' % context.endianness, '-mppc%s' % context.bits],
263

264
        # ia64 only accepts -mbe or -mle
265
        'ia64':    [gas, '-m%ce' % context.endianness[0]]
266
    }
267

268
    assembler = assemblers.get(context.arch, [gas])
1✔
269

270
    return assembler
1✔
271

272
def _linker():
1✔
273
    ld, _ = which_binutils('ld', check_version=True)
1✔
274
    bfd = ['--oformat=' + _bfdname()]
1✔
275

276
    E = {
1✔
277
        'big':    '-EB',
278
        'little': '-EL'
279
    }[context.endianness]
280

281
    arguments = {
1✔
282
        'i386': ['-m', 'elf_i386'],
283
    }.get(context.arch, [])
284

285
    return [ld] + bfd + [E] + arguments
1✔
286

287

288
def _execstack(linker):
1✔
289
    ldflags = ['-z', 'execstack']
1✔
290
    version = util_versions[linker[0]]
1✔
291
    if version >= (2, 39):
1!
292
        return ldflags + ['--no-warn-execstack', '--no-warn-rwx-segments']
×
293
    return ldflags
1✔
294

295

296
def _objcopy():
1✔
297
    return [which_binutils('objcopy')]
1✔
298

299
def _objdump():
1✔
300
    path = [which_binutils('objdump')]
1✔
301

302
    if context.arch in ('i386', 'amd64'):
1✔
303
        path += ['-Mintel']
1✔
304

305
    return path
1✔
306

307
def _include_header():
1✔
308
    os   = context.os
1✔
309
    arch = context.arch
1✔
310
    include = '%s/%s.h' % (os, arch)
1✔
311

312
    if not include or not path.exists(path.join(_incdir, include)):
1✔
313
        log.warn_once("Could not find system include headers for %s-%s" % (arch,os))
1✔
314
        return '\n'
1✔
315

316
    return '#include <%s>\n' % include
1✔
317

318

319
def _arch_header():
1✔
320
    prefix  = ['.section .shellcode,"awx"',
1✔
321
                '.global _start',
322
                '.global __start',
323
                '_start:',
324
                '__start:']
325
    headers = {
1✔
326
        'i386'  :  ['.intel_syntax noprefix', '.p2align 0'],
327
        'amd64' :  ['.intel_syntax noprefix', '.p2align 0'],
328
        'arm'   : ['.syntax unified',
329
                   '.arch armv7-a',
330
                   '.arm',
331
                   '.p2align 2'],
332
        'thumb' : ['.syntax unified',
333
                   '.arch armv7-a',
334
                   '.thumb',
335
                   '.p2align 2'
336
                   ],
337
        'mips'  : ['.set mips2',
338
                   '.set noreorder',
339
                   '.p2align 2'
340
                   ],
341
    }
342

343
    return '\n'.join(prefix + headers.get(context.arch, [])) + '\n'
1✔
344

345
def _bfdname():
1✔
346
    arch = context.arch
1✔
347
    E    = context.endianness
1✔
348

349
    bfdnames = {
1✔
350
        'i386'    : 'elf32-i386',
351
        'aarch64' : 'elf64-%saarch64' % E,
352
        'amd64'   : 'elf64-x86-64',
353
        'arm'     : 'elf32-%sarm' % E,
354
        'thumb'   : 'elf32-%sarm' % E,
355
        'avr'     : 'elf32-avr',
356
        'mips'    : 'elf32-trad%smips' % E,
357
        'mips64'  : 'elf64-trad%smips' % E,
358
        'alpha'   : 'elf64-alpha',
359
        'cris'    : 'elf32-cris',
360
        'ia64'    : 'elf64-ia64-%s' % E,
361
        'm68k'    : 'elf32-m68k',
362
        'msp430'  : 'elf32-msp430',
363
        'powerpc' : 'elf32-powerpc',
364
        'powerpc64' : 'elf64-powerpc',
365
        'riscv'   : 'elf%d-%sriscv' % (context.bits, E),
366
        'vax'     : 'elf32-vax',
367
        's390'    : 'elf%d-s390' % context.bits,
368
        'sparc'   : 'elf32-sparc',
369
        'sparc64' : 'elf64-sparc',
370
    }
371

372
    if arch in bfdnames:
1!
373
        return bfdnames[arch]
1✔
374
    else:
375
        raise Exception("Cannot find bfd name for architecture %r" % arch)
×
376

377

378
def _bfdarch():
1✔
379
    arch = context.arch
1✔
380
    convert = {
1✔
381
        'amd64':     'i386:x86-64',
382
        'i386':      'i386',
383
        'ia64':      'ia64-elf64',
384
        'mips64':    'mips',
385
        'powerpc64': 'powerpc',
386
        'sparc64':   'sparc',
387
        'thumb':     'arm',
388
    }
389

390
    if arch in convert:
1✔
391
        return convert[arch]
1✔
392

393
    return arch
1✔
394

395
def _run(cmd, stdin = None):
1✔
396
    log.debug('%s', subprocess.list2cmdline(cmd))
1✔
397
    try:
1✔
398
        proc = subprocess.Popen(
1✔
399
            cmd,
400
            stdin  = subprocess.PIPE,
401
            stdout = subprocess.PIPE,
402
            stderr = subprocess.PIPE,
403
            universal_newlines = True
404
        )
405
        stdout, stderr = proc.communicate(stdin)
1✔
406
        exitcode = proc.wait()
1✔
407
    except OSError as e:
×
408
        if e.errno == errno.ENOENT:
×
409
            log.exception('Could not run %r the program', cmd[0])
×
410
        else:
411
            raise
×
412

413
    if (exitcode, stderr) != (0, ''):
1!
414
        msg = 'There was an error running %r:\n'
×
415
        args = cmd,
×
416
        if exitcode != 0:
×
417
            msg += 'It had the exitcode %d.\n'
×
418
            args += exitcode,
×
419
        if stderr != '':
×
420
            msg += 'It had this on stdout:\n%s\n'
×
421
            args += stderr,
×
422
        log.error(msg, *args)
×
423

424
    return stdout
1✔
425

426
@LocalContext
1✔
427
def cpp(shellcode):
1✔
428
    r"""cpp(shellcode, ...) -> str
429

430
    Runs CPP over the given shellcode.
431

432
    The output will always contain exactly one newline at the end.
433

434
    Arguments:
435
        shellcode(str): Shellcode to preprocess
436

437
    Kwargs:
438
        Any arguments/properties that can be set on ``context``
439

440
    Examples:
441

442
        >>> cpp("mov al, SYS_setresuid", arch = "i386", os = "linux")
443
        'mov al, 164\n'
444
        >>> cpp("weee SYS_setresuid", arch = "arm", os = "linux")
445
        'weee (0+164)\n'
446
        >>> cpp("SYS_setresuid", arch = "thumb", os = "linux")
447
        '(0+164)\n'
448
        >>> cpp("SYS_setresuid", os = "freebsd")
449
        '311\n'
450
    """
451
    code = _include_header() + shellcode
1✔
452
    cmd  = [
1✔
453
        'cpp',
454
        '-C',
455
        '-nostdinc',
456
        '-undef',
457
        '-P',
458
        '-I' + _incdir,
459
        '/dev/stdin'
460
    ]
461
    return _run(cmd, code).strip('\n').rstrip() + '\n'
1✔
462

463
@LocalContext
1✔
464
def make_elf_from_assembly(assembly,
1✔
465
                           vma=None,
466
                           extract=False,
467
                           shared=False,
468
                           strip=False,
469
                           **kwargs):
470
    r"""make_elf_from_assembly(assembly, vma=None, extract=None, shared=False, strip=False, **kwargs) -> str
471

472
    Builds an ELF file with the specified assembly as its executable code.
473

474
    This differs from :func:`.make_elf` in that all ELF symbols are preserved,
475
    such as labels and local variables.  Use :func:`.make_elf` if size matters.
476
    Additionally, the default value for ``extract`` in :func:`.make_elf` is
477
    different.
478

479
    Note:
480
        This is effectively a wrapper around :func:`.asm`. with setting
481
        ``extract=False``, ``vma=0x10000000``, and marking the resulting
482
        file as executable (``chmod +x``).
483

484
    Note:
485
        ELF files created with `arch=thumb` will prepend an ARM stub
486
        which switches to Thumb mode.
487

488
    Arguments:
489
        assembly(str): Assembly code to build into an ELF
490
        vma(int): Load address of the binary
491
            (Default: ``0x10000000``, or ``0`` if ``shared=True``)
492
        extract(bool): Extract the full ELF data from the file.
493
            (Default: ``False``)
494
        shared(bool): Create a shared library
495
            (Default: ``False``)
496
        kwargs(dict): Arguments to pass to :func:`.asm`.
497

498
    Returns:
499

500
        The path to the assembled ELF (extract=False), or the data
501
        of the assembled ELF.
502

503
    Example:
504

505
        This example shows how to create a shared library, and load it via
506
        ``LD_PRELOAD``.
507

508
        >>> context.clear()
509
        >>> context.arch = 'amd64'
510
        >>> sc = 'push rbp; mov rbp, rsp;'
511
        >>> sc += shellcraft.echo('Hello\n')
512
        >>> sc += 'mov rsp, rbp; pop rbp; ret'
513
        >>> solib = make_elf_from_assembly(sc, shared=1)
514
        >>> subprocess.check_output(['echo', 'World'], env={'LD_PRELOAD': solib}, universal_newlines = True)
515
        'Hello\nWorld\n'
516

517
        The same thing can be done with :func:`.make_elf`, though the sizes
518
        are different.  They both
519

520
        >>> file_a = make_elf(asm('nop'), extract=True)
521
        >>> file_b = make_elf_from_assembly('nop', extract=True)
522
        >>> file_a[:4] == file_b[:4]
523
        True
524
        >>> len(file_a) < len(file_b)
525
        True
526
    """
527
    if shared and vma:
1!
528
        log.error("Cannot specify a VMA for a shared library.")
×
529

530
    if vma is None:
1✔
531
        if shared:
1✔
532
            vma = 0
1✔
533
        else:
534
            vma = 0x10000000
1✔
535

536
    if context.arch == 'thumb':
1✔
537
        to_thumb = shellcraft.arm.to_thumb()
1✔
538

539
        if not assembly.startswith(to_thumb):
1✔
540
            assembly = to_thumb + assembly
1✔
541

542
    result = asm(assembly, vma = vma, shared = shared, extract = False, **kwargs)
1✔
543

544
    if not extract:
1✔
545
        os.chmod(result, 0o755)
1✔
546
    else:
547
        with open(result, 'rb') as io:
1✔
548
            result = io.read()
1✔
549

550
    return result
1✔
551

552
@LocalContext
1✔
553
def make_elf(data,
1✔
554
             vma=None,
555
             strip=True,
556
             extract=True,
557
             shared=False):
558
    r"""make_elf(data, vma=None, strip=True, extract=True, shared=False, **kwargs) -> str
559

560
    Builds an ELF file with the specified binary data as its executable code.
561

562
    Arguments:
563
        data(str): Assembled code
564
        vma(int):  Load address for the ELF file
565
        strip(bool): Strip the resulting ELF file. Only matters if ``extract=False``.
566
            (Default: ``True``)
567
        extract(bool): Extract the assembly from the ELF file.
568
            If ``False``, the path of the ELF file is returned.
569
            (Default: ``True``)
570
        shared(bool): Create a Dynamic Shared Object (DSO, i.e. a ``.so``)
571
            which can be loaded via ``dlopen`` or ``LD_PRELOAD``.
572

573
    Examples:
574
        This example creates an i386 ELF that just does
575
        execve('/bin/sh',...).
576

577
        >>> context.clear(arch='i386')
578
        >>> bin_sh = unhex('6a68682f2f2f73682f62696e89e331c96a0b5899cd80')
579
        >>> filename = make_elf(bin_sh, extract=False)
580
        >>> p = process(filename)
581
        >>> p.sendline(b'echo Hello; exit')
582
        >>> p.recvline()
583
        b'Hello\n'
584
    """
585
    retval = None
1✔
586

587
    if shared and vma:
1!
588
        log.error("Cannot specify a VMA for a shared library.")
×
589

590
    if context.arch == 'thumb':
1✔
591
        to_thumb = asm(shellcraft.arm.to_thumb(), arch='arm')
1✔
592

593
        if not data.startswith(to_thumb):
1!
594
            data = to_thumb + data
1✔
595

596

597
    assembler = _assembler()
1✔
598
    linker    = _linker()
1✔
599
    code      = _arch_header()
1✔
600
    code      += '.string "%s"' % ''.join('\\x%02x' % c for c in bytearray(data))
1✔
601
    code      += '\n'
1✔
602

603
    log.debug("Building ELF:\n" + code)
1✔
604

605
    tmpdir    = tempfile.mkdtemp(prefix = 'pwn-asm-')
1✔
606
    step1     = path.join(tmpdir, 'step1-asm')
1✔
607
    step2     = path.join(tmpdir, 'step2-obj')
1✔
608
    step3     = path.join(tmpdir, 'step3-elf')
1✔
609

610
    try:
1✔
611
        with open(step1, 'w') as f:
1✔
612
            f.write(code)
1✔
613

614
        _run(assembler + ['-o', step2, step1])
1✔
615

616
        linker_options = _execstack(linker)
1✔
617
        if vma is not None:
1✔
618
            linker_options += ['--section-start=.shellcode=%#x' % vma,
1✔
619
                               '--entry=%#x' % vma]
620
        elif shared:
1!
621
            linker_options += ['-shared', '-init=_start']
×
622

623
        linker_options += ['-o', step3, step2]
1✔
624

625
        _run(linker + linker_options)
1✔
626

627
        if strip:
1!
628
            _run([which_binutils('objcopy'), '-Sg', step3])
1✔
629
            _run([which_binutils('strip'), '--strip-unneeded', step3])
1✔
630

631
        if not extract:
1✔
632
            os.chmod(step3, 0o755)
1✔
633
            retval = step3
1✔
634

635
        else:
636
            with open(step3, 'rb') as f:
1✔
637
                retval = f.read()
1✔
638
    except Exception:
×
639
        log.exception("An error occurred while building an ELF:\n%s" % code)
×
640
    else:
641
        atexit.register(lambda: shutil.rmtree(tmpdir))
1!
642

643
    return retval
1✔
644

645
@LocalContext
1✔
646
def asm(shellcode, vma = 0, extract = True, shared = False):
1✔
647
    r"""asm(code, vma = 0, extract = True, shared = False, ...) -> str
648

649
    Runs :func:`cpp` over a given shellcode and then assembles it into bytes.
650

651
    To see which architectures or operating systems are supported,
652
    look in :mod:`pwnlib.context`.
653

654
    Assembling shellcode requires that the GNU assembler is installed
655
    for the target architecture.
656
    See :doc:`Installing Binutils </install/binutils>` for more information.
657

658
    Arguments:
659
        shellcode(str): Assembler code to assemble.
660
        vma(int):       Virtual memory address of the beginning of assembly
661
        extract(bool):  Extract the raw assembly bytes from the assembled
662
                        file.  If :const:`False`, returns the path to an ELF file
663
                        with the assembly embedded.
664
        shared(bool):   Create a shared object.
665
        kwargs(dict):   Any attributes on :data:`.context` can be set, e.g.set
666
                        ``arch='arm'``.
667

668
    Examples:
669

670
        >>> asm("mov eax, SYS_select", arch = 'i386', os = 'freebsd')
671
        b'\xb8]\x00\x00\x00'
672
        >>> asm("mov eax, SYS_select", arch = 'amd64', os = 'linux')
673
        b'\xb8\x17\x00\x00\x00'
674
        >>> asm("mov rax, SYS_select", arch = 'amd64', os = 'linux')
675
        b'H\xc7\xc0\x17\x00\x00\x00'
676
        >>> asm("mov r0, #SYS_select", arch = 'arm', os = 'linux', bits=32)
677
        b'R\x00\xa0\xe3'
678
        >>> asm("mov #42, r0", arch = 'msp430')
679
        b'0@*\x00'
680
        >>> asm("la %r0, 42", arch = 's390', bits=64)
681
        b'A\x00\x00*'
682
    """
683
    result = ''
1✔
684

685
    assembler = _assembler()
1✔
686
    linker    = _linker()
1✔
687
    objcopy   = _objcopy() + ['-j', '.shellcode', '-Obinary']
1✔
688
    code      = ''
1✔
689
    code      += _arch_header()
1✔
690
    code      += cpp(shellcode)
1✔
691

692
    log.debug('Assembling\n%s' % code)
1✔
693

694
    tmpdir    = tempfile.mkdtemp(prefix = 'pwn-asm-')
1✔
695
    step1     = path.join(tmpdir, 'step1')
1✔
696
    step2     = path.join(tmpdir, 'step2')
1✔
697
    step3     = path.join(tmpdir, 'step3')
1✔
698
    step4     = path.join(tmpdir, 'step4')
1✔
699

700
    try:
1✔
701
        with open(step1, 'w') as fd:
1✔
702
            fd.write(code)
1✔
703

704
        _run(assembler + ['-o', step2, step1])
1✔
705

706
        if not vma:
1✔
707
            shutil.copy(step2, step3)
1✔
708

709
        if vma or not extract:
1✔
710
            ldflags = _execstack(linker) + ['-o', step3, step2]
1✔
711
            if vma:
1✔
712
                ldflags += ['--section-start=.shellcode=%#x' % vma,
1✔
713
                            '--entry=%#x' % vma]
714
            elif shared:
1✔
715
                ldflags += ['-shared', '-init=_start']
1✔
716

717
            # In order to ensure that we generate ELF files with 4k pages,
718
            # and not e.g. 65KB pages (AArch64), force the page size.
719
            # This is a result of GNU Gold being silly.
720
            #
721
            # Introduced in commit dd58f409 without supporting evidence,
722
            # this shouldn't do anything except keep consistent page granularity
723
            # across architectures.
724
            ldflags += ['-z', 'max-page-size=4096',
1✔
725
                        '-z', 'common-page-size=4096']
726

727
            _run(linker + ldflags)
1✔
728

729
        elif open(step2,'rb').read(4) == b'\x7fELF':
1!
730
            # Sanity check for seeing if the output has relocations
731
            relocs = subprocess.check_output(
1✔
732
                [which_binutils('readelf'), '-r', step2],
733
                universal_newlines = True
734
            ).strip()
735
            if extract and len(relocs.split('\n')) > 1:
1!
736
                log.error('Shellcode contains relocations:\n%s' % relocs)
×
737
        else:
738
            shutil.copy(step2, step3)
×
739

740
        if not extract:
1✔
741
            return step3
1✔
742

743
        _run(objcopy + [step3, step4])
1✔
744

745
        with open(step4, 'rb') as fd:
1✔
746
            result = fd.read()
1✔
747

748
    except Exception:
×
749
        lines = '\n'.join('%4i: %s' % (i+1,line) for (i,line) in enumerate(code.splitlines()))
×
750
        log.exception("An error occurred while assembling:\n%s" % lines)
×
751
    else:
752
        atexit.register(lambda: shutil.rmtree(tmpdir))
1!
753

754
    return result
1✔
755

756
@LocalContext
1✔
757
def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
1✔
758
    """disasm(data, ...) -> str
759

760
    Disassembles a bytestring into human readable assembler.
761

762
    To see which architectures are supported,
763
    look in :mod:`pwnlib.contex`.
764

765
    Arguments:
766
      data(str): Bytestring to disassemble.
767
      vma(int): Passed through to the --adjust-vma argument of objdump
768
      byte(bool): Include the hex-printed bytes in the disassembly
769
      offset(bool): Include the virtual memory address in the disassembly
770

771
    Kwargs:
772
      Any arguments/properties that can be set on ``context``
773

774
    Examples:
775

776
        >>> print(disasm(unhex('b85d000000'), arch = 'i386'))
777
           0:   b8 5d 00 00 00          mov    eax, 0x5d
778
        >>> print(disasm(unhex('b85d000000'), arch = 'i386', byte = 0))
779
           0:   mov    eax, 0x5d
780
        >>> print(disasm(unhex('b85d000000'), arch = 'i386', byte = 0, offset = 0))
781
        mov    eax, 0x5d
782
        >>> print(disasm(unhex('b817000000'), arch = 'amd64'))
783
           0:   b8 17 00 00 00          mov    eax, 0x17
784
        >>> print(disasm(unhex('48c7c017000000'), arch = 'amd64'))
785
           0:   48 c7 c0 17 00 00 00    mov    rax, 0x17
786
        >>> print(disasm(unhex('04001fe552009000'), arch = 'arm'))
787
           0:   e51f0004        ldr     r0, [pc, #-4]   ; 0x4
788
           4:   00900052        addseq  r0, r0, r2, asr r0
789
        >>> print(disasm(unhex('4ff00500'), arch = 'thumb', bits=32))
790
           0:   f04f 0005       mov.w   r0, #5
791
        >>> print(disasm(unhex('656664676665400F18A4000000000051'), byte=0, arch='amd64'))
792
           0:   gs data16 fs rex nop WORD PTR gs:[eax+eax*1+0x0]
793
           f:   push   rcx
794
        >>> print(disasm(unhex('01000000'), arch='sparc64'))
795
           0:   01 00 00 00     nop
796
        >>> print(disasm(unhex('60000000'), arch='powerpc64'))
797
           0:   60 00 00 00     nop
798
        >>> print(disasm(unhex('00000000'), arch='mips64'))
799
           0:   00000000        nop
800
        >>> print(disasm(unhex('48b84141414141414100c3'), arch='amd64'))
801
           0:   48 b8 41 41 41 41 41 41 41 00   movabs rax, 0x41414141414141
802
           a:   c3                      ret
803
        >>> print(disasm(unhex('00000000'), vma=0x80000000, arch='mips'))
804
        80000000:       00000000        nop
805
    """
806
    result = ''
1✔
807

808
    arch   = context.arch
1✔
809

810
    tmpdir = tempfile.mkdtemp(prefix = 'pwn-disasm-')
1✔
811
    step1  = path.join(tmpdir, 'step1')
1✔
812
    step2  = path.join(tmpdir, 'step2')
1✔
813

814
    bfdarch = _bfdarch()
1✔
815
    bfdname = _bfdname()
1✔
816
    objdump = _objdump() + ['-w', '-d', '--adjust-vma', str(vma), '-b', bfdname]
1✔
817
    objcopy = _objcopy() + [
1✔
818
        '-I', 'binary',
819
        '-O', bfdname,
820
        '-B', bfdarch,
821
        '--set-section-flags', '.data=code',
822
        '--rename-section', '.data=.text',
823
    ]
824

825
    if not byte:
1✔
826
        objdump += ['--no-show-raw-insn']
1✔
827

828
    if arch == 'thumb':
1✔
829
        objcopy += ['--prefix-symbol=$t.']
1✔
830
    else:
831
        objcopy += ['-w', '-N', '*']
1✔
832

833
    try:
1✔
834

835
        with open(step1, 'wb') as fd:
1✔
836
            fd.write(data)
1✔
837

838
        _run(objcopy + [step1, step2])
1✔
839

840
        output0 = _run(objdump + [step2])
1✔
841
        output1 = re.split(r'<\.text(?:\+0x0)?>:\n', output0, flags=re.MULTILINE)
1✔
842

843
        if len(output1) != 2:
1!
844
            log.error('Could not find .text in objdump output:\n%s' % output0)
×
845

846
        result = output1[1].strip('\n').rstrip().expandtabs()
1✔
847
    except Exception:
×
848
        log.exception("An error occurred while disassembling:\n%s" % data)
×
849
    else:
850
        atexit.register(lambda: shutil.rmtree(tmpdir))
1!
851

852

853
    lines = []
1✔
854
    pattern = '^( *[0-9a-f]+: *)', '((?:[0-9a-f]+ )+ *)', '(.*)'
1✔
855
    if not byte:
1✔
856
        pattern = pattern[::2]
1✔
857
    pattern = ''.join(pattern)
1✔
858
    for line in result.splitlines():
1✔
859
        match = re.search(pattern, line)
1✔
860
        if not match:
1✔
861
            lines.append(line)
1✔
862
            continue
1✔
863

864
        groups = match.groups()
1✔
865
        if byte:
1✔
866
            o, b, i = groups
1✔
867
        else:
868
            o, i = groups
1✔
869

870
        line = ''
1✔
871

872
        if offset:
1✔
873
            line += o
1✔
874
        if byte:
1✔
875
            line += b
1✔
876
        if instructions:
1✔
877
            line += i
1✔
878
        lines.append(line)
1✔
879

880
    return re.sub(',([^ ])', r', \1', '\n'.join(lines))
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

© 2025 Coveralls, Inc