Skip to content

Commit 6fdbdcb

Browse files
committed
Update to v1.1
1 parent fc989d7 commit 6fdbdcb

2 files changed

Lines changed: 143 additions & 38 deletions

File tree

README.md

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,36 @@
33

44
## About
55

6-
Discord Cache Dump is a tool that gathers the cache of all known Electron Discord client build types, copies it into its own directory, and gives them their appropriate file extensions.
6+
Discord Cache Dump is a tool that gathers the cache of all known Electron Discord client builds, copies into their corresponding build directories, and gives the files their appropriate file extensions.
77

88
## Features
99

10-
- Detection of known Discord build types
10+
- Detection of known Discord build types (can do multiple in a single run)
1111
- Discloses count of amount of files it is unable to gather at the time for that particular build
1212
- Supports Windows, GNU/Linux, and macOS
1313
- Checks storage available where the program is being ran before copying
1414
- Dumps are timestamped along with the cache being in their own build type directories
1515

1616
## Known limitations
1717

18-
- The files that the Discord client process is utilising at the time cannot be copied over as it involves opening
19-
- It is advised to just kill the client you wish to copy files from that contains strings of *potentially sensitive* data
20-
- macOS requires root due to how permissions are set
21-
- The tool does get the sudoer user as well as UID for permission changing purposes, so no need to worry about that
18+
- The files that a Discord client process is utilising at the time cannot be copied over as it involves opening
19+
- It is advised to kill the parent process of the client(s) you wish to copy files from that contains strings of *potentially sensitive* data
20+
- Due to how cache is stored in later GNU/Linux builds, this program includes a file extraction function
21+
- .MOV files cannot be completely extracted
22+
- Some .GZ (gzip) files containing mentions of .JS cannot be completely extracted
23+
- Some cache files containing request and response data cannot be completely extracted
24+
- Cache files that cannot be handled at all will not have their contents altered as they are being copied over
2225

2326
## Always opened files
24-
The following files are known to be constantly used by Discord and so cannot be copied while that Discord client is running.
27+
The following files are known to be used constantly by Discord and so cannot be copied while that Discord client is running.
2528

2629
| File | Contents |
2730
| ------ | ------------------------------------------------------------------------------------------------ |
28-
| index | Unknown |
31+
| index | Cache index |
2932
| data_0 | Unknown |
3033
| data_1 | Full URLs to friendly URLs, API, avatars, emojis, embeds, attachments, uploads (self and others) |
3134
| data_2 | Code, assets (png, svg) |
32-
| data_3 | Certificates, hostnames, IP addresses, image EXIF, reference to javascript assets (webpack) |
35+
| data_3 | Certificates, hostnames, IP addresses, image EXIF, references to javascript assets (webpack) |
3336

3437
## Prerequisites
3538

@@ -41,19 +44,22 @@ In order to compile the tool, there are a few things required to get it set up.
4144

4245
## Usage
4346

44-
| Platform | Command |
45-
| --------- | ------------------- |
46-
| Windows | `dcd_windows.exe` |
47-
| GNU/Linux | `./dcd_linux` |
48-
| macOS | `sudo ./dcd_darwin` |
47+
| Platform | Command |
48+
| --------- | ------------------------------------------------------ |
49+
| Windows | `dcd_windows.exe` |
50+
| GNU/Linux | `./dcd_linux` (run `chmod +x ./dcd_linux` initially) |
51+
| macOS | `./dcd_darwin` (run `chmod +x ./dcd_darwin` initially) |
4952

5053
## Credits
51-
| User | Contribution |
52-
| ------------------------------------------- | --------------------------------------------------- |
53-
| [NodePoint](https://github.com/NodePoint) | Development, Windows and GNU/Linux platform testing |
54-
| [NotZoeyDev](https://github.com/NotZoeyDev) | macOS platform testing |
54+
| User | Contribution |
55+
| ------------------------------------------- | ----------------------------------------------------------------------------------- |
56+
| [NodePoint](https://github.com/NodePoint) | Research & analysis, development, Windows, GNU/Linux and macOS platform testing |
57+
| [NotZoeyDev](https://github.com/NotZoeyDev) | macOS platform testing |
58+
| [@not_utf16](https://twitter.com/not_utf16) | GNU/Linux platform testing |
5559

5660
## Tested
57-
- Windows 10 Pro
61+
- Windows 10
62+
- Ubuntu (GNU/Linux)
5863
- Kali Linux (GNU/Linux)
64+
- Solus (GNU/Linux)
5965
- macOS

main.go

Lines changed: 118 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package main
22

33
import (
4+
"bytes"
45
"fmt"
56
"io/ioutil"
67
"os"
78
"os/user"
9+
"regexp"
810
"runtime"
911
"strconv"
1012
"strings"
@@ -15,7 +17,7 @@ import (
1517
)
1618

1719
const (
18-
softVersion = 1.0
20+
softVersion = "1.1"
1921
dumpDir = "dump"
2022
)
2123

@@ -35,15 +37,8 @@ func exitNewLine() string {
3537
// Check for root, as running the program with it can change the path
3638
func rootCheck(tuid int) {
3739
if tuid == 0 {
38-
if platform != "darwin" {
39-
fmt.Print("[NOTICE] This program is running as root\n")
40-
fmt.Print("[...] The logged in user will be used.\n\n")
41-
}
42-
} else {
43-
if platform == "darwin" {
44-
fmt.Printf("[ERROR] Due to file permissions, this must be ran as root on macOS%s", exitNewLine())
45-
os.Exit(1)
46-
}
40+
fmt.Print("[NOTICE] This program is running as root\n")
41+
fmt.Print("[...] The logged in user will be used.\n\n")
4742
}
4843
}
4944

@@ -58,8 +53,106 @@ var unreadableRes int64
5853
var overallSize int64
5954
var spareStorage int64
6055

56+
// Extract cache files (for GNU/Linux)
57+
func fileExtractor(contents []byte) []byte {
58+
var magicNumber string
59+
formatLock := false
60+
sanitiseLock := false
61+
re := regexp.MustCompile("^[a-zA-Z0-9_\\-:/.%?&=]*$")
62+
63+
var extractDat []byte
64+
extractDat = contents
65+
66+
// Clean ending
67+
magicNumber = "\xd8\x41\x0d\x97\x45\x6f\xfa\xf4\x01\x00"
68+
extractRequired := bytes.Contains(extractDat, []byte(magicNumber))
69+
if extractRequired {
70+
// Remove end bytes
71+
extractDat = bytes.SplitN(extractDat, []byte(magicNumber), 2)[0]
72+
}
73+
74+
// There are a few file types that are missing data we would need
75+
// in order to extract them the way we intend to do so.
76+
// In that case, we rely on their magic numbers.
77+
78+
// Clean JPG/JPEG
79+
if !formatLock {
80+
magicNumber = "\xff\xd8\xff"
81+
magicNumberIndex := bytes.Index(contents, []byte(magicNumber))
82+
extractRequired = false
83+
if magicNumberIndex > -1 {
84+
if bytes.Contains(contents[magicNumberIndex:magicNumberIndex+3], []byte(magicNumber)) {
85+
if len(contents[magicNumberIndex+6:magicNumberIndex+10]) == 4 {
86+
if bytes.Equal(contents[magicNumberIndex+6:magicNumberIndex+10], []byte("\x4a\x46\x49\x46")) {
87+
extractRequired = true
88+
}
89+
}
90+
}
91+
}
92+
if extractRequired {
93+
formatLock = true
94+
extractDat = bytes.SplitN(extractDat, []byte(magicNumber), 2)[1]
95+
extractDat = append([]byte(magicNumber), extractDat...)
96+
}
97+
}
98+
99+
// Clean WEBP
100+
if !formatLock {
101+
magicNumber = "\x00\x00\x57\x45\x42\x50\x56\x50\x38"
102+
extractRequired = bytes.Contains(contents, []byte(magicNumber))
103+
if !extractRequired {
104+
magicNumber = "\x01\x00\x57\x45\x42\x50\x56\x50\x38"
105+
extractRequired = bytes.Contains(contents, []byte(magicNumber))
106+
}
107+
if extractRequired {
108+
formatLock = true
109+
magicNumber = "\x52\x49\x46\x46"
110+
extractDat = bytes.SplitN(extractDat, []byte(magicNumber), 2)[1]
111+
extractDat = append([]byte(magicNumber), extractDat...)
112+
}
113+
}
114+
115+
// Extract the rest
116+
if !formatLock && len(extractDat[12:13]) == 1 {
117+
uriLengthInt := extractDat[12:13]
118+
uriLengthConv := fmt.Sprintf("%d", uriLengthInt[0])
119+
uriLength, err := strconv.Atoi(uriLengthConv)
120+
if err == nil {
121+
if uriLength > 0 && len(extractDat[24:24+uriLength]) == uriLength && re.MatchString(string(extractDat[24 : 24+uriLength][0])) {
122+
fileContent := extractDat[24+uriLength:]
123+
extractDat = fileContent
124+
} else {
125+
extractDat = contents
126+
sanitiseLock = true
127+
}
128+
} else {
129+
extractDat = contents
130+
sanitiseLock = true
131+
}
132+
}
133+
134+
// Clean extra data
135+
magicNumber = "\x6b\x67\x53\x65\x01\xbf\x97\xeb"
136+
extractRequired = bytes.Contains(extractDat, []byte(magicNumber))
137+
if !sanitiseLock && extractRequired {
138+
extractDatTmp := bytes.Split(extractDat, []byte(magicNumber))
139+
var extractDatBuilder []byte
140+
for i := 0; i < len(extractDatTmp); i++ {
141+
if i > 0 {
142+
extractDatBuilder = append(extractDatBuilder, extractDatTmp[i][24:]...)
143+
}
144+
}
145+
extractDat = extractDatBuilder
146+
}
147+
148+
extractedConvByte := extractDat
149+
150+
return extractedConvByte
151+
}
152+
61153
// Copy source file to destination
62154
func copyFile(from string, to string, permuid int) {
155+
var resData []byte
63156
input, err := ioutil.ReadFile(from)
64157
if err != nil {
65158
/*
@@ -70,12 +163,18 @@ func copyFile(from string, to string, permuid int) {
70163
return
71164
}
72165

73-
err = ioutil.WriteFile(to, input, 0644)
166+
if platform == "linux" {
167+
resData = fileExtractor(input)
168+
} else {
169+
resData = input
170+
}
171+
172+
err = ioutil.WriteFile(to, resData, 0644)
74173
if err != nil {
75174
fmt.Printf("\n[ERROR] Write error: %s\n", err)
76175
return
77176
}
78-
// If ran as root as a sudoer on Linux or macOS, it's going to be root:root, so we change it to what it really should be
177+
// If ran as root as a sudoer on GNU/Linux or macOS, it's going to be root:root, so we change it to what it really should be
79178
if platform != "windows" {
80179
os.Chown(to, permuid, permuid)
81180
}
@@ -107,10 +206,11 @@ var sudoerUID int
107206

108207
func main() {
109208
fmt.Print("\n")
209+
110210
// Banner
111-
fmt.Print("#####################################\n")
112-
fmt.Printf("# Discord Cache Dump :: Version %0.1f #\n", softVersion)
113-
fmt.Print("#####################################\n\n")
211+
fmt.Print("#######################################\n")
212+
fmt.Printf("# Discord Cache Dump :: Version %s #\n", softVersion)
213+
fmt.Print("#######################################\n\n")
114214

115215
user, err := user.Current()
116216
if err != nil {
@@ -283,13 +383,13 @@ func main() {
283383
// Check and create dump directory structure
284384
timeDateStamp := timeDate()
285385
if _, err := os.Stat(dumpDir + "/"); os.IsNotExist(err) {
286-
os.Mkdir(dumpDir, 0644)
386+
os.Mkdir(dumpDir, 0755)
287387
if platform != "windows" {
288388
os.Chown(dumpDir, sudoerUID, sudoerUID)
289389
}
290390
}
291391
if _, err := os.Stat(dumpDir + "/" + timeDateStamp + "/"); os.IsNotExist(err) {
292-
os.Mkdir(dumpDir+"/"+timeDateStamp, 0644)
392+
os.Mkdir(dumpDir+"/"+timeDateStamp, 0755)
293393
if platform != "windows" {
294394
os.Chown(dumpDir+"/"+timeDateStamp, sudoerUID, sudoerUID)
295395
}
@@ -299,7 +399,7 @@ func main() {
299399
for i := 0; i < len(discordBuildDir); i++ {
300400
if len(cachedFile[i]) > 0 {
301401
if _, err := os.Stat(dumpDir + "/" + timeDateStamp + "/" + discordBuildName[i] + "/"); os.IsNotExist(err) {
302-
os.Mkdir(dumpDir+"/"+timeDateStamp+"/"+discordBuildName[i], 0644)
402+
os.Mkdir(dumpDir+"/"+timeDateStamp+"/"+discordBuildName[i], 0755)
303403
if platform != "windows" {
304404
os.Chown(dumpDir+"/"+timeDateStamp+"/"+discordBuildName[i], sudoerUID, sudoerUID)
305405
}
@@ -355,5 +455,4 @@ func main() {
355455
} else {
356456
fmt.Printf("Saved: %s/%s/%s%s", curDir, dumpDir, timeDateStamp, exitNewLine())
357457
}
358-
359458
}

0 commit comments

Comments
 (0)