Skip to content

PYC

stegobox.codec.PYC

Bases: BaseCodec

This module implements steganography of str in Python bytecode.

with code shamelessly taken from AngelKitty/stegosaurus

Source code in stegobox/codec/pyc.py
class PYC(BaseCodec):
    """This module implements steganography of str in Python bytecode.

    with code shamelessly taken from
    [AngelKitty/stegosaurus](https://github.com/AngelKitty/stegosaurus)
    """

    def __init__(self) -> None:
        super().__init__()

    def encode(
        self, carrier: tuple[bytes, types.CodeType], payload: str
    ) -> tuple[bytes, types.CodeType]:
        """Encoder requires the carrier to be Python bytecode
            and the payload to be a string.

        Args:
            carrier: Python bytecode. Read with `stegobox.io.pycfile.read()`.
            payload: Payload (secret message) to be encoded.

        Returns:
            Python bytecode header
            Python bytecode code
        """
        header, code = carrier
        mutable_bytecode = MutableBytecode(code)
        mutable_bytecodestack = self._create_mutablebytecode_stack(mutable_bytecode)
        max_payloadsize = self._max_supportedpayloadsize(mutable_bytecodestack)
        payload_len = len(payload)
        if payload_len > max_payloadsize:
            raise ValueError("payload is too long")

        self._embedpayload(mutable_bytecodestack, payload)
        code = self._tocodetype(mutable_bytecode)

        return (header, code)

    def decode(self, carrier: tuple[bytes, types.CodeType]) -> str:
        """Decode the secret payload from the carrier Python bytecode

        Args:
            carrier: Python bytecode. Read with `stegobox.io.pycfile.read()`.

        Returns:
            str: The decoded payload (secret message).
        """
        _, code = carrier
        mutable_bytecode = MutableBytecode(code)
        mutable_bytecodestack = self._create_mutablebytecode_stack(mutable_bytecode)
        depayload = self._extract_payload(mutable_bytecodestack)
        return depayload

    def _embedpayload(self, mutable_bytecodestack, payload):
        payload_bytes = bytearray(payload, "utf8")
        payload_index = 0
        payload_len = len(payload_bytes)

        for bytes, byte_index in self._bytesavailableforpayload(mutable_bytecodestack):
            if payload_index < payload_len:
                bytes[byte_index] = payload_bytes[payload_index]
                payload_index += 1
            else:
                bytes[byte_index] = 0

        print("Payload embedded in carrier")

    def _extract_payload(self, mutable_bytecodestack):
        payload_bytes = bytearray()

        for bytes, byte_index in self._bytesavailableforpayload(mutable_bytecodestack):
            byte = bytes[byte_index]
            if byte == 0:
                break
            payload_bytes.append(byte)

        payload = str(payload_bytes, "utf8")

        return payload

    def _bytesavailableforpayload(self, mutable_bytecodestack):
        for mutable_bytecode in reversed(mutable_bytecodestack):
            bytes = mutable_bytecode.bytes
            consecutive_printablebytes = 0
            explode_after = math.inf
            for i in range(0, len(bytes)):
                if chr(bytes[i]) in string.printable:
                    consecutive_printablebytes += 1
                else:
                    consecutive_printablebytes = 0

                if i % 2 == 0 and bytes[i] < opcode.HAVE_ARGUMENT:
                    if consecutive_printablebytes >= explode_after:
                        consecutive_printablebytes = 0
                        continue
                    yield (bytes, i + 1)

    def _create_mutablebytecode_stack(self, mutable_bytecode):
        def _stack(parent, stack):
            stack.append(parent)

            for child in [
                const for const in parent.consts if isinstance(const, MutableBytecode)
            ]:
                _stack(child, stack)

            return stack

        return _stack(mutable_bytecode, [])

    def _max_supportedpayloadsize(self, mutable_bytecodestack):
        max_payloadsize = 0

        for bytes, i in self._bytesavailableforpayload(mutable_bytecodestack):
            max_payloadsize += 1

        return max_payloadsize

    def _tocodetype(self, mutable_bytecode):
        return types.CodeType(
            mutable_bytecode.originalCode.co_argcount,
            mutable_bytecode.originalCode.co_posonlyargcount,
            mutable_bytecode.originalCode.co_kwonlyargcount,
            mutable_bytecode.originalCode.co_nlocals,
            mutable_bytecode.originalCode.co_stacksize,
            mutable_bytecode.originalCode.co_flags,
            bytes(mutable_bytecode.bytes),
            tuple(
                [
                    self._tocodetype(const)
                    if isinstance(const, MutableBytecode)
                    else const
                    for const in mutable_bytecode.consts
                ]
            ),
            mutable_bytecode.originalCode.co_names,
            mutable_bytecode.originalCode.co_varnames,
            mutable_bytecode.originalCode.co_filename,
            mutable_bytecode.originalCode.co_name,
            mutable_bytecode.originalCode.co_firstlineno,
            mutable_bytecode.originalCode.co_linetable,
            mutable_bytecode.originalCode.co_freevars,
            mutable_bytecode.originalCode.co_cellvars,
        )

encode(carrier, payload)

Encoder requires the carrier to be Python bytecode and the payload to be a string.

Parameters:

Name Type Description Default
carrier tuple[bytes, CodeType]

Python bytecode. Read with stegobox.io.pycfile.read().

required
payload str

Payload (secret message) to be encoded.

required

Returns:

Type Description
bytes

Python bytecode header

CodeType

Python bytecode code

Source code in stegobox/codec/pyc.py
def encode(
    self, carrier: tuple[bytes, types.CodeType], payload: str
) -> tuple[bytes, types.CodeType]:
    """Encoder requires the carrier to be Python bytecode
        and the payload to be a string.

    Args:
        carrier: Python bytecode. Read with `stegobox.io.pycfile.read()`.
        payload: Payload (secret message) to be encoded.

    Returns:
        Python bytecode header
        Python bytecode code
    """
    header, code = carrier
    mutable_bytecode = MutableBytecode(code)
    mutable_bytecodestack = self._create_mutablebytecode_stack(mutable_bytecode)
    max_payloadsize = self._max_supportedpayloadsize(mutable_bytecodestack)
    payload_len = len(payload)
    if payload_len > max_payloadsize:
        raise ValueError("payload is too long")

    self._embedpayload(mutable_bytecodestack, payload)
    code = self._tocodetype(mutable_bytecode)

    return (header, code)

decode(carrier)

Decode the secret payload from the carrier Python bytecode

Parameters:

Name Type Description Default
carrier tuple[bytes, CodeType]

Python bytecode. Read with stegobox.io.pycfile.read().

required

Returns:

Name Type Description
str str

The decoded payload (secret message).

Source code in stegobox/codec/pyc.py
def decode(self, carrier: tuple[bytes, types.CodeType]) -> str:
    """Decode the secret payload from the carrier Python bytecode

    Args:
        carrier: Python bytecode. Read with `stegobox.io.pycfile.read()`.

    Returns:
        str: The decoded payload (secret message).
    """
    _, code = carrier
    mutable_bytecode = MutableBytecode(code)
    mutable_bytecodestack = self._create_mutablebytecode_stack(mutable_bytecode)
    depayload = self._extract_payload(mutable_bytecodestack)
    return depayload