The Incredibly Dumb Way to Hack Airplane Wi-Fi (But It's Free)
The plane rose to an altitude of three kilometers. I pulled out my laptop, hoping to use the Internet, and maybe do a little work if it got too boring.
Connected to the plane's Wi-Fi, I opened a browser. The online login page asked for my credit card details. I searched for the card, which I found inside my passport. As I searched, I noticed that the login page offered to sign in to my airline miles account for free, even though I hadn't paid for anything yet. I figured it was a hole in the firewall. I had a long way to go from London to San Francisco, so I decided to investigate.
I logged into my JetStreamers Diamond Altitude account, went to my profile page, and saw an edit button. It looked normal: drop shadow, rounded corners, nothing special. It allowed me to change my name, address, and so on.
But suddenly I realized that this was no ordinary button. It would fraudulently allow me full access to the Internet through my air miles account. It would be slow and incredibly stupid, but it would work.
Many colleagues asked me to review their PRs because I left comments like “two weeks late” or “interfering with the deployment of a critical update.” But my ideas are important too, so I put on my headphones and turned on some music to concentrate. I forgot to charge my headphones, so Limp Bizkit started playing through my laptop speakers. Luckily, none of the passengers objected, so we had a blast together.
Before I could access the entire internet through my airline miles account, I had to write some prototypes. At first I thought I would write them in Go, but then I realized that if I wrote them in Python, I could call the resulting tool PySkyWiFi
Of course, I chose the second option.
Prototype 1: Messenger
The basic idea was this: Let's say I logged into my airline miles account and changed my name. If you logged into my account, you'd be able to read my new name from the ground. I could update it again, and I'd be able to read the new value. If you kept doing this, the account name field could be used as a tunnel through the airplane Wi-Fi firewall into the real world.
This tunnel can support simple instant messaging protocol. I can change the name to “Привет, как дела
” The other person will be able to read my message and send a reply, changing the name back to “Хорошо, а у тебя
” I'll read this and we can continue this conversation. It doesn't look very functional, but it could be the first step towards full Internet access.
I had paid for internet on my old laptop. The data migration from that computer had not yet been completed, so I had to take it with me. I wrote to my wife and asked her to help me with my experiments, to which she responded warmly “ты о чём вообще, я занята
“.
So I had to get a new laptop, which still didn't have internet access. I created a test account for the air miles program and logged in on both computers. It turns out that you can actually chat with yourself by changing the name field in the UI.
But this is a bad UX, so I wrote a command line tool to automate it. The tool asks the user for a message, then logs into the air miles account on the website using my credentials. It then inserts the user's message into the test account's name field. Every few seconds, the tool polls the name field to see if the account name has changed again, which tells us that the other person has sent a message. Once the tool detects a new value, it prints it, asks the user for a new response, and so on.
With this tool, I can chat through my terminal with someone on the ground. I wouldn't have to pay for Wi-Fi, and neither party would care that the messages were sent through my SkyVenture Premium Gold Rewards account.
Still need to find someone willing to chat with me, but it's a good start!
Note: I don't want to send automated data through my airmiles account in the future, as that could cause problems. Nothing I do will hurt, but some companies really don't like that, so I made sure PySkyWiFi worked with my airmiles account by changing my name about a dozen times in quick succession. All of these were successful, which meant my account probably didn't have a rate limit on the requests I could send.
I then wrote the rest of the when, sending data through friendly services like GitHub Gist and local files on my computer, using the same principles I would use if I were sending it through an airline mileage account. If PySkyWiFi worked through GitHub, it would work through a Star Power UltimateBlastOff account. This had the added benefit of making iterations much faster and easier.
I'm going to keep talking about sending data through your airmiles account because that's our goal.
Prototype 2: News, Stock Quotes and Football Score
The tunnel created through the account will be useful for more than just instant messaging. For the next prototype, I wrote a program that can run on my computer at home or in the cloud. It should automatically send real-world information to me on a plane through the account. I can deploy it before my next flight and set it up to send me the latest stock quotes or football scores while I'm flying.
To do this, I wrote a daemon that runs on a computer on the ground connected to the Internet. The daemon constantly polls the name field in the account for structured messages that I send from the plane (e.g. STOCKPRICE: APPL
or SCORE: MANUNITED
). When the daemon sees a new request, it parses it, extracts the requested information using the appropriate API, and sends it back to me via the account. Everything worked perfectly.
Now you can use the first prototype to send instant messages through your account, and use the second prototype to track market data and football matches.
It's time to squeeze the entire Internet through your airline mileage account.
The most important: PySkyWiFi
For the rest of the flight, I wrote PySkyWiFi. PySkyWiFi is a greatly simplified version of the TCP/IP protocol that pushes entire HTTP requests through an account out to a computer connected to the Internet on the ground. A daemon running on that computer on the ground makes the HTTP requests, then pushes the completed HTTP responses through the account out to me on the plane.
This meant that on my next flight I would have full access to the Internet via my Air Miles account. If the network conditions on the plane were good, I could achieve speeds of hundreds of bytes per second.
Warning: Obviously none of this should be done in real life.
Below I will explain how it works (source).
How PySkyWiFi Works
PySkyWiFi consists of two components:
Heavenly Proxy — a proxy running on a laptop on a plane
Earth Demon — a daemon running on a ground-based computer with an Internet connection or in the cloud.
Here is a simplified diagram:
The preparation begins before you even leave the house. First, you need to start the ground demon. Then you call a taxi to the airport, get on a plane, and connect to the Wi-Fi network. You start the sky proxy on your laptop. The PySkyWiFi repeater is ready to use.
Next you need to use a tool like curl
to make an HTTP request to a skyproxy running on your laptop. You direct your request to the proxy (e.g. localhost:1234/
) and place it in a special HTTP header HTTPX-PySkyWiFi
The URL to request. For example:
curl localhost:1234 -H "X-PySkyWiFi: example.com"`
Headline X-PySkyWiFi
will be stripped by the ground daemon and used to route the request to the target website. Everything else in the request (including the body and other headers) will be forwarded unchanged.
After the request is made, you will have to wait a few minutes. If a miracle happens and nothing breaks, then sooner or later you will receive an HTTP response, as if you had sent the request through the regular Internet like a regular person. The only difference is that it will not cost you a penny. And then you will probably pay for Wi-Fi, because you have already satisfied your curiosity, and the time allotted to you on this earth is very short.
Step by step analysis
Here's how it all works inside:
Sky Proxy receives HTTP request from call
curl
. It breaks the request into pieces because the entire request is too large to be transmitted through the air miles account at once.Sky Proxy writes each fragment in turn to the account name field.
The ground daemon polls the account. When it detects that the name field has changed and contains a new fragment, it reads the fragment and sends a confirmation to the sender so that it knows it can send the next fragment. The receiver concatenates the fragments and recreates the original HTTP request.
Once the ground daemon has received and reconstructed the complete HTTP request, it sends the request over the Internet.
The ground demon receives an HTTP response.
The ground daemon sends an HTTP response to the sky proxy, reversing the process described above. This time, the ground daemon splits the HTTP response into chunks and sends these chunks one by one to the account name field (actually, to simplify the protocol, it writes these response chunks to the second account name field).
The sky proxy queries the second account. It reads each fragment and glues them back together to recreate the HTTP response.
Sky Proxy returns an HTTP response to the original call
curl
. Whereincurl
works with a completely normal HTTP response, just a little slow. It has no idea about all the crap that just happened
The sky proxy and ground daemon are pretty simple: they send HTTP requests and parse HTTP responses. The magic is in how they squeeze those requests and responses through your airmiles account. Let's take a closer look.
Pushing HTTP requests through an account
The PySkyWiFi communication logic is divided into two layers: transport And network. The job of the transport layer is to decide what data clients should send to each other. It determines how senders should break up long messages into manageable chunks, and how senders and receivers should communicate information like “I'm ready to receive another chunk.” Without looking too closely, PySkyWiFi's transport layer is somewhat similar to the TCP protocol that underlies most of the Internet.
The job of the network layer is to send data between clients after the transport protocol has decided what that data should be. It vaguely resembles IP, if you look even less closely and understand even less.
This separation of concerns between layers is useful because the transport layer doesn't have to worry about how the network layer sends data, and the network layer doesn't have to worry about what the data means or where it comes from. The transport layer just passes some data to the network layer, and the network layer passes it on however it wants.
This separation makes it easy to add support for other mileage platforms because we only need to implement a new network layer that reads a new mileage account type. It also allows us to write test versions of the network protocol that write to and read from local files rather than accounts. In each case, the network layer changes, but the transport layer remains the same.
Transport layer
A PySkyWiFi transport connection between two clients consists of two “channels” (“airmiles accounts”). Each client has a SEND channel to which it can write, and a RECV channel from which it can read. Clients write to their SEND channel by writing data to it, and they read from their RECV channel by constantly polling it and watching for changes.
From the transport layer's perspective, a channel is simply something to write and read data from. Otherwise, the transport layer doesn't care how channels work.
At any given time, a PSWF (PySkYWiFi) client can either send or receive data, but not simultaneously. The client sending mode will not see the data sent by the other client, and the client in in receiving mode must never send data because the other client will not see it. This is in contrast to TCP, in which clients can send and receive data whenever they want.
When pushing HTTP requests and HTTP responses through an account, the sky proxy sends the first message, and the ground daemon receives it. Once the sky proxy has finished sending its HTTP request, it switches to receiving mode, and the ground daemon switches to sending. The ground daemon makes the HTTP request and sends back the response, and then they switch roles again so that the sky proxy can send another HTTP request.
How long does it take for messages to travel through such a narrow channel?
PSWF uses narrow channels (like the account name field) that can only hold so much data at once, so squeezing long messages (like HTTP requests) through them is labor-intensive and time-consuming.
To send a long message, the sender first splits the message into chunks that fit into its SEND channel. It can then send each chunk one at a time over that channel.
To begin a message, the sender transmits the first piece of message data within a segment DATA
:
Segment
DATA
has the following structure:
Letter
D
Fragment serial number (a six-digit number that uniquely identifies the fragment)
The data fragment itself.
For example, a data segment in the middle of a message might look like this:
D000451adline": "Mudslide in Wigan causes m
After the sender transmits the segment DATA
he pauses. He wants to pass on the next segment. DATA
but cannot rewrite the account name field until it knows that the recipient has accepted and processed the previous one.
The receiver informs the sender that it is safe to transmit the new segment. DATA
acknowledging the reading of each segment. The receiver does this by writing the segment ACK
in your own SEND channel:
Segment
ACK
has the following structure:For example:
A000451
The sender constantly polls its own RCV channel for changes, so it immediately reads this new segment. ACK
. After the sender has read ACK
it knows that the receiver has received the segment corresponding to the sequence number ACK
. For example, if the sender receives a segment ACK
with serial number 000451
then he knows that he can safely send the next segment DATA
with serial number 000452
. Therefore, the sender extracts the next fragment from the message and assembles a new segment DATA
from this fragment and the sequence number. The sender writes the new segment to its SEND channel and then pauses, waiting for the next one ACK
.
This cycle continues until the sender has transmitted all the data in the message. To inform the receiver of completion, the sender transmits a segment END
.
Segment
END
consists of one letterE
.
When the recipient sees the segment END
it realizes that the sender's message is complete. The sender and receiver switch roles. The former sender starts polling its RECV channel for segments DATA
and the previous recipient begins to break the response message into fragments and send them through its channel.
The transport logic doesn't care at all about the details of the network layer through which the segments are transmitted. The transport layer only needs the network layer to provide two channels, one for reading and one for writing. The network layer can transmit this data via local files, a Discord profile, or an airline mileage account. Because of this versatility, PySkyWiFi can work with airline mileage accounts of any airline, as long as the company allows free login from the plane.
Here's how PSWF uses transport protocol segments to exchange long messages:
The transport layer decides what data clients should send to each other, but it says nothing about how they should send it. That's the job of the network layer.
Network layer
The network layer's job is to send data between clients. It doesn't care where the data comes from or what it means; it just gets some data from the transport layer and sends it to another client (usually via an account).
This means that the network layer is fairly simple. It also means that adding a new network layer for the new airmiles platform is also fairly simple. We use the new platform to implement a few operations and a few properties (see below), and then the transport layer can automatically use the new platform without any additional effort.
The network layer consists of two operations:
send(msg: str)
— recordmsg
in the storage. In the case of an implementation based on the airmiles program, it records the valuemsg
in the account name fieldrecv() -> str
— reading a message from storage. In the case of an airmiles program implementation, it reads the value from the account name field.
In addition, the network layer implementation must define two properties:
sleep_for
— the number of seconds the transport layer should wait between polling new segments from the RECV channel. In the case of test implementations,sleep_for
may be very low, but in case of implementation, for example, based on an air miles program account, it should be many seconds, so as not to overload the remote server with too many requests.segment_data_size
— the number of characters that the transport layer must transmit in one segment. Should be equal to the maximum size of the account field used to transmit segments (often about 20 characters).
Optionally, the network layer implementation can also provide two more operations:
connect_send()
— a hook called by the sender when the SEND channel is initialized. In an airmiles account-based implementation, this allows the client to log into the platform with a username and password. This gives the client a cookie that it can use to authenticate future calls.send
Andrecv
.connect_recv()
— a hook called by the receiver when the RECV channel is initialized
If you implement all these methods, you will be able to use PySkyWiFi on other airlines. But, again, you shouldn't do that.
Tricks and subtleties
When writing a network layer that uses the new airline mileage system, there are a couple of tricks you can use to improve the speed and reliability of the implementation.
1. Encode messages so that the account can definitely accept their format
The HTML forms for airline mileage program accounts do not allow non-alphabetical characters to be entered in the name. Stephen
you can enter it, but GET /data?id=5
will probably be rejected.
To work around this problem, the network layer must convert segments to base26 encoding before writing them to the account. Base26 encoding is a way of representing a string using letters from A
before Z
To convert a byte string to base26, you need to convert the bytes into one big number, and then represent that number in a base 26 number system (hence the name), where the letters from A
before Z
.
def b26_encode(input_string: str) -> int:
# Преобразуем входную в строку в целое число base-256
base256_int = 0
for char in input_string:
base256_int = base256_int * 256 + ord(char)
# Преобразуем целое число base-256 в строку base26
if base256_int == 0:
return 'A' # Особый случай пустых входных данных или входных данных, равных нулю
base26_str = ""
while base256_int > 0:
base26_str = chr(base256_int % 26 + 65) + base26_str
base256_int //= 26
return base26_str
b26_encode("Hello world")
# => 'CZEZINADXFFTZEIDPKM'
The transport layer doesn't need to know about this encoding. The network layer receives some bytes, encodes them in base26, and writes this encoded string of characters from A
before Z
into the account. When the network layer reads the base26 value from the account, it decodes the encoded string back into a number, then into bytes, and then returns those bytes to the transport layer.
When encoding a string in base 26, it becomes significantly longer, similar to how the binary form of a number is much longer than the decimal form. This reduces the throughput of our protocol. We can increase the width by using base52 (upper and lower case letters), which reduces the data size a bit. We'll leave this as an improvement for version 2.
2. Increase throughput by using more account fields
Another way to increase the throughput of PSWF is to increase the segment size that the network layer can handle. If we double the segment size, we double the throughput of the protocol.
Fields in mileage program accounts typically have length restrictions. For example, a name may be limited to 20 characters. However, you can maximize the capacity:
Using the full length of the field
Splitting a segment into multiple fields
Let's say we can manage five fields, each of which can store up to 20 characters. Instead of using a single field to transmit 20-character segments, we can break the 100-character segment into 5 blocks of 20 and update them all together in a single request. Then the receiver can also read all 5 fields in a single request and stitch them together to recreate the full segment.
Further improvements
HTTP CONNECT
It would be better if PySkyWiFi was used to set up a tunnel between the sky proxy and the target site HTTP requests CONNECT
rather than manually forwarding HTTP requests. Most HTTP proxies are implemented based on CONNECT
-requests, and using them will allow PySkyWiFi to act as a system-level proxy and thus process web browser requests. In addition, PySkyWiFi will then be able to directly establish TLS connections with the target website so that traffic is encrypted when transmitted through the airmiles program account.
On the other hand, for use CONNECT
It will take a lot more work, and my joke is already taking too long.
Finally
Once I had all that done, I used PySkyWiFi to load my blog's home page using curl
creating a data tunnel through a GitHub Gist. Many minutes later, I got a response. I scrolled through the HTML and thought that this was simultaneously the most and least productive flight of my life.