Ever been stuck with an embedded board that looks like a spaceship but behaves like a potato?
No drivers, no network, no SSH, no hope. Just you and a lonely serial cable whispering bits of despair.
That’s the moment you wish you could throw a file at it... maybe a fresh build of busybox that finally has that utility you desperately need but isn’t available on the current system.
Enter: the send_console.
A gloriously simple hack to push a file over the same serial line you’re using to yell at your misbehaving device... because sometimes, network connectivity simply isn’t an option.
Let’s dive into what it is, why it matters, and why you’ll love it (or at least curse it slightly less than the alternative).
The Art of Smuggling Files Over Serial (Or: “How to Befriend Your Terminal”)
Let’s start with some very real pain points:
- You’re enabling a brand-new platform. It just finished booting for the first time, half the drivers are still missing, and network connectivity is but a distant dream.
- You’re working with a device controlled by a sidekick board: access through the back door, with device network not supposed to be accessed from outside.
- You’re hacking an embedded device. You finally found the serial pins hidden on the board, connected to them, but you have no tool to upload a file on it.
In all these cases, you really need to get a file onto that machine to move forward, but there’s no standard way to do it.
In theory, serial lines were made for this.... In practice? Not anymore.
In the good old days, we had XMODEM and ZMODEM.
Cute protocols that handled file transfers over serial lines... Today?
You can’t bet they’re installed. Often, you’re lucky if you even have a working cat
.
This brings us to the real star of the show: how terminals actually work.
Terminal Magic: Why You Can’t Just “Send the Bytes, Bro”
Imagine your terminal is a grumpy customs officer. You hand it a packet (a byte), but before it lets it through, it:
- Checks for forbidden characters,
- Buffers a bunch of data,
- Sometimes “fixes” what it thinks you meant to send.
Terminals are not dumb pipes: they enforce line discipline, apply buffering, and often modify the data you send.
Enter stty
, our magic wand.
It tells the terminal to behave properly: disable echo, set raw mode, turn off processing.
Why stty
?
- It’s incredibly portable.
- It’s available almost everywhere: full Linux systems, BusyBox minimal environments, you name it. Hint: it is mandatory in the List of POSIX commands.
Another companion is terminal buffering:
Serial interfaces typically buffer input and output for efficiency, but this can ruin a clean file transfer if not managed.
Here, stdbuf comes to the rescue, forcing applications like cat
to flush data properly as it’s processed.
send_console-ng: “Now With 50% More Reliability!”
Before send_console-ng
, there was… well, just send_console
.
A brave little utility with a lot of heart... and a lot of problems.
The first version tried to be smart:
It sent commands and checked their echoes to validate that everything arrived correctly.
Sounds reasonable, right?
Except…
In a real embedded system, kernel messages can appear at any moment.
Random printk noise: “driver XYZ initialized”, “thermal event detected”, “welcome to dmesg hell”, bursting onto your serial line like popcorn on a campfire.
This meant that even if your command was properly echoed, the echo might get interrupted mid-flight by a random kernel log.
In a heroic attempt to fight this chaos, I even crafted a function designed to validate echoes, despite random insertions:
The function was smart. It could piece together broken echoes.
It battled bravely.
But in the end, I realized I was a modern Don Quixote, fighting not windmills, but terminal text wrapping feature, dmesg notifcation, ansi escape codes, and all their friends.
send_console-ng takes a much saner approach:
Instead of dancing with echoes, it takes control of the terminal settings on the remote side.
It flips the terminal into raw mode with stty
, disables buffering shenanigans, and then simply blasts the file through cleanly.
On the host side, the command is beautifully simple:
-b
sets the baudrate,-f
specifies the file to send,-d
picks the serial device.
Behind the scenes, send_console-ng
:
- Compresses the file with gzip,
- Encodes it safely with base64,
- Manages terminal settings with stty,
- Forces unbuffered transmission with stdbuf.
- Handles the decoding on the remote side.
Disclaimer
send_console-ng is still immature. I'm not sure if it will ever mature, but it's certainly not at this point. For instance, when it reaches a terminal, it starts by testing the commands... However, it's currently incapable of determining if the terminal it's running on is the correct one: for instance, if it reaches a login prompt, or worse, a gdb terminal, it will most likely result in an error... The worst?!? I don't even want to think about it.
So, if I've written my regexes correctly, it might just throw an error, or maybe not...
Some Realistic Limits (And Why I’m Not Crying About It Yet)
Reality check!
This tool depends on the presence of:
stty
gzip
base64
cat
stdbuf
If any of these are missing on the host or target, the tool simply won’t work.
Can it be made work without them? Perhaps, here some ideas on how it could work in more hostile environments
For stty
: if it’s missing, the missing functionality boils down to just two syscalls needed.
A small, architecture-dependent assembly utility could easily fill that gap.
Here's an example that could work on x86_64 machines:
For base64
:
If base64
is missing, an alternative is possible:
Send the file as a sequence of:
However, this will significantly increase transfer time.
While base64 encoding uses about 12 bits per byte, raw hexadecimal uses 32 bits per byte... almost tripling the amount of data.
The upside?
It’s completely dependency-free.
For gzip
:
Compression is a huge time saver but not strictly necessary.
Skipping gzip would work... At the cost of slower and bigger transfers.
Conclusion
Next time you find yourself staring at a device that’s technically alive but practically unreachable, remember:
You don’t need miracles. You just need a bit of clever system abuse and send_console-ng
Because in embedded work, persistence always beats perfection.