Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ The table below shows which release corresponds to each branch, and what date th
- [#2615][2615] tube/process: Fix redirecting stderr to stdout on Windows
- [#2639][2639] ROP: Remove stdout and argv workaround in ROPgadget invocation
- [#2630][2630] support `preexec_fn` in `debug()`
- [#2647][2647] packing: Add `overlap` to overlap structures easily

[2638]: https://github.com/Gallopsled/pwntools/pull/2638
[2598]: https://github.com/Gallopsled/pwntools/pull/2598
Expand Down Expand Up @@ -134,6 +135,7 @@ The table below shows which release corresponds to each branch, and what date th
[2615]: https://github.com/Gallopsled/pwntools/pull/2615
[2639]: https://github.com/Gallopsled/pwntools/pull/2639
[2630]: https://github.com/Gallopsled/pwntools/pull/2630
[2647]: https://github.com/Gallopsled/pwntools/pull/2647

## 4.15.0 (`stable`)

Expand Down
80 changes: 79 additions & 1 deletion pwnlib/util/packing.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
r"""
Module for packing and unpacking integers.

Expand Down Expand Up @@ -1220,5 +1219,84 @@ def _decode(b):
return b
return b.decode(context.encoding)

def overlap(*structs: bytes | tuple[bytes, int]) -> bytes:
r"""overlap(*structs: bytes | tuple[bytes, int]) -> bytes

Merge multiple byte sequences with possible positional offsets into a
single overlapped bytes object. From lowest byte index, scan through
every bytes object, if only one byte is non-zero, then the output will
append that byte. If multiple non-zero bytes are found at the same index,
an error will be thrown as these bytes objects can not be overlapped.

If holes present as the bytes object has an ``offset`` or its length is
not enough to align to the output bytes object length, these holes are
set to ``\x00`` implicitly.

See the examples below for how this function works.

Arguments:
*structs(bytes | tuple[bytes, int]): ``bytes`` objects like ``b'123'``
or adding an optional offset like ``(b'123', 3)``. The ``offset``
can be positive or negative or 0. ``bytes`` objects has an
implicit offset 0.

Returns:
A bytes object merged from input bytes objects.

Raises:
BufferError: If multiple non-zero values appears at the same index

Examples:

>>> overlap(b'\x00123', b'a\x00\x00\x00b')
b'a123b'
>>> overlap(b'11\x00\x0022', (b'33\x00\x0044', 2))
b'11332244'
>>> overlap((b'123', -8), (b'456', -4))
b'123\x00456'
>>> overlap((b'xx', 2), (b'yy', 0))
b'yyxx'
>>> overlap((b'123', 1), b'456')
Traceback (most recent call last):
...
BufferError: Conflicting value 0x31 and 0x35 at index 1 when overlapping
"""
if len(structs) == 0:
return b''
if len(structs) == 1:
if isinstance(structs[0], tuple):
return _need_bytes(structs[0][0])
return _need_bytes(structs[0]) # structs[0] is bytes

segments: list[tuple[bytes, int]] = [
# ensure types
(_need_bytes(elem[0]), int(elem[1]))
if isinstance(elem, tuple) else
(_need_bytes(elem), 0)
for elem in structs
]

# find lowest offset and subtract it to align offsets
compensation = min(elem[1] for elem in segments)
segments = [(elem[0], elem[1] - compensation) for elem in segments]

length = max(len(elem[0]) + elem[1] for elem in segments)
output = bytearray(length) # bytearray initialize all bytes as 0

# overlap segments and fail if multiple non-zero value at the same index
for segment, offset in segments:
for i, b in enumerate(segment):
if b != 0:
if output[offset + i] == 0:
output[offset + i] = b
else:
raise BufferError(
f'Conflicting value {output[offset + i]:#x} '
f'and {b:#x} at index {compensation + offset + i} '
f'when overlapping'
)

return bytes(output)

del op, size, end, sign
del name, routine, mod
Loading