Smoke Signals: The Original P2P Network

The Casual Steganographer
11 min readOct 15, 2023

--

AA s you already know from frightening stories on the internet, there’s innumerable futuristic ways of hiding a messages and secret symbols in our high-tech world. We fully intend on covering these techniques eventually¹, but we’re starting way back. Like way, way back. To scratch that digital itch² though, we’ll accompany each of our historical-leaning articles with a code snippet that puts a digital twist on our topic, usually placed at the bottom if you feel like skipping all the boring “learning” bits in-between. Our github can be found here, it is kept sterilized to avoid leaving too many crumbs to the authors, so don’t look for best repo practices there.

An aside, you may have noticed there are a few little Steg’s and few little eggs near the drop cap for this article. The eggs are a reminder to look for the secret key to unlock the hidden Monero wallet in this article. Details explained in our initial article here. Remember the Monero mnemonic key, like the one featured in the picture below.

Example Monero wallet mnemonic. Image by Author.

Stone Age to Static Age: The Communication Eras

We take our communication for granted these days.

That’s no real revelation to anyone. I can DM a stranger across the world from me in an instant, with an equally prompt reply. This is how it should be, and if we spend any longer than a second waiting for our tweet to post (our post to X? Is that what it is now?), we’re furious. This cross-hemisphere handshake is achieved by an astounding interplay of human engineering triumphs behind the scenes, all initiated by a simple button press. As an exercise in building patience and perspective, for comparison’s sake, we’ll walk through the history of near-instantaneous communication up to this point in human history and try to capture some perspective of how far we’ve come.

THE BORING ERA — Prehistory ~ 1 m.y.a.

Dominant Energy Source: Biological

Image by Author. [DALL·E]

This era is pre-homo sapiens, with no real recognizable sense of society. It’s understandable that we didn’t make a lot of headway on the communication front during this period. We were mainly focused on getting that promotion from prey to predator on the food chain.

Touch ~0m — Not a lot of range on this one, not appreciated for communicating with strangers either.

Smell ~1000m — Not thought of often for communicating but think of what kind of meaning forms in your head when you catch a whiff of rain, or smoke from a wildfire. We’ll count it.

Sight ~100m — This may seem a short distance, but we’ll say to gain any meaningful information you need to be able to visually resolve your caveperson buddy’s gestures. Your experience may vary.

Sound ~200500m depending on your pipes. Beyond that distortion can occur, making speech unintelligible.

THE TOOL ERA — (40,000BCE~1830CE)

Dominant Energy Source: Chemical (burning wood, oil)

We’re gonna need a bigger barbican. Image by Author. [DALL·E]

During this era, we see civilization starting to incorporate manual means of amplifying our transmission distance for our “near-instantaneous” communication channels. We extend this period to the end of the Industrial Revolution, aiming to capture a roughly “pre-electric” era.

Fire 30–50km — A very dimension-rich (we explain our use of this term below) tool in the way of communication. Size, color, presence of smoke. The star of today’s article. Best is, we can resolve the information from the fire, or smoke (depending on weather conditions) at far distances.

Flags ~20km — Think heraldry and coats of arms hung high on castle walls, waved between ships. A staple in society today, it’s been said that what a collective of people put on their flag is what they want to say to the world. Note: sometimes that message isn’t immediately clear without more cultural context, looking at you Sicily.

Horns 2–3 km —Toot.

The Electrical Era — (1830~1990s)

Dominant Energy Source: Chemical/Atomic (petroleum, solar, uranium)

Image by Author. [DALL·E]

This era begins just after the end of the Industrial Revolution. A period of accelerated discovery, relative to its preceding eras. We see the first “over-the-horizon” communication, both wired and wireless varieties. We’ve omitted the telephone, telegram, and wired communication for the sake of focusing on “freer” methods of communication in this article.

Amplifiers 1–2km — A little bit louder now.

Incandescent Light 30km — A little bit softer now. With electric lighting we can exert even more control over light as a form of communication.

Radio 100km³First “over-the-horizon” man-made communication method unlocked!⁴

The Speculative Era — (Now)

Image by Author. [DALL·E]

If we can construe most modern methods of communication as iterative improvements of previous methods; this leaves quantum entanglement as the arguably last novel phenomena-based method of near-instant communication left. Questions remain to its efficacy as a communication method however, and the waters are muddy.

But let's get back to our first big communication breakthrough from harnessing nature, the fire.

Igniting Modern Communication

Precisely dating the origins of when humanity first tried communicating via fire is near impossible, so we’re abandoning that effort and sharing two fun bits of history instead.

Let’s take a look at two ingenious uses of fire from history and find some commonalities with modern communication networks.

Roman Fire Feint

A good example of clever communication from history the Battle of Ager Falernus between General Fabius (of Fabian strategy fame) and Hannibal. As anyone who’s ever received a back-handed compliment can tell you, communication isn’t always well intentioned.

In this battle, Hannibal, camped far from the Roman army, fooled General Fabius by lighting wood tied to the horns of his oxen and setting them loose into the surrounding forest, creating the illusion of a much larger Carthaginian force on the move.

Hot Beef. Image by Author. [DALL·E]

Everybody Expects the Spanish Armada

In 1588, when the Spanish Armada threatened England, a series of beacons were set up across the south of the country. When the Armada was sighted, the beacons were lit, and the warning rapidly spread across the land.

The multiple beacons allowed messages to spread as quickly as sight, and kindling, allowed.

Day seven of the battle with the Armada, 7 August 1588 — Hendrick Cornelisz Vroom. [wikimedia commons]

Using our modern lens, we can say Hannibal had ingeniously relied on his own soldiers recognizing his feint with fiery oxen for what it was, while Fabius’s men were baffled, though they were seeing the same message, its meaning was only known to authorized parties.

In the case of the English warning beacons, we see a fantastic example of redundant, distributed communication at play. In the case of a failed beacon, multiple backups exist to continue relaying the warning, with no single point of failure in the network.

Can you see where this heavily leading section is headed? That’s right! We can see the same, valued attributes of modern, highly secure P2P communication dating back to antiquity. This conclusion isn’t shocking, but it is a fun one. We’ve always desired private, reliable ways to talk to each other. These days are no different, we’ve just found fancier ways to throw our voice around.

…we’ve just found fancier ways to throw our voice around.

WORD of the Article

A quick word on dimensionality as a concept

An important mindset to get into, for all you casual steganographers out there, is to think strategically about dimensionality. Most people think about space when they think dimensions, everyone has probably read or heard the story of Flatland by now, or watched Futurama’s take on it. We use it, this 2D plane world, as a metaphor because it’s much easier to reduce dimensionality for comprehension, then expand once more. From here we imagine some higher dimension observing you the same as you would a sheet of paper. These new dimensions are orthogonal to all others, another concept that’s easier to grasp in lower dimensions.

In 2D space, two perpendicular lines, intersecting at a right angle, are orthogonal to one another. Each additional spatial dimension permits movement along another plane. Steg is going to give us a better way to think about it, in terms of dance moves.

We would‘ve gone higher, but DALL·E can’t do 4D dance moves, yet. Image by Author. [DALL·E]

Each new dimension opens a whole new realm of boogie for Steg that wasn’t accessible before. Similarly, when we use the word in the manner we do and talk about the dimension of a medium for transmitting information, we’re really talking about the availability of semi-independent methods we can hide our messages with. If you’re totally on-board and want to dig deeper, check out a concept called channel capacity. But don’t worry if it’s still not 100% clear just yet, keep tuning in and checking out the articles. Maybe even hunt for one of the $5 Monero wallets that Steg has hidden away somewhere!

Ending Quotes and Starting Snippets

Let’s end on a message from the Grandaddy of information theory himself, Claude Shannon, and write some code.

The fundamental problem of communication is that of reproducing at one point either exactly or approximately a message selected at another point. Frequently the messages have meaning; that is they refer to or are correlated according to some system with certain physical or conceptual entities.

This “correlation with certain physical or conceptual entities” is what will underly most of our bits of code we plan on sharing. These will normally be examples of communicating a message through an unexpected medium or channel so let’s conceptualize our “digital smoke signal” and give it a go.

Steg’s Snippets (#001 — SMOKESignalv1)

Note — With each of Steg’s Snippets we will be aiming to communicate information by encoding it in a, hopefully, novel (and probably silly) manner.

SMOKESignal, Available at your local github. Image by Author.

In this proof of concept, we’re creating an information channel through data packets, instead of smoke⁵. If you’re an observer I want to communicate with, but you’re unable to access my packet’s content, how might I get a message to you?

Assuming I know you’re watching our line of transmission, unable to read, but able to see the size of data being transmitted, we can encode a channel of communication on this medium. Rather than pass data within the packets themselves, we can map a key to the size of the packets we send, similar to varying the size of a puff of smoke from a fire.

Here’s a basic function to create a packet or “puff” of the variable “SMOKE”

import socket
import time

def create_packet(size_kb):
"""Create a packet of specified size in KB filled with the word 'SMOKE'."""
size_bytes = size_kb * 1024 # Convert KB to bytes
filler_data = "SMOKE" * (size_bytes // 5) # The size of "SMOKE" is 5 bytes
# If there's a remainder when dividing by 5, it means the filler data
# won't align with the desired packet size.
remainder = size_bytes % 5
if remainder:
filler_data += "SMOKE"[:remainder]
return filler_data.encode('utf-8')

Simple instructions to send our packet to our designated location.

def send_packet(host, port, size_kb):
"""Sends a packet to a specified host and port."""
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
packet = create_packet(size_kb)
sock.sendto(packet, (host, port))

Let’s make a little alphabet dictionary for our packet sizes. We’re using UDP so we’ll be limited to sending puffs up to 67 KB in size. An interesting exercise would be to expand this code to communicate through other dimensions besides packet size.⁶

letter_to_size = {
'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5,
'F': 6, 'G': 7, 'H': 8, 'I': 9, 'J': 10,
'K': 11, 'L': 12, 'M': 13, 'N': 14, 'O': 15,
'P': 16, 'Q': 17, 'R': 18, 'S': 19, 'T': 20,
'U': 21, 'V': 22, 'W': 23, 'X': 24, 'Y': 25, 'Z': 26,
' ': 27, # Space
'.': 28, # Period
}

Now we send our “message”.

def send_encoded_message(host, port, message, letter_to_size, repeats):
"""Sends an encoded message using packet sizes."""
for _ in range(repeats):
for letter in message.upper(): # Convert the message to uppercase for consistent mapping
if letter in letter_to_size:
size_kb = letter_to_size[letter]
print(f"Sending '{letter}' as a packet of {size_kb} KB")
send_packet(host, port, size_kb)
time.sleep(2) # Introducing a delay between sending packets

Look carefully above, remember, there is no worthwhile information inside the packet itself, if anyone listens in on your traffic or sniffs your packets, they just see a bunch of smoke.

message = "HELLO WORLD"
host = "192.168.0.196" # Localhost for testing
port = 12345
repeats = 2
send_encoded_message(host, port, message, letter_to_size, repeats)

Assuming the role of a curious stranger, we notice a data transfer on our monitored line, but, if we look with Wireshark, all we see is that we’re receiving some strange packets of different size, and they all seem to be filled with “SMOKE”. But our simple packet sniffing notebook will be able to read and uncover our underlying message.

Wireshark display for UDP packets coming to our port. The panel on the right shows the contents of our packets.

We set our UDP packet sniffer up on a port of our choosing to check for any packets sent to it. Using the same alphabet as our packet sender, we can infer a message based on the size of the packet alone.

import socket

# Reverse the dictionary for lookup by size
size_to_letter = {v: k for k, v in letter_to_size.items()}

def udp_sniffer(port, packet_count):
# Create a socket to listen for UDP packets on the specified port
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.bind(("0.0.0.0", port)) # Bind to all available interfaces
print(f"Listening for UDP packets on port {port}...")

decoded_message = ""
for _ in range(packet_count):
# Receive the packet data and address
data, addr = s.recvfrom(65535) # Maximum UDP packet size
src_ip, src_port = addr

# Determine the length of the packet
packet_length = len(data)

# Extract the significant digits based on the packet length
if 1000 <= packet_length < 10000: # i.e., between 1 KB and 9 KB
packet_size = int(str(packet_length)[0])
elif 10000 <= packet_length < 100000: # i.e., between 10 KB and 99 KB
packet_size = int(str(packet_length)[:2])
else:
packet_size = None # This sets a default if, for some reason, packet size is outside expected bounds


decoded_character = size_to_letter.get(packet_size, '?') # '?' denotes unrecognized characters
decoded_message += decoded_character

# Display basic packet info and decoded character
print(f"Packet received from {src_ip}:{src_port} - Length: {len(data)} bytes - Decoded as '{decoded_character}'")

print(f"\nDecoded Message: {decoded_message}")

# Set the desired port and packet count
PORT = 12345
PACKET_COUNT = 15

# Start the packet sniffer
udp_sniffer(PORT, PACKET_COUNT)

Let’s see it in action.

Riveting stuff. Image by Author.

There we go, smoke signal sent, and when we know where to look for the message, smoke signal received.

Though simple in concept, the authors could find no record in literature of packet size being modulated to communicate information. This doesn’t mean it hasn’t been done before, but hopefully this serves to broadcast the technique and inspires you to find even more interesting ways to send your own smoke signals.

Articles coming soon:

  • I ❤ You’s on the blockchain
  • ResNet Shibboleth — Hidden Messages in Classifiers
  • Vehicular Steganography

[1] Probably.

[2] Sorry.

[3] 1000km if you bounce your signal.

[4] If we don’t count the Halifax Explosion.

[6] Depending on the age of your router, you may be priveleged to use data and smoke.

[7] Interesting enough that we’ll cover it in an upcoming article. Try to beat us to the punch.

--

--

The Casual Steganographer
The Casual Steganographer

Written by The Casual Steganographer

Former academics who like puzzles and making projects to procrastinate on other projects. CasualSteganographer@proton.me github.com/TheCasualSteganographer

No responses yet