Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
unixpickle
GitHub Repository: unixpickle/kahoot-hack
Path: blob/master/kahoot/sessions.go
10110 views
1
package kahoot
2
3
import (
4
"bytes"
5
"encoding/base64"
6
"encoding/json"
7
"errors"
8
"fmt"
9
"io/ioutil"
10
"net/http"
11
"net/url"
12
"regexp"
13
)
14
15
var (
16
challengeRegexp = regexp.MustCompile(`^decode\.call\(this, '([a-zA-Z0-9]*)'\); ` +
17
`function decode\(message\) \{var offset = ([0-9\+\*\(\)\s]*); ` +
18
`if \(this\.angular\.[a-zA-Z]*\(offset\)\) \{` +
19
`console.log\("Offset derived as: \{", offset, "\}"\);\}` +
20
`return _\.replace\(message, /\./g, function\(char, position\) ` +
21
`\{return String\.fromCharCode\(\(\(\(char\.charCodeAt\(0\) \* position\)` +
22
` \+ offset\) % 77\) \+ 48\);\}\);\}$`)
23
)
24
25
func gameSessionToken(gamePin string) (string, error) {
26
return attemptGameSessionToken(gamePin)
27
}
28
29
func attemptGameSessionToken(gamePin string) (string, error) {
30
resp, err := http.Get("https://kahoot.it/reserve/session/" + gamePin)
31
if resp != nil {
32
defer resp.Body.Close()
33
}
34
if err != nil {
35
return "", err
36
}
37
body, err := ioutil.ReadAll(resp.Body)
38
if err != nil {
39
return "", err
40
}
41
42
token := resp.Header.Get("X-Kahoot-Session-Token")
43
var bodyObj struct {
44
Challenge string `json:"challenge"`
45
}
46
if err := json.Unmarshal(body, &bodyObj); err != nil {
47
if string(body) == "Not found" {
48
return "", fmt.Errorf("game pin not found: %s", gamePin)
49
}
50
return "", fmt.Errorf("parse session challenge: %s", err)
51
}
52
53
return decipherToken(token, bodyObj.Challenge)
54
}
55
56
func decipherToken(xToken, challenge string) (string, error) {
57
r := bytes.NewReader([]byte(xToken))
58
base64Dec := base64.NewDecoder(base64.StdEncoding, r)
59
rawToken, err := ioutil.ReadAll(base64Dec)
60
if err != nil {
61
return "", fmt.Errorf("parse session token: %s", err)
62
}
63
64
mask, err := computeChallenge(challenge)
65
if err != nil {
66
return "", errors.New("failed to defeat challenge: " + challenge)
67
}
68
69
for i := range rawToken {
70
rawToken[i] ^= mask[i%len(mask)]
71
}
72
73
return string(rawToken), nil
74
}
75
76
func computeChallenge(ch string) ([]byte, error) {
77
submatch := challengeRegexp.FindStringSubmatch(ch)
78
if submatch != nil {
79
offset, err := eval(submatch[2])
80
if err == nil {
81
var newRunes []rune
82
for i, x := range submatch[1] {
83
n := (((int64(x) * int64(i)) + offset) % 77) + 48
84
newRunes = append(newRunes, rune(n))
85
}
86
return []byte(string(newRunes)), nil
87
}
88
}
89
90
evalURL := url.URL{
91
Scheme: "http",
92
Host: "safeval.pw",
93
Path: "/eval",
94
RawQuery: url.Values{"code": []string{ch}}.Encode(),
95
}
96
resp, err := http.Get(evalURL.String())
97
if resp != nil {
98
defer resp.Body.Close()
99
}
100
if err != nil {
101
return nil, err
102
}
103
if resp.StatusCode != http.StatusOK {
104
return nil, errors.New("server failed to evaluate: " + ch)
105
}
106
return ioutil.ReadAll(resp.Body)
107
}
108
109