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

Gallopsled / pwntools / 781b1967170151f34ef8334c55b13a3fe6c70e11

pending completion
781b1967170151f34ef8334c55b13a3fe6c70e11

push

github-actions

Arusekk
Use global env

3903 of 6420 branches covered (60.79%)

12247 of 16698 relevant lines covered (73.34%)

0.73 hits per line

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

73.2
/pwnlib/util/packing.py
1
 # -*- coding: utf-8 -*-
2
r"""
1✔
3
Module for packing and unpacking integers.
4

5
Simplifies access to the standard ``struct.pack`` and ``struct.unpack``
6
functions, and also adds support for packing/unpacking arbitrary-width
7
integers.
8

9
The packers are all context-aware for ``endian`` and ``signed`` arguments,
10
though they can be overridden in the parameters.
11

12
Examples:
13

14
    >>> p8(0)
15
    b'\x00'
16
    >>> p32(0xdeadbeef)
17
    b'\xef\xbe\xad\xde'
18
    >>> p32(0xdeadbeef, endian='big')
19
    b'\xde\xad\xbe\xef'
20
    >>> with context.local(endian='big'): p32(0xdeadbeef)
21
    b'\xde\xad\xbe\xef'
22

23
    Make a frozen packer, which does not change with context.
24

25
    >>> p=make_packer('all')
26
    >>> p(0xff)
27
    b'\xff'
28
    >>> p(0x1ff)
29
    b'\xff\x01'
30
    >>> with context.local(endian='big'): print(repr(p(0x1ff)))
31
    b'\xff\x01'
32
"""
33
from __future__ import absolute_import
1✔
34
from __future__ import division
1✔
35

36
import collections
1✔
37
import six
1✔
38
import struct
1✔
39
import sys
1✔
40
import warnings
1✔
41

42
from six.moves import range
1✔
43

44
from pwnlib.context import LocalNoarchContext
1✔
45
from pwnlib.context import context
1✔
46
from pwnlib.log import getLogger
1✔
47

48
from pwnlib.util import iters
1✔
49

50
mod = sys.modules[__name__]
1✔
51
log = getLogger(__name__)
1✔
52

53
def pack(number, word_size = None, endianness = None, sign = None, **kwargs):
1✔
54
    r"""pack(number, word_size = None, endianness = None, sign = None, **kwargs) -> str
55

56
    Packs arbitrary-sized integer.
57

58
    Word-size, endianness and signedness is done according to context.
59

60
    `word_size` can be any positive number or the string "all". Choosing the
61
    string "all" will output a string long enough to contain all the significant
62
    bits and thus be decodable by :func:`unpack`.
63

64
    `word_size` can be any positive number. The output will contain word_size/8
65
    rounded up number of bytes. If word_size is not a multiple of 8, it will be
66
    padded with zeroes up to a byte boundary.
67

68
    Arguments:
69
        number (int): Number to convert
70
        word_size (int): Word size of the converted integer or the string 'all' (in bits).
71
        endianness (str): Endianness of the converted integer ("little"/"big")
72
        sign (str): Signedness of the converted integer (False/True)
73
        kwargs: Anything that can be passed to context.local
74

75
    Returns:
76
        The packed number as a string.
77

78
    Examples:
79
        >>> pack(0x414243, 24, 'big', True)
80
        b'ABC'
81
        >>> pack(0x414243, 24, 'little', True)
82
        b'CBA'
83
        >>> pack(0x814243, 24, 'big', False)
84
        b'\x81BC'
85
        >>> pack(0x814243, 24, 'big', True)
86
        Traceback (most recent call last):
87
           ...
88
        ValueError: pack(): number does not fit within word_size
89
        >>> pack(0x814243, 25, 'big', True)
90
        b'\x00\x81BC'
91
        >>> pack(-1, 'all', 'little', True)
92
        b'\xff'
93
        >>> pack(-256, 'all', 'big', True)
94
        b'\xff\x00'
95
        >>> pack(0x0102030405, 'all', 'little', True)
96
        b'\x05\x04\x03\x02\x01'
97
        >>> pack(-1)
98
        b'\xff\xff\xff\xff'
99
        >>> pack(0x80000000, 'all', 'big', True)
100
        b'\x00\x80\x00\x00\x00'
101
"""
102
    if sign is None and number < 0:
1✔
103
        sign = True
1✔
104

105
    if word_size != 'all':
1✔
106
        kwargs.setdefault('word_size', word_size)
1✔
107

108
    kwargs.setdefault('endianness', endianness)
1✔
109
    kwargs.setdefault('sign', sign)
1✔
110

111
    with context.local(**kwargs):
1✔
112
        # Lookup in context if not found
113
        word_size  = 'all' if word_size == 'all' else context.word_size
1✔
114
        endianness = context.endianness
1✔
115
        sign       = context.sign
1✔
116

117
        if not isinstance(number, six.integer_types):
1!
118
            raise ValueError("pack(): number must be of type (int,long) (got %r)" % type(number))
×
119

120
        if not isinstance(sign, bool):
1!
121
            raise ValueError("pack(): sign must be either True or False (got %r)" % sign)
×
122

123
        if endianness not in ['little', 'big']:
1!
124
            raise ValueError("pack(): endianness must be either 'little' or 'big' (got %r)" % endianness)
×
125

126
        # Verify that word_size make sense
127
        if word_size == 'all':
1✔
128
            if number == 0:
1!
129
                word_size = 8
×
130
            elif number > 0:
1✔
131
                if sign:
1✔
132
                    word_size = (number.bit_length() | 7) + 1
1✔
133
                else:
134
                    word_size = ((number.bit_length() - 1) | 7) + 1
1✔
135
            else:
136
                if not sign:
1!
137
                    raise ValueError("pack(): number does not fit within word_size")
×
138
                word_size = ((number + 1).bit_length() | 7) + 1
1✔
139
        elif not isinstance(word_size, six.integer_types) or word_size <= 0:
1!
140
            raise ValueError("pack(): word_size must be a positive integer or the string 'all'")
×
141

142
        if sign:
1✔
143
            limit = 1 << (word_size-1)
1✔
144
            if not -limit <= number < limit:
1✔
145
                raise ValueError("pack(): number does not fit within word_size")
1✔
146
        else:
147
            limit = 1 << word_size
1✔
148
            if not 0 <= number < limit:
1!
149
                raise ValueError("pack(): number does not fit within word_size [%i, %r, %r]" % (0, number, limit))
×
150

151
        # Normalize number and size now that we have verified them
152
        # From now on we can treat positive and negative numbers the same
153
        number = number & ((1 << word_size) - 1)
1✔
154
        byte_size = (word_size + 7) // 8
1✔
155

156
        out = []
1✔
157

158
        for _ in range(byte_size):
1✔
159
            out.append(_p8lu(number & 0xff))
1✔
160
            number = number >> 8
1✔
161

162
        if endianness == 'little':
1✔
163
            return b''.join(out)
1✔
164
        else:
165
            return b''.join(reversed(out))
1✔
166

167
@LocalNoarchContext
1✔
168
def unpack(data, word_size = None):
1✔
169
    r"""unpack(data, word_size = None, endianness = None, sign = None, **kwargs) -> int
170

171
    Unpacks arbitrary-sized integer.
172

173
    Word-size, endianness and signedness is done according to context.
174

175
    `word_size` can be any positive number or the string "all". Choosing the
176
    string "all" is equivalent to ``len(data)*8``.
177

178
    If `word_size` is not a multiple of 8, then the bits used for padding
179
    are discarded.
180

181
    Arguments:
182
        number (int): String to convert
183
        word_size (int): Word size of the converted integer or the string "all" (in bits).
184
        endianness (str): Endianness of the converted integer ("little"/"big")
185
        sign (str): Signedness of the converted integer (False/True)
186
        kwargs: Anything that can be passed to context.local
187

188
    Returns:
189
        The unpacked number.
190

191
    Examples:
192
        >>> hex(unpack(b'\xaa\x55', 16, endian='little', sign=False))
193
        '0x55aa'
194
        >>> hex(unpack(b'\xaa\x55', 16, endian='big', sign=False))
195
        '0xaa55'
196
        >>> hex(unpack(b'\xaa\x55', 16, endian='big', sign=True))
197
        '-0x55ab'
198
        >>> hex(unpack(b'\xaa\x55', 15, endian='big', sign=True))
199
        '0x2a55'
200
        >>> hex(unpack(b'\xff\x02\x03', 'all', endian='little', sign=True))
201
        '0x302ff'
202
        >>> hex(unpack(b'\xff\x02\x03', 'all', endian='big', sign=True))
203
        '-0xfdfd'
204
    """
205

206
    # Lookup in context if not found
207
    word_size  = word_size  or context.word_size
1✔
208
    endianness = context.endianness
1✔
209
    sign       = context.sign
1✔
210
    data = _need_bytes(data, 2)
1✔
211

212
    # Verify that word_size make sense
213
    if word_size == 'all':
1✔
214
        word_size = len(data) * 8
1✔
215
    elif not isinstance(word_size, six.integer_types) or word_size <= 0:
1!
216
        raise ValueError("unpack(): word_size must be a positive integer or the string 'all'")
×
217

218
    byte_size = (word_size + 7) // 8
1✔
219

220
    if byte_size != len(data):
1!
221
        raise ValueError("unpack(): data must have length %d, since word_size was %d" % (byte_size, word_size))
×
222

223
    number = 0
1✔
224

225
    if endianness == "little":
1✔
226
        data = reversed(data)
1✔
227
    data = bytearray(data)
1✔
228

229
    for c in data:
1✔
230
        number = (number << 8) + c
1✔
231

232
    number = number & ((1 << word_size) - 1)
1✔
233

234
    if not sign:
1✔
235
        return int(number)
1✔
236

237
    signbit = number & (1 << (word_size-1))
1✔
238
    return int(number - 2*signbit)
1✔
239

240
@LocalNoarchContext
1✔
241
def unpack_many(data, word_size = None):
1✔
242
    """unpack_many(data, word_size = None, endianness = None, sign = None) -> int list
243

244
    Splits `data` into groups of ``word_size//8`` bytes and calls :func:`unpack` on each group.  Returns a list of the results.
245

246
    `word_size` must be a multiple of `8` or the string "all".  In the latter case a singleton list will always be returned.
247

248
    Args
249
        number (int): String to convert
250
        word_size (int): Word size of the converted integers or the string "all" (in bits).
251
        endianness (str): Endianness of the converted integer ("little"/"big")
252
        sign (str): Signedness of the converted integer (False/True)
253
        kwargs: Anything that can be passed to context.local
254

255
    Returns:
256
        The unpacked numbers.
257

258
    Examples:
259
        >>> list(map(hex, unpack_many(b'\\xaa\\x55\\xcc\\x33', 16, endian='little', sign=False)))
260
        ['0x55aa', '0x33cc']
261
        >>> list(map(hex, unpack_many(b'\\xaa\\x55\\xcc\\x33', 16, endian='big', sign=False)))
262
        ['0xaa55', '0xcc33']
263
        >>> list(map(hex, unpack_many(b'\\xaa\\x55\\xcc\\x33', 16, endian='big', sign=True)))
264
        ['-0x55ab', '-0x33cd']
265
        >>> list(map(hex, unpack_many(b'\\xff\\x02\\x03', 'all', endian='little', sign=True)))
266
        ['0x302ff']
267
        >>> list(map(hex, unpack_many(b'\\xff\\x02\\x03', 'all', endian='big', sign=True)))
268
        ['-0xfdfd']
269
    """
270
    # Lookup in context if None
271
    word_size  = word_size  or context.word_size
1✔
272
    endianness = context.endianness
1✔
273
    sign       = context.sign
1✔
274

275
    if word_size == 'all':
1✔
276
        return [unpack(data, word_size)]
1✔
277

278
    # Currently we only group on byte boundaries
279
    if word_size % 8 != 0:
1!
280
        raise ValueError("unpack_many(): word_size must be a multiple of 8")
×
281

282
    out = []
1✔
283
    n = word_size // 8
1✔
284
    for i in range(0, len(data), n):
1✔
285
        out.append(unpack(data[i:i+n], word_size))
1✔
286

287
    return list(map(int, out))
1✔
288

289

290

291
#
292
# Make individual packers, e.g. _p8lu
293
#
294
ops   = ['p','u']
1✔
295
sizes = {8:'b', 16:'h', 32:'i', 64:'q'}
1✔
296
ends  = ['b','l']
1✔
297
signs = ['s','u']
1✔
298

299
return_types     = {'p': 'bytes', 'u': 'int'}
1✔
300
op_verbs         = {'p': 'pack', 'u': 'unpack'}
1✔
301
arg_doc          = {'p': 'number (int): Number to convert',
1✔
302
                    'u': 'data (bytes): Byte string to convert'}
303
rv_doc           = {'p': 'The packed number as a byte string',
1✔
304
                    'u': 'The unpacked number'}
305

306

307
def make_single(op,size,end,sign):
1✔
308
    name = '_%s%s%s%s' % (op, size, end, sign)
1✔
309
    fmt  = sizes[size]
1✔
310
    end = '>' if end == 'b' else '<'
1✔
311

312
    if sign == 'u':
1✔
313
        fmt = fmt.upper()
1✔
314
    fmt = end+fmt
1✔
315

316
    struct_op = getattr(struct.Struct(fmt), op_verbs[op])
1✔
317
    if op == 'u':
1✔
318
        def routine(data, stacklevel=1):
1✔
319
            data = _need_bytes(data, stacklevel)
1✔
320
            return struct_op(data)[0]
1✔
321
    else:
322
        def routine(data, stacklevel=None):
1✔
323
            return struct_op(data)
1✔
324
    routine.__name__ = routine.__qualname__ = name
1✔
325

326
    return name, routine
1✔
327

328

329
for op,size,end,sign in iters.product(ops, sizes, ends, signs):
1✔
330
    name, routine = make_single(op,size,end,sign)
1✔
331
    setattr(mod, name, routine)
1✔
332

333

334
#
335
# Make normal user-oriented packers, e.g. p8
336
#
337
def make_multi(op, size):
1✔
338

339
    name = "%s%s" % (op,size)
1✔
340

341
    ls = getattr(mod, "_%sls" % (name))
1✔
342
    lu = getattr(mod, "_%slu" % (name))
1✔
343
    bs = getattr(mod, "_%sbs" % (name))
1✔
344
    bu = getattr(mod, "_%sbu" % (name))
1✔
345

346
    @LocalNoarchContext
1✔
347
    def routine(number):
348
        endian = context.endian
1✔
349
        signed = context.signed
1✔
350
        return {("little", True  ): ls,
1✔
351
                ("little", False):  lu,
352
                ("big",    True  ): bs,
353
                ("big",    False):  bu}[endian, signed](number, 3)
354

355
    routine.__name__ = name
1✔
356
    routine.__doc__  = """%s%s(number, sign, endian, ...) -> %s
1✔
357

358
    %ss an %s-bit integer
359

360
    Arguments:
361
        %s
362
        endianness (str): Endianness of the converted integer ("little"/"big")
363
        sign (str): Signedness of the converted integer ("unsigned"/"signed")
364
        kwargs (dict): Arguments passed to context.local(), such as
365
            ``endian`` or ``signed``.
366

367
    Returns:
368
        %s
369
    """ % (op, size, return_types[op], op_verbs[op].title(), size, arg_doc[op], rv_doc[op])
370

371
    return name, routine
1✔
372

373

374
for op,size in iters.product(ops, sizes):
1✔
375
    name, routine = make_multi(op,size)
1✔
376
    setattr(mod, name, routine)
1✔
377

378
def make_packer(word_size = None, sign = None, **kwargs):
1✔
379
    """make_packer(word_size = None, endianness = None, sign = None) -> number → str
380

381
    Creates a packer by "freezing" the given arguments.
382

383
    Semantically calling ``make_packer(w, e, s)(data)`` is equivalent to calling
384
    ``pack(data, w, e, s)``. If word_size is one of 8, 16, 32 or 64, it is however
385
    faster to call this function, since it will then use a specialized version.
386

387
    Arguments:
388
        word_size (int): The word size to be baked into the returned packer or the string all (in bits).
389
        endianness (str): The endianness to be baked into the returned packer. ("little"/"big")
390
        sign (str): The signness to be baked into the returned packer. ("unsigned"/"signed")
391
        kwargs: Additional context flags, for setting by alias (e.g. ``endian=`` rather than index)
392

393
    Returns:
394
        A function, which takes a single argument in the form of a number and returns a string
395
        of that number in a packed form.
396

397
    Examples:
398
        >>> p = make_packer(32, endian='little', sign='unsigned')
399
        >>> p
400
        <function _p32lu at 0x...>
401
        >>> p(42)
402
        b'*\\x00\\x00\\x00'
403
        >>> p(-1)
404
        Traceback (most recent call last):
405
            ...
406
        error: integer out of range for 'I' format code
407
        >>> make_packer(33, endian='little', sign='unsigned')
408
        <function ...<lambda> at 0x...>
409
"""
410
    with context.local(sign=sign, **kwargs):
1✔
411
        word_size  = word_size or context.word_size
1✔
412
        endianness = context.endianness
1✔
413
        sign       = sign if sign is None else context.sign
1✔
414

415
        if word_size in [8, 16, 32, 64]:
1✔
416
            packer = {
1✔
417
                (8, 0, 0):  _p8lu,
418
                (8, 0, 1):  _p8ls,
419
                (8, 1, 0):  _p8bu,
420
                (8, 1, 1):  _p8bs,
421
                (16, 0, 0): _p16lu,
422
                (16, 0, 1): _p16ls,
423
                (16, 1, 0): _p16bu,
424
                (16, 1, 1): _p16bs,
425
                (32, 0, 0): _p32lu,
426
                (32, 0, 1): _p32ls,
427
                (32, 1, 0): _p32bu,
428
                (32, 1, 1): _p32bs,
429
                (64, 0, 0): _p64lu,
430
                (64, 0, 1): _p64ls,
431
                (64, 1, 0): _p64bu,
432
                (64, 1, 1): _p64bs,
433
            }.get((word_size, {'big': 1, 'little': 0}[endianness], sign))
434

435
            if packer:
1✔
436
                return packer
1✔
437

438
        return lambda number: pack(number, word_size, endianness, sign)
1✔
439

440
@LocalNoarchContext
1✔
441
def make_unpacker(word_size = None, endianness = None, sign = None, **kwargs):
1✔
442
    """make_unpacker(word_size = None, endianness = None, sign = None,  **kwargs) -> str → number
443

444
    Creates an unpacker by "freezing" the given arguments.
445

446
    Semantically calling ``make_unpacker(w, e, s)(data)`` is equivalent to calling
447
    ``unpack(data, w, e, s)``. If word_size is one of 8, 16, 32 or 64, it is however
448
    faster to call this function, since it will then use a specialized version.
449

450
    Arguments:
451
        word_size (int): The word size to be baked into the returned packer (in bits).
452
        endianness (str): The endianness to be baked into the returned packer. ("little"/"big")
453
        sign (str): The signness to be baked into the returned packer. ("unsigned"/"signed")
454
        kwargs: Additional context flags, for setting by alias (e.g. ``endian=`` rather than index)
455

456
    Returns:
457
        A function, which takes a single argument in the form of a string and returns a number
458
        of that string in an unpacked form.
459

460
    Examples:
461
        >>> u = make_unpacker(32, endian='little', sign='unsigned')
462
        >>> u
463
        <function _u32lu at 0x...>
464
        >>> hex(u(b'/bin'))
465
        '0x6e69622f'
466
        >>> u(b'abcde')
467
        Traceback (most recent call last):
468
            ...
469
        error: unpack requires a string argument of length 4
470
        >>> make_unpacker(33, endian='little', sign='unsigned')
471
        <function ...<lambda> at 0x...>
472
"""
473
    word_size  = word_size or context.word_size
1✔
474
    endianness = context.endianness
1✔
475
    sign       = context.sign
1✔
476

477
    if word_size in [8, 16, 32, 64]:
1✔
478
        endianness = 1 if endianness == 'big'    else 0
1✔
479

480
        return {
1✔
481
            (8, 0, 0):  _u8lu,
482
            (8, 0, 1):  _u8ls,
483
            (8, 1, 0):  _u8bu,
484
            (8, 1, 1):  _u8bs,
485
            (16, 0, 0): _u16lu,
486
            (16, 0, 1): _u16ls,
487
            (16, 1, 0): _u16bu,
488
            (16, 1, 1): _u16bs,
489
            (32, 0, 0): _u32lu,
490
            (32, 0, 1): _u32ls,
491
            (32, 1, 0): _u32bu,
492
            (32, 1, 1): _u32bs,
493
            (64, 0, 0): _u64lu,
494
            (64, 0, 1): _u64ls,
495
            (64, 1, 0): _u64bu,
496
            (64, 1, 1): _u64bs,
497
        }[word_size, endianness, sign]
498
    else:
499
        return lambda number: unpack(number, word_size, endianness, sign)
1!
500

501
def _fit(pieces, preprocessor, packer, filler, stacklevel=1):
1✔
502

503
    # Pulls bytes from `filler` and adds them to `pad` until it ends in `key`.
504
    # Returns the index of `key` in `pad`.
505
    pad = bytearray()
1✔
506
    def fill(key):
1✔
507
        key = bytearray(key)
1✔
508
        offset = pad.find(key)
1✔
509
        while offset == -1:
1✔
510
            pad.append(next(filler))
1✔
511
            offset = pad.find(key, -len(key))
1✔
512
        return offset
1✔
513

514
    # Key conversion:
515
    # - convert str/unicode keys to offsets
516
    # - convert large int (no null-bytes in a machine word) keys to offsets
517
    pieces_ = dict()
1✔
518
    large_key = 2**(context.word_size-8)
1✔
519
    for k, v in pieces.items():
1✔
520
        if isinstance(k, six.integer_types):
1✔
521
            if k >= large_key:
1✔
522
                k = fill(pack(k))
1✔
523
        elif isinstance(k, (six.text_type, bytearray, bytes)):
1!
524
            k = fill(_need_bytes(k, stacklevel, 0x80))
1✔
525
        else:
526
            raise TypeError("flat(): offset must be of type int or str, but got '%s'" % type(k))
×
527
        if k in pieces_:
1!
528
            raise ValueError("flag(): multiple values at offset %d" % k)
×
529
        pieces_[k] = v
1✔
530
    pieces = pieces_
1✔
531

532
    # We must "roll back" `filler` so each recursive call to `_flat` gets it in
533
    # the right position
534
    filler = iters.chain(pad, filler)
1✔
535

536
    # Build output
537
    out = b''
1✔
538

539
    # Negative indices need to be removed and then re-submitted
540
    negative = {k:v for k,v in pieces.items() if isinstance(k, int) and k<0}
1✔
541

542
    for k in negative:
1✔
543
        del pieces[k]
1✔
544

545
    # Positive output
546
    for k, v in sorted(pieces.items()):
1✔
547
        if k < len(out):
1!
548
            raise ValueError("flat(): data at offset %d overlaps with previous data which ends at offset %d" % (k, len(out)))
×
549

550
        # Fill up to offset
551
        while len(out) < k:
1✔
552
            out += p8(next(filler))
1✔
553

554
        # Recursively flatten data
555
        out += _flat([v], preprocessor, packer, filler, stacklevel + 1)
1✔
556

557
    # Now do negative indices
558
    out_negative = b''
1✔
559
    if negative:
1✔
560
        most_negative = min(negative.keys())
1✔
561
        for k, v in sorted(negative.items()):
1✔
562
            k += -most_negative
1✔
563

564
            if k < len(out_negative):
1!
565
                raise ValueError("flat(): data at offset %d overlaps with previous data which ends at offset %d" % (k, len(out)))
×
566

567
            # Fill up to offset
568
            while len(out_negative) < k:
1✔
569
                out_negative += p8(next(filler))
1✔
570

571
            # Recursively flatten data
572
            out_negative += _flat([v], preprocessor, packer, filler, stacklevel + 1)
1✔
573

574
    return filler, out_negative + out
1✔
575

576
def _flat(args, preprocessor, packer, filler, stacklevel=1):
1✔
577
    out = []
1✔
578
    for arg in args:
1✔
579

580
        if not isinstance(arg, (list, tuple, dict)):
1✔
581
            arg_ = preprocessor(arg)
1✔
582
            if arg_ is not None:
1✔
583
                arg = arg_
1✔
584

585
        if hasattr(arg, '__flat__'):
1✔
586
            val = arg.__flat__()
1✔
587
        elif isinstance(arg, (list, tuple)):
1✔
588
            val = _flat(arg, preprocessor, packer, filler, stacklevel + 1)
1✔
589
        elif isinstance(arg, dict):
1✔
590
            filler, val = _fit(arg, preprocessor, packer, filler, stacklevel + 1)
1✔
591
        elif isinstance(arg, bytes):
1✔
592
            val = arg
1✔
593
        elif isinstance(arg, six.text_type):
1!
594
            val = _need_bytes(arg, stacklevel + 1)
×
595
        elif isinstance(arg, six.integer_types):
1✔
596
            val = packer(arg)
1✔
597
        elif isinstance(arg, bytearray):
1!
598
            val = bytes(arg)
1✔
599
        else:
600
            raise ValueError("flat(): Flat does not support values of type %s" % type(arg))
×
601

602
        out.append(val)
1✔
603

604
        # Advance `filler` for "non-recursive" values
605
        if not isinstance(arg, (list, tuple, dict)):
1✔
606
            for _ in range(len(val)):
1✔
607
                next(filler)
1✔
608

609
    return b''.join(out)
1✔
610

611
@LocalNoarchContext
1✔
612
def flat(*args, **kwargs):
613
    r"""flat(\*args, preprocessor = None, length = None, filler = de_bruijn(),
614
     word_size = None, endianness = None, sign = None) -> str
615

616
    Flattens the arguments into a string.
617

618
    This function takes an arbitrary number of arbitrarily nested lists, tuples
619
    and dictionaries.  It will then find every string and number inside those
620
    and flatten them out.  Strings are inserted directly while numbers are
621
    packed using the :func:`pack` function.  Unicode strings are UTF-8 encoded.
622

623
    Dictionary keys give offsets at which to place the corresponding values
624
    (which are recursively flattened).  Offsets are relative to where the
625
    flattened dictionary occurs in the output (i.e. ``{0: 'foo'}`` is equivalent
626
    to ``'foo'``).  Offsets can be integers, unicode strings or regular strings.
627
    Integer offsets >= ``2**(word_size-8)`` are converted to a string using
628
    :func:`pack`.  Unicode strings are UTF-8 encoded.  After these conversions
629
    offsets are either integers or strings.  In the latter case, the offset will
630
    be the lowest index at which the string occurs in `filler`.  See examples
631
    below.
632

633
    Space between pieces of data is filled out using the iterable `filler`.  The
634
    `n`'th byte in the output will be byte at index ``n % len(iterable)`` byte
635
    in `filler` if it has finite length or the byte at index `n` otherwise.
636

637
    If `length` is given, the output will be padded with bytes from `filler` to
638
    be this size.  If the output is longer than `length`, a :py:exc:`ValueError`
639
    exception is raised.
640

641
    The three kwargs `word_size`, `endianness` and `sign` will default to using
642
    values in :mod:`pwnlib.context` if not specified as an argument.
643

644
    Arguments:
645
      args: Values to flatten
646
      preprocessor (function): Gets called on every element to optionally
647
         transform the element before flattening. If :const:`None` is
648
         returned, then the original value is used.
649
      length: The length of the output.
650
      filler: Iterable to use for padding.
651
      word_size (int): Word size of the converted integer.
652
      endianness (str): Endianness of the converted integer ("little"/"big").
653
      sign (str): Signedness of the converted integer (False/True)
654

655
    Examples:
656

657
        (Test setup, please ignore)
658
    
659
        >>> context.clear()
660

661
        Basic usage of :meth:`flat` works similar to the pack() routines.
662

663
        >>> flat(4)
664
        b'\x04\x00\x00\x00'
665

666
        :meth:`flat` works with strings, bytes, lists, and dictionaries.
667

668
        >>> flat(b'X')
669
        b'X'
670
        >>> flat([1,2,3])
671
        b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'
672
        >>> flat({4:b'X'})
673
        b'aaaaX'
674

675
        :meth:`.flat` flattens all of the values provided, and allows nested lists
676
        and dictionaries.
677

678
        >>> flat([{4:b'X'}] * 2)
679
        b'aaaaXaaacX'
680
        >>> flat([[[[[[[[[1]]]], 2]]]]])
681
        b'\x01\x00\x00\x00\x02\x00\x00\x00'
682

683
        You can also provide additional arguments like endianness, word-size, and
684
        whether the values are treated as signed or not.
685

686
        >>> flat(1, b"test", [[[b"AB"]*2]*3], endianness = 'little', word_size = 16, sign = False)
687
        b'\x01\x00testABABABABABAB'
688

689
        A preprocessor function can be provided in order to modify the values in-flight.
690
        This example converts increments each value by 1, then converts to a byte string.
691

692
        >>> flat([1, [2, 3]], preprocessor = lambda x: str(x+1).encode())
693
        b'234'
694

695
        Using dictionaries is a fast way to get specific values at specific offsets,
696
        without having to do ``data += "foo"`` repeatedly.
697

698
        >>> flat({12: 0x41414141,
699
        ...       24: b'Hello',
700
        ...      })
701
        b'aaaabaaacaaaAAAAeaaafaaaHello'
702

703
        Dictionary usage permits directly using values derived from :func:`.cyclic`.
704
        See :func:`.cyclic`, :function:`pwnlib.context.context.cyclic_alphabet`, and :data:`.context.cyclic_size`
705
        for more options.  
706

707
        The cyclic pattern can be provided as either the text or hexadecimal offset.
708

709
        >>> flat({ 0x61616162: b'X'})
710
        b'aaaaX'
711
        >>> flat({'baaa': b'X'})
712
        b'aaaaX'
713

714
        Fields do not have to be in linear order, and can be freely mixed.
715
        This also works with cyclic offsets.
716

717
        >>> flat({2: b'A', 0:b'B'})
718
        b'BaA'
719
        >>> flat({0x61616161: b'x', 0x61616162: b'y'})
720
        b'xaaay'
721
        >>> flat({0x61616162: b'y', 0x61616161: b'x'})
722
        b'xaaay'
723

724
        Fields do not have to be in order, and can be freely mixed.
725

726
        >>> flat({'caaa': b'XXXX', 16: b'\x41', 20: 0xdeadbeef})
727
        b'aaaabaaaXXXXdaaaAaaa\xef\xbe\xad\xde'
728
        >>> flat({ 8: [0x41414141, 0x42424242], 20: b'CCCC'})
729
        b'aaaabaaaAAAABBBBeaaaCCCC'
730
        >>> fit({
731
        ...     0x61616161: b'a',
732
        ...     1: b'b',
733
        ...     0x61616161+2: b'c',
734
        ...     3: b'd',
735
        ... })
736
        b'abadbaaac'
737

738
        By default, gaps in the data are filled in with the :meth:`.cyclic` pattern.
739
        You can customize this by providing an iterable or method for the ``filler``
740
        argument.
741

742
        >>> flat({12: b'XXXX'}, filler = b'_', length = 20)
743
        b'____________XXXX____'
744
        >>> flat({12: b'XXXX'}, filler = b'AB', length = 20)
745
        b'ABABABABABABXXXXABAB'
746

747
        Nested dictionaries also work as expected.
748

749
        >>> flat({4: {0: b'X', 4: b'Y'}})
750
        b'aaaaXaaaY'
751
        >>> fit({4: {4: b'XXXX'}})
752
        b'aaaabaaaXXXX'
753

754
        Negative indices are also supported, though this only works for integer
755
        keys.
756
    
757
        >>> flat({-4: b'x', -1: b'A', 0: b'0', 4: b'y'})
758
        b'xaaA0aaay'
759
    """
760
    # HACK: To avoid circular imports we need to delay the import of `cyclic`
761
    from pwnlib.util import cyclic
1✔
762

763
    preprocessor = kwargs.pop('preprocessor', lambda x: None)
1✔
764
    filler       = kwargs.pop('filler', cyclic.de_bruijn())
1✔
765
    length       = kwargs.pop('length', None)
1✔
766
    stacklevel   = kwargs.pop('stacklevel', 0)
1✔
767

768
    if isinstance(filler, (str, six.text_type)):
1✔
769
        filler = bytearray(_need_bytes(filler))
1✔
770

771
    if kwargs != {}:
1!
772
        raise TypeError("flat() does not support argument %r" % kwargs.popitem()[0])
×
773

774
    filler = iters.cycle(filler)
1✔
775
    out = _flat(args, preprocessor, make_packer(), filler, stacklevel + 2)
1✔
776

777
    if length:
1✔
778
        if len(out) > length:
1!
779
            raise ValueError("flat(): Arguments does not fit within `length` (= %d) bytes" % length)
×
780
        out += b''.join(p8(next(filler)) for _ in range(length - len(out)))
1✔
781

782
    return out
1✔
783

784
def fit(*args, **kwargs):
1✔
785
    """Legacy alias for :func:`flat`"""
786
    kwargs['stacklevel'] = kwargs.get('stacklevel', 0) + 1
1✔
787
    return flat(*args, **kwargs)
1✔
788

789
"""
790
    Generates a string from a dictionary mapping offsets to data to place at
791
    that offset.
792

793
    For each key-value pair in `pieces`, the key is either an offset or a byte
794
    sequence.  In the latter case, the offset will be the lowest index at which
795
    the sequence occurs in `filler`.  See examples below.
796

797
    Each piece of data is passed to :meth:`flat` along with the keyword
798
    arguments `word_size`, `endianness` and `sign`.
799

800
    Space between pieces of data is filled out using the iterable `filler`.  The
801
    `n`'th byte in the output will be byte at index ``n % len(iterable)`` byte
802
    in `filler` if it has finite length or the byte at index `n` otherwise.
803

804
    If `length` is given, the output will padded with bytes from `filler` to be
805
    this size.  If the output is longer than `length`, a :py:exc:`ValueError`
806
    exception is raised.
807

808
    If entries in `pieces` overlap, a :py:exc:`ValueError` exception is
809
    raised.
810

811
    Arguments:
812
      pieces: Offsets and values to output.
813
      length: The length of the output.
814
      filler: Iterable to use for padding.
815
      preprocessor (function): Gets called on every element to optionally
816
         transform the element before flattening. If :const:`None` is
817
         returned, then the original value is used.
818
      word_size (int): Word size of the converted integer (in bits).
819
      endianness (str): Endianness of the converted integer ("little"/"big").
820
      sign (str): Signedness of the converted integer (False/True)
821

822
    Examples:
823

824
    """
825

826
def signed(integer):
1✔
827
    return unpack(pack(integer), signed=True)
×
828

829
def unsigned(integer):
1✔
830
    return unpack(pack(integer))
×
831

832
def dd(dst, src, count = 0, skip = 0, seek = 0, truncate = False):
1✔
833
    """dd(dst, src, count = 0, skip = 0, seek = 0, truncate = False) -> dst
834

835
    Inspired by the command line tool ``dd``, this function copies `count` byte
836
    values from offset `seek` in `src` to offset `skip` in `dst`.  If `count` is
837
    0, all of ``src[seek:]`` is copied.
838

839
    If `dst` is a mutable type it will be updated.  Otherwise, a new instance of
840
    the same type will be created.  In either case the result is returned.
841

842
    `src` can be an iterable of characters or integers, a unicode string or a
843
    file object.  If it is an iterable of integers, each integer must be in the
844
    range [0;255].  If it is a unicode string, its UTF-8 encoding will be used.
845

846
    The seek offset of file objects will be preserved.
847

848
    Arguments:
849
        dst: Supported types are :class:`file`, :class:`list`, :class:`tuple`,
850
             :class:`str`, :class:`bytearray` and :class:`unicode`.
851
        src: An iterable of byte values (characters or integers), a unicode
852
             string or a file object.
853
        count (int): How many bytes to copy.  If `count` is 0 or larger than
854
                     ``len(src[seek:])``, all bytes until the end of `src` are
855
                     copied.
856
        skip (int): Offset in `dst` to copy to.
857
        seek (int): Offset in `src` to copy from.
858
        truncate (bool): If :const:`True`, `dst` is truncated at the last copied
859
                         byte.
860

861
    Returns:
862
        A modified version of `dst`.  If `dst` is a mutable type it will be
863
        modified in-place.
864

865
    Examples:
866
        >>> dd(tuple('Hello!'), b'?', skip = 5)
867
        ('H', 'e', 'l', 'l', 'o', b'?')
868
        >>> dd(list('Hello!'), (63,), skip = 5)
869
        ['H', 'e', 'l', 'l', 'o', b'?']
870
        >>> _ = open('/tmp/foo', 'w').write('A' * 10)
871
        >>> dd(open('/tmp/foo'), open('/dev/zero'), skip = 3, count = 4).read()
872
        'AAA\\x00\\x00\\x00\\x00AAA'
873
        >>> _ = open('/tmp/foo', 'w').write('A' * 10)
874
        >>> dd(open('/tmp/foo'), open('/dev/zero'), skip = 3, count = 4, truncate = True).read()
875
        'AAA\\x00\\x00\\x00\\x00'
876
    """
877

878
    # Re-open file objects to make sure we have the mode right
879
    if hasattr(src, 'name'):
1✔
880
        src = open(src.name, 'rb')
1✔
881
    if hasattr(dst, 'name'):
1✔
882
        real_dst = dst
1✔
883
        dst = open(dst.name, 'rb+')
1✔
884

885
    # Special case: both `src` and `dst` are files, so we don't need to hold
886
    # everything in memory
887
    if hasattr(src, 'seek') and hasattr(dst, 'seek'):
1✔
888
        src.seek(seek)
1✔
889
        dst.seek(skip)
1✔
890
        n = 0
1✔
891
        if count:
1!
892
            while n < count:
1✔
893
                s = src.read(min(count - n, 0x1000))
1✔
894
                if not s:
1!
895
                    break
×
896
                n += len(s)
1✔
897
                dst.write(s)
1✔
898
        else:
899
            while True:
×
900
                s = src.read(0x1000)
×
901
                if not s:
×
902
                    break
×
903
                n += len(s)
×
904
                dst.write(s)
×
905
        if truncate:
1✔
906
            dst.truncate(skip + n)
1✔
907
        src.close()
1✔
908
        dst.close()
1✔
909
        return real_dst
1✔
910

911
    # Otherwise get `src` in canonical form, i.e. a string of at most `count`
912
    # bytes
913
    if isinstance(src, six.text_type):
1!
914
        if count:
×
915
            # The only way to know where the `seek`th byte is, is to decode, but
916
            # we only need to decode up to the first `seek + count` code points
917
            src = src[:seek + count].encode('utf8')
×
918
            # The code points may result in more that `seek + count` bytes
919
            src = src[seek : seek + count]
×
920
        else:
921
            src = src.encode('utf8')[seek:]
×
922

923
    elif hasattr(src, 'seek'):
1!
924
        src.seek(seek)
×
925
        src_ = b''
×
926
        if count:
×
927
            while len(src_) < count:
×
928
                s = src.read(count - len(src_))
×
929
                if not s:
×
930
                    break
×
931
                src_ += s
×
932
        else:
933
            while True:
×
934
                s = src.read()
×
935
                if not s:
×
936
                    break
×
937
                src_ += s
×
938
        src.close()
×
939
        src = src_
×
940

941
    elif isinstance(src, bytes):
1✔
942
        if count:
1!
943
            src = src[seek : seek + count]
×
944
        else:
945
            src = src[seek:]
1✔
946

947
    elif hasattr(src, '__iter__'):
1!
948
        src = src[seek:]
1✔
949
        src_ = b''
1✔
950
        for i, b in enumerate(src, seek):
1✔
951
            if count and i > count + seek:
1!
952
                break
×
953
            if isinstance(b, bytes):
1!
954
                src_ += b
×
955
            elif isinstance(b, six.integer_types):
1!
956
                if b > 255 or b < 0:
1!
957
                    raise ValueError("dd(): Source value %d at index %d is not in range [0;255]" % (b, i))
×
958
                src_ += _p8lu(b)
1✔
959
            else:
960
                raise TypeError("dd(): Unsupported `src` element type: %r" % type(b))
×
961
        src = src_
1✔
962

963
    else:
964
        raise TypeError("dd(): Unsupported `src` type: %r" % type(src))
×
965

966
    # If truncate, then where?
967
    if truncate:
1!
968
        truncate = skip + len(src)
×
969

970
    # UTF-8 encode unicode `dst`
971
    if isinstance(dst, six.text_type):
1!
972
        dst = dst.encode('utf8')
×
973
        utf8 = True
×
974
    else:
975
        utf8 = False
1✔
976

977
    # Match on the type of `dst`
978
    if   hasattr(dst, 'seek'):
1!
979
        dst.seek(skip)
×
980
        dst.write(src)
×
981
        if truncate:
×
982
            dst.truncate(truncate)
×
983
        dst.close()
×
984
        dst = real_dst
×
985

986
    elif isinstance(dst, (list, bytearray)):
1✔
987
        dst[skip : skip + len(src)] = list(map(p8, bytearray(src)))
1✔
988
        if truncate:
1!
989
            while len(dst) > truncate:
×
990
                dst.pop()
×
991

992
    elif isinstance(dst, tuple):
1!
993
        tail = dst[skip + len(src):]
1✔
994
        dst = dst[:skip] + tuple(map(p8, bytearray(src)))
1✔
995
        if not truncate:
1!
996
            dst = dst + tail
1✔
997

998
    elif isinstance(dst, bytes):
×
999
        tail = dst[skip + len(src):]
×
1000
        dst = dst[:skip] + src
×
1001
        if not truncate:
×
1002
            dst = dst + tail
×
1003

1004
    else:
1005
        raise TypeError("dd(): Unsupported `dst` type: %r" % type(dst))
×
1006

1007
    if utf8:
1!
1008
        dst = dst.decode('utf8')
×
1009

1010
    return dst
1✔
1011

1012
def _need_bytes(s, level=1, min_wrong=0):
1✔
1013
    if isinstance(s, (bytes, bytearray)):
1✔
1014
        return s   # already bytes
1✔
1015

1016
    encoding = context.encoding
1✔
1017
    errors = 'strict'
1✔
1018
    worst = -1
1✔
1019
    if encoding == 'auto':
1!
1020
        worst = s and max(map(ord, s)) or 0
1✔
1021
        if worst > 255:
1!
1022
            encoding = 'UTF-8'
×
1023
            errors = 'surrogateescape'
×
1024
        elif worst > 127:
1!
1025
            encoding = 'ISO-8859-1'
×
1026
        else:
1027
            encoding = 'ASCII'
1✔
1028

1029
    if worst >= min_wrong:
1!
1030
        warnings.warn("Text is not bytes; assuming {}, no guarantees. See https://docs.pwntools.com/#bytes"
×
1031
                      .format(encoding), BytesWarning, level + 2)
1032
    return s.encode(encoding, errors)
1✔
1033

1034
def _need_text(s, level=1):
1✔
1035
    if isinstance(s, (str, six.text_type)):
1!
1036
        return s   # already text
1✔
1037

1038
    if not isinstance(s, (bytes, bytearray)):
×
1039
        return repr(s)
×
1040

1041
    encoding = context.encoding
×
1042
    errors = 'strict'
×
1043
    if encoding == 'auto':
×
1044
        for encoding in 'ASCII', 'UTF-8', 'ISO-8859-1':
×
1045
            try:
×
1046
                s.decode(encoding)
×
1047
            except UnicodeDecodeError:
×
1048
                pass
×
1049
            else:
1050
                break
×
1051

1052
    warnings.warn("Bytes is not text; assuming {}, no guarantees. See https://docs.pwntools.com/#bytes"
×
1053
                  .format(encoding), BytesWarning, level + 2)
1054
    return s.decode(encoding, errors)
×
1055

1056
def _encode(s):
1✔
1057
    if isinstance(s, (bytes, bytearray)):
1!
1058
        return s   # already bytes
1✔
1059

1060
    if context.encoding == 'auto':
×
1061
        try:
×
1062
            return s.encode('latin1')
×
1063
        except UnicodeEncodeError:
×
1064
            return s.encode('utf-8', 'surrogateescape')
×
1065
    return s.encode(context.encoding)
×
1066

1067
def _decode(b):
1✔
1068
    if context.encoding == 'auto':
1!
1069
        try:
1✔
1070
            return b.decode('utf-8')
1✔
1071
        except UnicodeDecodeError:
×
1072
            return b.decode('latin1')
×
1073
        except AttributeError:
×
1074
            return b
×
1075
    return b.decode(context.encoding)
×
1076

1077
del op, size, end, sign
1✔
1078
del name, routine, mod
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