We used to think of Telegram as a reliable and secure transmission medium for messages of any sort. But under the hood it has a rather common combination of a- and symmetric encryptions. Where’s fun in that? And why would anyone trust their private messages to a third-party anyway?
TL;DR — inventing a private covert channel through users blacklisting each other.
There are many workarounds to transmit data between two users avoiding direct contact. You can use a middlemen, crypto- and steganography methods, broadcasting relay networks, and other extensions of existing protocols. But sometimes it’s useful being able to establish a secure contact using only officially documented features. Or as one should say, set up a covert channel.
One can find a simple example of a good covert channel in a Soviet spy movie “Seventeen Moments of Spring” (this one is, like, really good, give it a chance). In it, a flower in a window of the safe house was used as a signal of the spy failing his mission. The flower by itself does not mean anything: it can be there and could be not, such symbiosis is a common thing and only telling us about the owner’s love for flowers. Only the predetermined interpretation distinguishes the information received by a spy from the one received by a random passerby.
Flower-based channels in Telegram
To organize your own covert channel by the same principle you’ll need only two things: a window and a flower. The window represents an object you can change the state of seen by others and the flower — possible states and a way of changing them.
So what Alice could change in Telegram that Bob could look at? Many things, actually: avatars, usernames, last visited time and more. But usually these things are available to everyone at the same time, limiting dialog privacy — if one possesses the transition method, anything Alice sends is not private anymore. Not so surprisingly, it is possible to get around this limitation without any kind of encryption involved.
I'm blocking you, haha
Every user has his own blacklist, and if the reader was annoying enough, he could have noticed after being blocked that his not-already-a-friend ‘last visited’ status has been changed to ‘last seen a long time ago’. The truth is, he could have been online just a few seconds ago or even be right now, but Telegram API will not send this information to your app anymore. That way, it is protecting other user’s privacy from unwanted eyes. In exchange though, they can see if blacklisted or not.
So what are seeing a flower and being blacklisted have in common? Both could be checked at a given moment, allowing to receive one bit of information depending on if you are blocked or not. Another advantage is a fact that Telegram probably does not store logs of users blocking each other (at most for short periods in journaling purposes).
A possibility to send and receive bits is fun and all, but we still need to describe its exploitation mechanism. Telegram refuses to notice you while blocked, so every ‘receive bit’ action should be initialized by the recipient (let’s call him Bob) and do not depend on the sender (she would be Alice), i. e. be independent. It also means that Alice and Bob should do requests at the same frequency.
Bit exchange algorithm on every clock looks like this:
- A checks sending a bit and if has different from the previous value changing it depending on a value:
- A -> T: block B if bit is 1;
- A -> T: unblock B if bit is 0.
- B receives a bit:
- B -> T: resolve A;
- T -> B: available to B information about A;
- B: checks if the received information has a status it:
- B: if it is -> he is not blocked and the bit is 0
- B: if it is not -> he is blocked and the bit is 1
Most modern PCs have good internal frequency generators (a system clock, for example), so we can synchronize our clocks using them while not touching the channel to transmit anything except for the message bits. Only worth noticing that Telegram API requests, both (un)blocking and user status resolving, are network calls and do not tend to work quickly, especially if you are using proxies or a VPN. This produces a limitation: clock length should be longer, than the average response time (since we need to fit one into another) and that’s why our data transmission speed is limited.
Texts in natural languages have pretty high redundancy and messages received with errors will still be mostly readable by a human. Since Telegram is a messenger (ignoring some crazy stuff) after all, we can neglect error correction limiting possible transmitting data to simple text messages.
The channel has extremely low bandwidth, that’s why we need to use the most effective message encoding available. Luckily, the name of the messenger reminds us about times such problem was a common one.
That’s why while living in the 21st century, we will encode our texts with one of the most efficient methods available to telegraphers a hundred years ago — the the Baudot code. More precisely, its final variation ITA-2 created by Donald Murray to perform fewer API calls for the most frequent symbols.
The only thing left to successfully transmit a message is to find session boundaries, so the recipient could find a sent one among the continuous bit stream. Before the transmission has started Bob is either blocked or not, and this state will not change by itself anytime soon. That’s why Alice can indicate session start by swapping it to the opposite for a single clock. At the successful end of the session, she will unblock and leave him be. He, on the other side, will continue to receive zero bits until decides they are not a part of the message — the Baudot code has no
Drawbacks of this method are: a practical impossibility to connect (you can, but it will likely require manual error correction due to the bit shift) to ongoing translation and a need to separate null symbols received with errors from ones been sent. But there all problems of the implementation.
After several hours spent trying to use an official library, I got tired and wrote everything with Python using more human-friendly Telethon. It even has a synchronous-style API, which is rare today for reasons unknown. Message encoding with ITA-2 I’ve written by myself since there were nothing useful on the Internet.
Clock synchronization made using system clock (and yes, it sleep()s! in between) since it’s precise enough, considering the time required on every network API call is more than a tenth of a second in most cases. User can set transmission speed as he wills, but I recommend following ‘no more than a request per second’ rule if you don’t want to both see errors on the other side and find yourself banned by the flood prevention system. Telegram turned out to be very picky about his API usage, freezing access for a day for even a few simple (successful!) authorization attempts in a row and just random blocking during the transmission for reasons unknown.
If the user decided to use such a weird channel to exchange messages, he really should not care about any graphical user interface features. And not all systems have it anyway. That’s why I wrote my application in a form of a terminal tool. It allows to both send and receive messages (only one operation per launch tho). Of course, you can run as many copies of the program as you want to and use multiple channels simultaneously both directions.
Using the stuff
You can read more about using this thing as both a command-line utility and a python3 library through the API at GitHub (repository linked at the end of the post). The only problem is to acquire your own API credentials (simple manual is helpful enough) since Telegram does not allow to disclose mine and set according values in your local copy of a script. Everything passed through the command line arguments except for the authorization part (which by default done through the stdio) and looks like this:
For Alice: For Bob: Enter your phone number: XXX | Enter your phone number: XXX Enter auth code: YYY | Enter auth code: YYY Started message transmission... | Listening for the message... ---++ ('O', '9') | ---++ ('O', '9') --+-+ ('H', '#') | --+-+ ('H', '#') +++++ (1, 1) | +++++ (1, 1) --++- ('N', ',') | --++- ('N', ',') --+-- (' ', ' ') | --+-- (' ', ' ') ++-++ (0, 0) | ++-++ (0, 0) --+-+ ('H', '#') | --+-+ ('H', '#') -++-- ('I', '8') | -++-- ('I', '8') --+-- (' ', ' ') | --+-- (' ', ' ') --+++ ('M', '.') | --+++ ('M', '.') ++--- ('A', '-') | ++--- ('A', '-') -+-+- ('R', "'") | -+-+- ('R', "'") ++++- ('K', '(') | ++++- ('K', '(') +++++ (1, 1) | +++++ (1, 1) +-++- ('F', '!') | +-++- ('F', '!') --+++ ('M', '.') | --+++ ('M', '.') --+++ ('M', '.') | --+++ ('M', '.') Done, exiting... | ----- ('', '') | ----- ('', '') | Automatically decoded: OH, HI MARK!..
Outside of the Telegram
Worth noticing that it is possible to implement such a channel over any messenger and/or social network in which one can detect if he got blocked by others or not. You can use my code to do so and do not reinvent the wheel. Low python’s performance will not be a limiting factor due to the low transmission speed and an API calls response time.
P.S. Special thanks to my passion's unusual love for blocking me