diff --git a/client/token/token.go b/client/token/token.go index 62aedff..daf534f 100644 --- a/client/token/token.go +++ b/client/token/token.go @@ -11,6 +11,11 @@ import ( "github.com/syndtr/goleveldb/leveldb/opt" ) +const ( + v10Prefix = "v10" + dpapiPrefix = "DPAPI" +) + var ( ErrorTokenRetrieve = errors.New("token: error retrieving token from database") ErrorTokenPlatform = errors.New("token: retrieval not supported on this platform yet") diff --git a/client/token/token_darwin.go b/client/token/token_darwin.go index d2a99d9..698f200 100644 --- a/client/token/token_darwin.go +++ b/client/token/token_darwin.go @@ -34,14 +34,20 @@ func GetToken() (string, error) { safeTokens, err := getSafeStorageTokens(path) if err != nil { // try another database - log.Debug(err) + log.Error(err) + continue + } + + key, err := getDecryptionKey() + if err != nil { + log.Error(err) continue } for _, safeToken := range safeTokens { // strip rickroll safeToken := strings.TrimPrefix(safeToken, "dQw4w9WgXcQ:") - token, err := decryptToken(safeToken) + token, err := decryptToken(key, safeToken) if err != nil { // try next token log.Error(err) @@ -74,24 +80,19 @@ func getDecryptionKey() ([]byte, error) { return key, nil } -func decryptToken(safeToken string) (string, error) { +func decryptToken(key []byte, safeToken string) (string, error) { safeTokenBytes, err := base64.StdEncoding.DecodeString(safeToken) if err != nil { - return "", fmt.Errorf("token: error decoding safeStorage token") - } - - decryptionKey, err := getDecryptionKey() - if err != nil { - return "", err + return "", fmt.Errorf("token: error decoding safeStorage token: %w", err) } - block, err := aes.NewCipher(decryptionKey) + block, err := aes.NewCipher(key) if err != nil { return "", err } iv := bytes.Repeat([]byte{' '}, 16) - ciphertext := safeTokenBytes[3:] + ciphertext := safeTokenBytes[len(v10Prefix):] cbc := cipher.NewCBCDecrypter(block, iv) cbc.CryptBlocks(ciphertext, ciphertext) diff --git a/client/token/token_unknown.go b/client/token/token_unknown.go index a7bf013..3659b63 100644 --- a/client/token/token_unknown.go +++ b/client/token/token_unknown.go @@ -1,4 +1,4 @@ -//go:build !darwin +//go:build !darwin && !windows package token diff --git a/client/token/token_windows.go b/client/token/token_windows.go new file mode 100644 index 0000000..35d2e35 --- /dev/null +++ b/client/token/token_windows.go @@ -0,0 +1,119 @@ +package token + +import ( + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/billgraziano/dpapi" + log "github.com/sirupsen/logrus" +) + +type LocalState struct { + OsCrypt struct { + EncryptedKey string `json:"encrypted_key"` + } `json:"os_crypt"` +} + +var versions = []string{"Discord", "discordcanary", "discordptb"} + +func GetToken() (string, error) { + log.Warnf("discord must not be running to retrieve your token under %v", runtime.GOOS) + + appdata, def := os.LookupEnv("APPDATA") + if !def { + return "", ErrorNoAppdataPath + } + + for _, ver := range versions { + path := filepath.Join(appdata, ver, "Local Storage/leveldb") + log.Debugf("searching for leveldb database in %v", path) + + safeTokens, err := getSafeStorageTokens(path) + if err != nil { + // try another database + log.Error(err) + continue + } + + path = filepath.Join(appdata, ver, "Local State") + key, err := getDecryptionKey(path) + if err != nil { + log.Error(err) + continue + } + + for _, safeToken := range safeTokens { + // strip rickroll + safeToken := strings.TrimPrefix(safeToken, "dQw4w9WgXcQ:") + token, err := decryptToken(key, safeToken) + if err != nil { + // try next token + log.Error(err) + continue + } + + return strings.Trim(token, "\n"), nil + } + } + + return "", ErrorTokenRetrieve +} + +func getDecryptionKey(path string) ([]byte, error) { + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("token: error reading local state: %w", err) + } + defer file.Close() + + var localState LocalState + if err := json.NewDecoder(file).Decode(&localState); err != nil { + return nil, fmt.Errorf("token: error unmarshaling local state: %w", err) + } + + decoded, err := base64.StdEncoding.DecodeString(localState.OsCrypt.EncryptedKey) + if err != nil { + return nil, fmt.Errorf("token: error decoding: %w", err) + } + + decrypted, err := dpapi.DecryptBytes(decoded[len(dpapiPrefix):]) + if err != nil { + return nil, fmt.Errorf("token: error decrypting with dpapi: %w", err) + } + + return []byte(decrypted), nil +} + +func decryptToken(key []byte, safeToken string) (string, error) { + safeTokenBytes, err := base64.StdEncoding.DecodeString(safeToken) + if err != nil { + return "", fmt.Errorf("token: error decoding safeStorage token: %w", err) + } + + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + + nonce := safeTokenBytes[len(v10Prefix) : len(v10Prefix)+12] + ciphertext := safeTokenBytes[len(v10Prefix)+12:] + + plaintext, err := aesgcm.Open(nil, []byte(nonce), []byte(ciphertext), nil) + if err != nil { + return "", err + } + + return string(plaintext), nil +} diff --git a/go.mod b/go.mod index d5b1525..3d683f7 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/cedws/discord-delete go 1.18 require ( + github.com/billgraziano/dpapi v0.4.0 github.com/keybase/go-keychain v0.0.0-20220610143837-c2ce06069005 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.4.0 @@ -15,6 +16,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/sys v0.0.0-20220325203850-36772127a21f // indirect diff --git a/go.sum b/go.sum index 178841a..2460e09 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,10 @@ +github.com/billgraziano/dpapi v0.4.0 h1:t39THI1Ld1hkkLVrhkOX6u5TUxwzRddOffq4jcwh2AE= +github.com/billgraziano/dpapi v0.4.0/go.mod h1:gi1Lin0jvovT53j0EXITkY6UPb3hTfI92POaZgj9JBA= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -32,6 +35,8 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -66,6 +71,7 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200828161417-c663848e9a16/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220325203850-36772127a21f h1:TrmogKRsSOxRMJbLYGrB4SBbW+LJcEllYBLME5Zk5pU= golang.org/x/sys v0.0.0-20220325203850-36772127a21f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=