Svetoslav Vassilev
Slurping Discord Tokens
TL;DR
It is very easy to have your Discord token stolen! You should install UpSight Security to protect yourself from account takeovers!
Summary
In a previous blog we explained how account takeovers happen after the user has authenticated to a certain service. Today we will take a deeper look at how Discord tokens are getting stolen form Windows users.
If you are here just for the video, get your popcorn and scroll down to the Demo paragraph at the end of this blog. Please make sure to read the Call To Action, you will find some good stuff in there as well!
(Not) Reinventing the Wheel
I have very often committed the sin of reinventing the wheel or at least trying to make it rounder (it is easy really, just use Pi with more digits after the decimal point), but when it came to Discord token theft I made the right choice by searching for already existing examples and boy ... did I find them. Let's dissect the one that I consider to be fairly well coded and can reveal both encrypted and non-encrypted tokens - empyrean.
Empyrean looks for tokens in two main locations:
1) Discord controlled lelveldb files:
'Discord': roaming+'\\discord\\Local Storage\\leveldb\\',
'Discord Canary': roaming+'\\discordcanary\\Local Storage\\leveldb\\',
'Lightcord': roaming+'\\Lightcord\\Local Storage\\leveldb\\',
'Discord PTB': roaming+'\\discordptb\\Local Storage\\leveldb\\',
2) Web browsers' storage
2.1) Leveldb files of chromium based browsers
2.2) Mozila firefox' sqlite files.
Stealing Tokens from Discord's LevelDb Files
Discord does the right thing and encrypts the tokens before storing them in LevelDb. So ... how can we find them if they are encrypted? I am glad you asked! As it turned out, the encrypted and base64 encoded tokens always start with the same prefix:

But ... why? Well, it is because Discord developers are really, really cool! Check this out youtube.com/watch?v=dQw4w9WgXcQ if you do not believe me.
Unfortunately in this case the coolness tax is pretty steep, since knowing ^^^ we can use the following regex and scour the leveldb files for the encrypted tokens.
r"dQw4w9WgXcQ:[^\"]*"
Found tokens look like so:
"dQw4w9WgXcQ:djEw*************************************************Bc="
After dropping the homage to Rick Astley prefix and base64 decoding we end-up with
print(b64decode("djEw**************************************Bc="))
b"v10_\x86\xc5h@o\x02\x0c\*****************************\xa4\x17"
Clearly "v10_" is a version number and has to be dropped as well before we proceed further. In order to decrypt we need a key and in case of Discord it is stored in the file '%AppData%\discord\Local State':
{"os_crypt":{"encrypted_key":"RFBBU********XcxZKi"}}
Obviously as per the JSON above, the key is encrypted and base64 encoded. After base64 decoding we end up with:
b'DPAPI\x01\x00\x00\*************************\x92\xa2'
It is safe to assume that 'DPAPI' is just a prefix telling us how to decrypt the key, namely by using DPAPI:
DPAPI_IMP BOOL CryptUnprotectData(
[in] DATA_BLOB *pDataIn,
[out, optional] LPWSTR *ppszDataDescr,
[in, optional] DATA_BLOB *pOptionalEntropy,
[in, optional] PVOID pvReserved,
[in, optional] CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct,
[in] DWORD dwFlags,
[out] DATA_BLOB *pDataOut );
That is precisely what empyrean does and it only needs to pass the first parameter.
We should probably expand on the fact that CryptUnprotectData uses the Windows logon credentials of the process calling the API in order to decrypt the data. There is actually nothing else Discord could do here, short of asking the user for a password, but this would defeat the whole purpose of managing tokens and will make login into Discord a pain in the rear.
After getting our hands on the unencrypted key, we can go ahead and decrypt the token. This is done using AES, where bytes [3:15] (considering zero based index) are used as IV (initialization vector), the rest [15:] are the payload and the mode is GCM (Galois Counter Mode). The result is something along the lines of:
ODYz*****************************************_v2g
That is it - now, you have yourself a decrypted token that can be used to control the user Discord account. You can delete messages, change the password, DM everybody in the victim's contact list, ... this user's Discord account is your oyster!
Stealing Discord Tokens from Web Browsers
This is quite a bit easier, since the tokens are not encrypted.
For all the browsers that use leveldb, the procedure is:
Find all the files with .log or .ldb extension in the browser AppData directory.
Execute the regex
r"[\w-]{24}\.[\w-]{6}\.[\w-]{25,110}"
Dump the match
It is exactly the same for Mozilla Firefox, only we are looking inside .sqlite files.
Demo Time
The short clip below showcases token theft in action! The "clean_bot" that I have developed deletes up to four messages, upon receiving a "/clean" command. Naturally UpSight Security prevents the token theft. Enjoy!
Call to Action
Install UpSight Security, doh! Seriously though - lets look at the Pros vs Cons.
Pros:
You will be protected against token theft from both Discord and web-browser files.
It will cost you exactly $0.
You will enjoy stress-less Discord experience as it was intended by the Discord founders.
Mass UpSight Security adoption will relieve the pressure from the esteemed Discord developers and they can keep being cool by leaving more Easter eggs for us to find! C'mon, just that should be enough of a reason!
Cons:
Nothing comes to mind ;). Ok - we may end up blocking a bit too much, but don't you worry in a week or two, we will give you the power to cast counter-spells.