Skip to content

Commit 4ec8122

Browse files
authored
Add (un)pack shorthands for 40-56 bit numbers u48()/p48() (#2687)
* Add (un)pack shorthands for 40-56 bit numbers `u40`/`p40`, `u48`/`p48`, and `u56`/`p56` can be used to quickly unpack shorter integers. `u48` can be used to unpack addresses leaked using a c-string function which stops at the first null-byte. Instead of doing `u64(data.ljust(8, b'\x00"))` you can now just do `u48(data)`. Refs #2447 * Update CHANGELOG * Fix typo in CHANGELOG
1 parent 2746143 commit 4ec8122

File tree

4 files changed

+182
-1
lines changed

4 files changed

+182
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ The table below shows which release corresponds to each branch, and what date th
115115
- [#2669][2669] asm: try native binutils before fallback architectures
116116
- [#2673][2673] Add libc module for libc-related functions
117117
- [#2680][2680] Cleanup Python 2 legacy
118+
- [#2687][2687] Add (un)pack shorthands for 40-56 bit numbers `u48()`/`p48()`
118119

119120
[2675]: https://github.com/Gallopsled/pwntools/pull/2675
120121
[2652]: https://github.com/Gallopsled/pwntools/pull/2652
@@ -156,6 +157,7 @@ The table below shows which release corresponds to each branch, and what date th
156157
[2669]: https://github.com/Gallopsled/pwntools/pull/2669
157158
[2673]: https://github.com/Gallopsled/pwntools/pull/2673
158159
[2680]: https://github.com/Gallopsled/pwntools/pull/2680
160+
[2687]: https://github.com/Gallopsled/pwntools/pull/2687
159161

160162
## 4.15.0 (`stable`)
161163

pwnlib/elf/elf.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2329,6 +2329,21 @@ def p64(self, address, data, *a, **kw):
23292329
self._update_args(kw)
23302330
return self.write(address, packing.p64(data, *a, **kw))
23312331

2332+
def p56(self, address, data, *a, **kw):
2333+
"""Writes a 56-bit integer ``data`` to the specified ``address``"""
2334+
self._update_args(kw)
2335+
return self.write(address, packing.p56(data, *a, **kw))
2336+
2337+
def p48(self, address, data, *a, **kw):
2338+
"""Writes a 48-bit integer ``data`` to the specified ``address``"""
2339+
self._update_args(kw)
2340+
return self.write(address, packing.p48(data, *a, **kw))
2341+
2342+
def p40(self, address, data, *a, **kw):
2343+
"""Writes a 40-bit integer ``data`` to the specified ``address``"""
2344+
self._update_args(kw)
2345+
return self.write(address, packing.p40(data, *a, **kw))
2346+
23322347
def p32(self, address, data, *a, **kw):
23332348
"""Writes a 32-bit integer ``data`` to the specified ``address``"""
23342349
self._update_args(kw)
@@ -2354,6 +2369,21 @@ def u64(self, address, *a, **kw):
23542369
self._update_args(kw)
23552370
return packing.u64(self.read(address, 8), *a, **kw)
23562371

2372+
def u56(self, address, *a, **kw):
2373+
"""Unpacks an integer from the specified ``address``."""
2374+
self._update_args(kw)
2375+
return packing.u56(self.read(address, 7), *a, **kw)
2376+
2377+
def u48(self, address, *a, **kw):
2378+
"""Unpacks an integer from the specified ``address``."""
2379+
self._update_args(kw)
2380+
return packing.u48(self.read(address, 6), *a, **kw)
2381+
2382+
def u40(self, address, *a, **kw):
2383+
"""Unpacks an integer from the specified ``address``."""
2384+
self._update_args(kw)
2385+
return packing.u40(self.read(address, 5), *a, **kw)
2386+
23572387
def u32(self, address, *a, **kw):
23582388
"""Unpacks an integer from the specified ``address``."""
23592389
self._update_args(kw)

pwnlib/tubes/tube.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1650,12 +1650,18 @@ def shutdown_raw(self, direction):
16501650

16511651

16521652
def p64(self, *a, **kw): return self.send(packing.p64(*a, **kw))
1653+
def p56(self, *a, **kw): return self.send(packing.p56(*a, **kw))
1654+
def p48(self, *a, **kw): return self.send(packing.p48(*a, **kw))
1655+
def p40(self, *a, **kw): return self.send(packing.p40(*a, **kw))
16531656
def p32(self, *a, **kw): return self.send(packing.p32(*a, **kw))
16541657
def p16(self, *a, **kw): return self.send(packing.p16(*a, **kw))
16551658
def p8(self, *a, **kw): return self.send(packing.p8(*a, **kw))
16561659
def pack(self, *a, **kw): return self.send(packing.pack(*a, **kw))
16571660

16581661
def u64(self, *a, **kw): return packing.u64(self.recvn(8), *a, **kw)
1662+
def u56(self, *a, **kw): return packing.u56(self.recvn(7), *a, **kw)
1663+
def u48(self, *a, **kw): return packing.u48(self.recvn(6), *a, **kw)
1664+
def u40(self, *a, **kw): return packing.u40(self.recvn(5), *a, **kw)
16591665
def u32(self, *a, **kw): return packing.u32(self.recvn(4), *a, **kw)
16601666
def u16(self, *a, **kw): return packing.u16(self.recvn(2), *a, **kw)
16611667
def u8(self, *a, **kw): return packing.u8(self.recvn(1), *a, **kw)

pwnlib/util/packing.py

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ def unpack_many(data, word_size = None):
288288
# Make individual packers, e.g. _p8lu
289289
#
290290
ops = ['p','u']
291-
sizes = {8:'b', 16:'h', 32:'i', 64:'q'}
291+
sizes = {8:'b', 16:'h', 32:'i', 40: '', 48: '', 56: '', 64:'q'}
292292
ends = ['b','l']
293293
signs = ['s','u']
294294

@@ -298,6 +298,20 @@ def unpack_many(data, word_size = None):
298298
def make_single(op,size,end,sign):
299299
name = '_%s%s%s%s' % (op, size, end, sign)
300300
fmt = sizes[size]
301+
302+
# Handle non-standard sizes without the struct module.
303+
if fmt == '':
304+
endianess = 'big' if end == 'b' else 'little'
305+
if op == 'u':
306+
def routine(data, stacklevel=1):
307+
data = _need_bytes(data, stacklevel)
308+
return unpack(data, size, endianness=endianess, sign=sign == 's')
309+
else:
310+
def routine(data, stacklevel=None):
311+
return pack(data, size, endianness=endianess, sign=sign == 's')
312+
routine.__name__ = routine.__qualname__ = name
313+
return name, routine
314+
301315
end = '>' if end == 'b' else '<'
302316

303317
if sign == 'u':
@@ -410,6 +424,81 @@ def p32(number, endianness = None, **kwargs):
410424
"""
411425
return _do_packing('p', 32, number, endianness)
412426

427+
@LocalNoarchContext
428+
def p40(number, endianness = None, **kwargs):
429+
"""p40(number, endianness, sign, ...) -> bytes
430+
431+
Packs an 40-bit integer
432+
433+
Arguments:
434+
number (int): Number to convert
435+
endianness (str): Endianness of the converted integer ("little"/"big")
436+
sign (str): Signedness of the converted integer ("unsigned"/"signed")
437+
kwargs (dict): Arguments passed to context.local(), such as
438+
``endian`` or ``signed``.
439+
440+
Returns:
441+
The packed number as a byte string
442+
443+
Examples:
444+
445+
>>> p40(0x4142434445, 'big')
446+
b'ABCDE'
447+
>>> p40(0x4142434445, endianness='big')
448+
b'ABCDE'
449+
"""
450+
return _do_packing('p', 40, number, endianness)
451+
452+
@LocalNoarchContext
453+
def p48(number, endianness = None, **kwargs):
454+
"""p48(number, endianness, sign, ...) -> bytes
455+
456+
Packs an 48-bit integer
457+
458+
Arguments:
459+
number (int): Number to convert
460+
endianness (str): Endianness of the converted integer ("little"/"big")
461+
sign (str): Signedness of the converted integer ("unsigned"/"signed")
462+
kwargs (dict): Arguments passed to context.local(), such as
463+
``endian`` or ``signed``.
464+
465+
Returns:
466+
The packed number as a byte string
467+
468+
Examples:
469+
470+
>>> p48(0x414243444546, 'big')
471+
b'ABCDEF'
472+
>>> p48(0x414243444546, endianness='big')
473+
b'ABCDEF'
474+
"""
475+
return _do_packing('p', 48, number, endianness)
476+
477+
@LocalNoarchContext
478+
def p56(number, endianness = None, **kwargs):
479+
"""p56(number, endianness, sign, ...) -> bytes
480+
481+
Packs an 56-bit integer
482+
483+
Arguments:
484+
number (int): Number to convert
485+
endianness (str): Endianness of the converted integer ("little"/"big")
486+
sign (str): Signedness of the converted integer ("unsigned"/"signed")
487+
kwargs (dict): Arguments passed to context.local(), such as
488+
``endian`` or ``signed``.
489+
490+
Returns:
491+
The packed number as a byte string
492+
493+
Examples:
494+
495+
>>> p56(0x41424344454647, 'big')
496+
b'ABCDEFG'
497+
>>> p56(0x41424344454647, endianness='big')
498+
b'ABCDEFG'
499+
"""
500+
return _do_packing('p', 56, number, endianness)
501+
413502
@LocalNoarchContext
414503
def p64(number, endianness = None, **kwargs):
415504
"""p64(number, endianness, sign, ...) -> bytes
@@ -489,6 +578,60 @@ def u32(data, endianness = None, **kwargs):
489578
"""
490579
return _do_packing('u', 32, data, endianness)
491580

581+
@LocalNoarchContext
582+
def u40(data, endianness = None, **kwargs):
583+
"""u40(data, endianness, sign, ...) -> int
584+
585+
Unpacks an 40-bit integer
586+
587+
Arguments:
588+
data (bytes): Byte string to convert
589+
endianness (str): Endianness of the converted integer ("little"/"big")
590+
sign (str): Signedness of the converted integer ("unsigned"/"signed")
591+
kwargs (dict): Arguments passed to context.local(), such as
592+
``endian`` or ``signed``.
593+
594+
Returns:
595+
The unpacked number
596+
"""
597+
return _do_packing('u', 40, data, endianness)
598+
599+
@LocalNoarchContext
600+
def u48(data, endianness = None, **kwargs):
601+
"""u48(data, endianness, sign, ...) -> int
602+
603+
Unpacks an 48-bit integer
604+
605+
Arguments:
606+
data (bytes): Byte string to convert
607+
endianness (str): Endianness of the converted integer ("little"/"big")
608+
sign (str): Signedness of the converted integer ("unsigned"/"signed")
609+
kwargs (dict): Arguments passed to context.local(), such as
610+
``endian`` or ``signed``.
611+
612+
Returns:
613+
The unpacked number
614+
"""
615+
return _do_packing('u', 48, data, endianness)
616+
617+
@LocalNoarchContext
618+
def u56(data, endianness = None, **kwargs):
619+
"""u56(data, endianness, sign, ...) -> int
620+
621+
Unpacks an 56-bit integer
622+
623+
Arguments:
624+
data (bytes): Byte string to convert
625+
endianness (str): Endianness of the converted integer ("little"/"big")
626+
sign (str): Signedness of the converted integer ("unsigned"/"signed")
627+
kwargs (dict): Arguments passed to context.local(), such as
628+
``endian`` or ``signed``.
629+
630+
Returns:
631+
The unpacked number
632+
"""
633+
return _do_packing('u', 56, data, endianness)
634+
492635
@LocalNoarchContext
493636
def u64(data, endianness = None, **kwargs):
494637
"""u64(data, endianness, sign, ...) -> int

0 commit comments

Comments
 (0)