exploiting expressvpn's free trial for fun and non-profit

About 2 years ago, I was tasked with setting up some Acer laptop for office usage. While just normally navigating through the start menu, a shortcut with the name of “ExpressVPN” caught my eye. Why was ExpressVPN preinstalled on this laptop?

A free trial appears

I quickly decided to open it, as OEMs like Acer or Lenovo usually love to ship sponsored software with some kind of special deal attached. And this is what I saw:

Sorry for all of the text being in German, I do not have a English virtual machine on hand. It basically boils down to “Get ExpressVPN for free for 30 days”, the usual VPN upsell tactics (your traffic being encrypted, access to any content, improvements to gaming latency <..which is probably not even true>) and a big green button that says “Get 30 days free”, with the subtext saying “No credit card needed”

Free trials are nothing out of the ordinary, see Lenovo with their Dropbox promotion for example: they gave you an additional 100 GB of storage space for a small period of time. It’s obvious why they want to do this (spoiler: it’s money, they just hope that you’ll renew their service) and probably a very good business move for them.

I got curious about how it actually worked, and because I didn’t really feel like actually installing ExpressVPN on the laptop I should just setup, I copied over the files to another machine and proceeded to look into it on there.

When continuing on my main computer, I was surprised that I was just able to install and claim a free trial, as I thought that these types of deals were usually locked to a specific OEM property in the registry or something like a serial number. But no, I was just able to get 30 days for free, just like that. Well, they probably just blacklist my device ID for new trials, right? Right?

After a quick reinstallation using the same installer, I was able to start another trial. This was great for me, as I didn’t own any internet-accessible servers yet and had to deal with restricted, but fast WiFi everyday, and because my preferred (free) VPN provider, Cloudflare WARP only started supporting traffic on TCP port 443 about half a year ago (as of writing this - who knows when/if I’m actually going to release this post), I simply used the free trial for a while, as it was the cheapest thing that I could do.. and I’d like to think of myself as someone that is very stingy with their money.

..so, how does that installer actually work?

The “installer” application extracts two files into %localappdata%\OEM\PromoX\ExpressVPN\payload: a copy of an ExpressVPN installer and an omnious OemSetup.cmd. This script is actually responsible for invoking the installer with the correct flags for OEM installation.

Now, it does a few things:

I did actually test installing the application without removing XDeviceID and was greeted with an error telling me that I had already used my trial. To this day, I still have no idea why their installation script removes these three registry keys.

it’s time for automation!

If there’s two things someone should know about me, it’s that I’m really good at wasting time and really bad at everything else.

So, it was time to get out mitmproxy to see if I could just look at the traffic in plain-text. I already had mitmproxy’s root certificate installed on my computer (…which is probably not the best idea, now that I think about it), which works for most applications. But it looked like ExpressVPN was not going to be one of these ordinary applications. It seems like they were using certificate pinning to only allow communication with their API servers.

Included in that ‘being bad at everything’ are also my reverse engineering skills. I have used IDA, the Interactive Disassembler, before, however only to varying degrees of success. My last successful reverse engineering project was just finding a simple collision check in Forklift Truck-Simulator 2009 and replacing the responsible instructions with NOP to bypass some area restrictions.

Now, I’ve also taken a look at Rainbow Six Siege before. I even made a website about the game that fetches data from their private API (keep in mind this website is not pretty, nor very functional nowadays) and I knew that I probably wasn’t able to disable their SSL fingerprinting on my own because of my lack of reverse engineering skills and their, still ongoing, efforts against cheaters by packing the game’s binary. However, I still wanted to find some endpoints no one had documented yet. I made a reasonable guess: if they were sending HTTP requests, the request, along with its body, is probably somewhere in memory. So.. I disabled BattlEye (by removing the Service file, then pressing “No” on the prompt asking me if I wanted to install it again), blocked the game’s internet access so the data I was looking for would maybe stay in memory for a bit longer, performed an action that resulted in a request, freezed the game, dumped its memory and passed it through strings. It’s a.. unique (and probably ungodly) technique, but it worked.

Desperate for some sort of knowledge on how ExpressVPN’s requests looked like and how I could write a quick automation script, I decided to apply that exact same technique.. but much to my dismay, it looked like they were using some sort of cipher on their request- and response bodies. I proceeded to use GitHub’s search tool to see if someone had figured something out - which is probably the first thing I should’ve done - and found a reverse engineered ExpressVPN authentication client to grab OpenVPN credentials. Sadly, it didn’t really help me, as it was for the iOS client, which didn’t seem to have the same encryption keys as the Windows client. I did try looking at the daemon in IDA, but didn’t have much success. Their daemon service, used for actually managing the VPN connection and communicating with their servers, is written in Golang, however the parts that are actually relevant to me just seem to call another library (libxvclient.dll, this library also exists on iOS and Android) that is certainly not written in Go and was way out of my league. So, I decided against this approach.

just doing whatever the GUI client does

As I’ve mentioned before, the GUI doesn’t really do anything except for communicating with the daemon, as that one is actually responsible for.. well, everything from logging in to managing settings and establishing connections. I quickly took a look at the process on my computer and saw that it was listening on the loopback address on port 2015. Firing up Wireshark and looking at traffic on that port, it was painfully obvious that they just used JSON-RPC for inter-process communication. This also meant that I could also look at whatever the GUI did & automate it by just sending the same requests.

So, I ended up writing a script in Python that automatically uses the OEM installer to setup ExpressVPN, activate a free trial as soon as possible and email me an activation code. And it worked! I did have to tweak the script a few times (I was also running this under Wine, as it was just way easier to have a prefix template I could use before installing - the WOW64 migration really set me back), but it normally just worked fine. I even used it to tunnel some of the traffic of my cobalt instance through, as YouTube kept being painful to me.

but everything cool has got to come to an end

However, about a month ago, it stopped working entirely. Using the promotion installer and trying to sign up for a trial just gives a vague error and that I should try updating the application, which didn’t work at all. Well, at least I’m not really reliant on their services, and I’m also not really allowed to be upset here. They’ve just patched the loophole I’ve been using on- & off for a year.

Small fun fact: updating actually broke the entire client for me. It’s now stuck on my PC and I’m unable to uninstall it because the uninstaller just… disappeared?

so, what did I learn from doing this?