PyExfil Module Architecture

June 23, 2026 · View on GitHub

Class Hierarchy

PyExfilModule          (abstract root — pyexfil/includes/base.py)
├── NetworkModule      socket/protocol exfiltration (DNS, ICMP, HTTP, FTP…)
├── CommModule         bidirectional C2 over network (ARP, NTP body, MDNS…)
├── PhysicalModule     one-way physical channel (audio tones, QR codes, ultrasonic…)
└── StegaModule        file-in / file-out steganography (PNG pixels, braille, DataMatrix…)

Every concrete module inherits from one of the four pillars, which in turn inherits from PyExfilModule.


Public API

All modules expose the same surface regardless of pillar:

MethodDescription
send(data, **kwargs)Transmit data through the channel
broadcast(data, **kwargs)One-to-many send (falls back to send if not overridden)
listen(callback, blocking, **kwargs)Start the receive side; blocking=False returns a daemon thread
receive(**kwargs)Synchronous single-shot receive
stop()Signal the listener loop to exit gracefully

send / broadcast return bool. listen returns None (blocking) or a threading.Thread (non-blocking). Exceptions inside send / broadcast are caught and logged — the method returns False instead of raising.


Pillar Details

NetworkModule

For socket-level exfiltration over standard network protocols.

Constructor parameters (in addition to base):

ParameterTypeDescription
hoststrTarget host (required)
portintTarget port (required)
enc_keystrEncryption key
packet_delayfloatSleep between packets (default 0.05s)
max_packet_sizeintMax bytes per packet (default 512)
verboseboolEnable debug logging

Must implement:

  • _send_impl(data, **kwargs) -> bool
  • _listen_impl(callback, **kwargs) -> None

Example:

from pyexfil.includes.base import NetworkModule

class MyExfil(NetworkModule):
    MODULE_NAME = "MyExfil"
    PROTOCOL    = "udp"

    def _send_impl(self, data, **kwargs):
        # open socket, send data
        return True

    def _listen_impl(self, callback, **kwargs):
        # bind socket, loop while not self._stop_event.is_set()
        # call callback(payload_bytes, {"src": addr})
        pass

CommModule

For bidirectional C2 channels where the same module both sends commands and receives data.

Constructor parameters:

ParameterTypeDescription
enc_keystrEncryption key
on_messageCallableApplication-level callback fired on receive
verboseboolEnable debug logging

Must implement:

  • _send_impl(data, **kwargs) -> bool
  • _broadcast_impl(data, **kwargs) -> bool
  • _listen_impl(callback, **kwargs) -> None

Example:

from pyexfil.includes.base import CommModule

class MyComm(CommModule):
    MODULE_NAME = "MyComm"
    PROTOCOL    = "arp"

    def _send_impl(self, data, **kwargs):
        # unicast to specific peer
        return True

    def _broadcast_impl(self, data, **kwargs):
        # layer-2 broadcast
        return True

    def _listen_impl(self, callback, **kwargs):
        # sniff/listen loop; call callback(data, meta) on each message
        pass

PhysicalModule

For covert channels that use physical media: audio, light, RF, visual codes.

Constructor parameters:

ParameterTypeDescription
verboseboolEnable debug logging

Physical modules are typically send-only. listen() and receive() raise NotImplementedError by default. Override _receive_impl if the module can also read back (e.g. a microphone listening for ultrasonic tones).

Must implement:

  • _send_impl(data, **kwargs) -> bool

Optionally override:

  • _receive_impl(**kwargs) — if the channel can be read back

Example:

from pyexfil.includes.base import PhysicalModule

class MyPhysical(PhysicalModule):
    MODULE_NAME = "MyPhysical"
    PROTOCOL    = "audio-tones"

    def _send_impl(self, data, **kwargs):
        # encode data as audio tones and play
        return True

StegaModule

For file-based steganography where data is hidden inside a carrier file.

The vocabulary is different from the other pillars:

StegaModule methodWhat it does
encode(carrier, payload, output)Hides payload inside carrier, writes result to output
decode(carrier, stego, output)Extracts hidden data from stego, writes to output
send([carrier, payload, output])Alias for encode (uniform API compatibility)
receive(carrier=, stego=, output=)Alias for decode

Constructor parameters:

ParameterTypeDescription
verboseboolEnable debug logging

Must implement:

  • _encode_impl(carrier, payload, output, **kwargs) -> bool
  • _decode_impl(carrier, stego, output, **kwargs) -> bool

Example:

from pyexfil.includes.base import StegaModule

class MyStega(StegaModule):
    MODULE_NAME = "MyStega"
    PROTOCOL    = "png-lsb"

    def _encode_impl(self, carrier, payload, output, **kwargs):
        # open carrier image, embed payload in LSBs, save to output
        return True

    def _decode_impl(self, carrier, stego, output, **kwargs):
        # open stego image, extract LSBs, write to output
        return True

Threading Model

listen(blocking=False) spawns a daemon thread named <MODULE_NAME>-listener. The thread runs _listen_impl and checks self._stop_event to know when to exit.

Inside _listen_impl, the canonical pattern is:

def _listen_impl(self, callback, **kwargs):
    sock = socket.socket(...)
    sock.settimeout(1.0)          # short timeout so stop_event is checked
    while not self._stop_event.is_set():
        try:
            data, addr = sock.recvfrom(4096)
        except socket.timeout:
            continue              # loop back and re-check stop_event
        callback(data, {"src": addr})

Call module.stop() to set the event and join the thread (5 s timeout).


Class Attributes

Set these on every concrete class:

class MyModule(NetworkModule):
    MODULE_NAME = "MyModule"    # shown in logs and repr()
    PROTOCOL    = "dns/udp"    # transport label (free-form string)

MODULE_TYPE is inherited from the pillar and should not be overridden.


Imports

# One import covers the whole hierarchy
from pyexfil.includes.base import (
    PyExfilModule,
    NetworkModule,
    CommModule,
    PhysicalModule,
    StegaModule,
)

# Crypto / encoding helpers
from pyexfil.includes.encryption_wrappers import AESEncryptOFB, AESDecryptOFB, RC4
from pyexfil.includes.prepare import PrepFile, PrepString, DecodePacket, RebuildFile

Existing Modules

ModuleClassPillarProtocol
network/DNSDNSExfilNetworkModuledns/udp
network/ICMPICMPExfilNetworkModuleicmp
network/HTTPSHTTPSExfil / HTTPSExfiltrationServerNetworkModulehttps/tcp
network/HTTP_CookiesHTTPCookieExfilNetworkModulehttp/tcp
network/FTPFTPExfiltratorNetworkModuleftp/tcp
network/BGPBGPExfilNetworkModulebgp/tcp
network/NTPNTPExfilNetworkModulentp/udp
network/HTTPRespHTTPRespExfilNetworkModulehttp/tcp
network/QUICQUICClient / QUICServerNetworkModulequic/udp
network/POP3POP3Exfil / POP3ServerNetworkModulepop3/tcp
network/SlackSlackExfiltrator / SlackListenerNetworkModuleslack-api
network/SpoofIPSpoofIPExfil / SpoofIPServerNetworkModuleip/raw
Comm/ARPBroadcastBrokerCommModulearp/l2
Comm/NTP_BodyNTPComm (client) / Broker (server)CommModulentp-body/udp
Comm/DNSoTLSDNSoTLSComm / DNSoTLSServerCommModuledns-over-tls
Comm/DB_LSPDB_LSPCommModuledb-lsp
Comm/jetdirectBrokerCommModulejetdirect/tcp
physical/audioAudioExfilPhysicalModuleaudio-tones
physical/qrQRExfil / QRDecoderPhysicalModuleqr-visual
physical/wifiPayloadWiFiPayloadExfilPhysicalModulewifi-beacon
Stega/binoffsetBinOffsetStegaStegaModulepng-pixel-offset
Stega/video_dictVideoDictStegaStegaModulevideo-dict