Reverse Engineering Discord's QR Codes

Photo of me breaking Discord

Aaaaaand action!

Hey you!

Who me?

Yeah, you. Scan this QR code for free Nitro! (scam included)

Totally safe QR code

Okay sure!

BOOM BITCH, YOU JUST GOT FUCKING PWNED!!!

Like a Boss

In Radio Host Voice:

Has this ever happened t–

Okay lets cut the bullshit, Discord recently released a feature called “QR Code Login Totally Safe v1337 ©” aka as “QRCLTSV1337C” and it’s got people going craaaazy.

It basically lets users who are already logged into Discord on their phones, scan a QR code on a Desktop and login without having to enter any credentials. Sounds pretty cool right, what could go wrong?

Wayment

Nothing.

There’s an argument people (idiots) are trying to make that the QR codes are unsafe since they can be used to phish people into scanning them, and lead to full account takeovers. Noooooow this is a dumb argument since there’s LITERALLY a confirmation prompt when a user scans said QR code, that asks them if they agree to login to a remote desktop!



……But can we hack it doe? 🤨

QR Codes and WebSockets but no JS?

Now clearly all of this QR code generation is being done client side right? This calls for 😎 JS Inspection Time 😎:

Discord JS Files Ew, Obfuscated JS! NAHHHHHH, NOT TODAY!

Hmmmm, yeah nevermind…

I’ll pass on the obfuscated JS this time…🙄

Lets start reverse engineering Discords remote authentication by first looking at what’s inside these QR codes:

QR Code Data

Here we can clearly see the QR code contains a URL using the following format:

https://discordapp.com/ra/{FINGERPRINT}

We can assume that ra stands for “remote authentication”. After generating multiple QR codes, I can firmly say the fingerprint will always be 43 characters. My first instict was to visit the URL:

Oopsie 404

Oops, a 4 oh fucking 4. At first glance this might look like an authentication problem, but we’ll later realize the URL is never actually accessed by either clients or the backend. This really got on my nerves because WHY THE FUCK is the fingerprint embedded in a URL, if the URL is NEVER USED. JUST PUT THE FINGERPRINT IN THE QR CODE BY ITSELF!??!?

Anyways, enough with the QR code. How is this fingerprint being generated? Easy, Discord tells us:

Console Info FTW?

  1. Websocket connection to wss://remote-auth-gateway.discord.gg/?v=1
  2. Hello with a timeout of 2 minutes
  3. Handshake with a fingerprint specified
  4. Computed nonce proof successfully
  5. Handshake complete, waiting for QR code to be scanned

Lets look at the actual websocket messages:

Hello is received

Hello is received

Encoded Public Key is sent

Encoded Public Key is sent

Encrypted Nonce is received

Encrypted Nonce is received

Decrypted Nonce Proof is sent

Decrypted Nonce Proof is sent

Fingerprint is received

Fingerprint is received

Do you notice the difference between the console.info() calls and the websocket messages themselves?

No? Yeah, I thought so dumba–

The fingerprint is revealed before the nonce proof communication even occurs. This means the fingerprint derives from the public key used. To test this I actually found a NodeJS library called discord-remote-auth, which is currently the most complete library there is on Discords remote authentication, if not the only one. Googling some strings from the websocket messages returns nearly nothing. It’s mostly random results.

Story Time:

When installing this library I got an error from NodeJS saying that a crypto library function didn’t exist. This was due to my NodeJS installation being outdated. In the proccess of updating it, I BROKE MY ENTIRE SYSTEM. /usr and /etc had their permissions changed to 700, leaving me without the use of a TON of binaries and functionality. I had to boot into single user mode and revert everything back to normal. To be quiet honest, I have no idea how it occured, but what’s done is done. Now you know what I have to go through to write these blogs for you.

I love you

I digress…

Now we know how the Desktop clients handle DRA (Discord Remote Authentication), lets look at the mobile application!

AVD and Virtual Camera Fiasco

We have three options:

  1. Convert the APK file to human readable Java code using dex2jar (Source code analysis)
  2. Be lazy and just intercept the network requests (Network analysis)
  3. Do both (“Im not doing that analysis”)

Now of course being the lazy man I am (that rhymes), I decided to just throw up an Android emulator and proxy it through BurpSuite. Since newer versions of Android don’t allow user installed certificates to intercept app traffic by default, I had to spin up a Nexus 5 API 23. I could have also just used a newer Android version and install Burps CA as a system cert, but I’m lazy, remember?

Btw check out AVD-Root to root an AVD on Lincox, yours truly ❤️

So its that easy right? NO!

I don’t have a web camera, so I have to make a virtual one that streams a QR token. This led to the following events:

  1. Compiling v4l2-loopback kernel module
  2. Oops, kernel headers not found
  3. Downloading kernel source
  4. Installing kernel headers
  5. Oops, no Module.symvers existent
  6. Compile all kernel modules again (takes hours)
  7. Finally compile v4l2-loopback

After being so annoyed I left everything for the next day and got some sleep. The next morning I created a 1280x720 RGB image of a QR code so I could use pyfakewebcam, a library for streaming to fake webcam devices. Finally, after hours of Googling compilation errors and downloading source code, I could view the network requests being made by Discords mobile app when scanning a QR code.

Oh yeah!

To be fair, it was quite anti-climatic. All the Android app does is parse a DRA URL and grab the fingerprint. First it sends a POST request to https://discordapp.com/api/v6/users/@me/remote-auth with the fingerprint payload:

Mobile Fingerprint Request

Then you receive a handshake token to proceed onto cancel or finish:

Handshake Token

To confirm the login with the received handshake token:

POST https://discordapp.com/api/v6/users/@me/remote-auth/finish

or to cancel:

POST https://discordapp.com/api/v6/users/@me/remote-auth/cancel

On finish the Desktop client will receive a websocket message with the users encrypted API token, and on cancel a simple cancel message is received.

It’s pretty self explanatory. Seems mathematically flawless. I tested for some race conditions on both Desktop and Mobile but I had no luck finding anything. Off the top of my head the only flaws I can point out is the lack of rate limiting. You can virtually create hundreds of thousands of fingerprints and even verify them all yourself if you wanted to, but most importantly a malicious actor can create a massive phishing campaign using the library I mentioned above. I have yet to look at the Java code that parses the URL and the JS that generates the public key but, that’s not exactly necessary.

It’s safe to say that if you scan a QR code and don’t confirm it, your token is completely fine!

Written on January 14, 2020