Reverse Engineering Discord's QR Codes
Yeah, you. Scan this QR code for free Nitro! (scam included)
BOOM BITCH, YOU JUST GOT FUCKING PWNED!!!
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?
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 😎:
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:
Here we can clearly see the QR code contains a URL using the following format:
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:
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:
- Websocket connection to
- Hello with a timeout of 2 minutes
- Handshake with a fingerprint specified
- Computed nonce proof successfully
- Handshake complete, waiting for QR code to be scanned
Lets look at the actual websocket messages:
Hello is received
Encoded Public Key is sent
Encrypted Nonce is received
Decrypted Nonce Proof is sent
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.
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.
/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.
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:
- Convert the APK file to human readable Java code using dex2jar (Source code analysis)
- Be lazy and just intercept the network requests (Network analysis)
- 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:
- Compiling v4l2-loopback kernel module
- Oops, kernel headers not found
- Downloading kernel source
- Installing kernel headers
- Oops, no Module.symvers existent
- Compile all kernel modules again (takes hours)
- 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.
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:
Then you receive a handshake token to proceed onto
To confirm the login with the received handshake token:
or to cancel:
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!