Skip to content

Instantly share code, notes, and snippets.

@json-m
Created November 20, 2024 21:24
Show Gist options
  • Select an option

  • Save json-m/f0984612ce37a55ec6bca733c73e38a7 to your computer and use it in GitHub Desktop.

Select an option

Save json-m/f0984612ce37a55ec6bca733c73e38a7 to your computer and use it in GitHub Desktop.
decrypts eve online launcher account data
package main
/*
#cgo LDFLAGS: -lcrypt32
#include <windows.h>
#include <wincrypt.h>
typedef struct {
BOOL success;
DWORD error_code;
DWORD decrypted_len;
char error_msg[1024];
} DecryptResult;
DecryptResult decrypt_dpapi(
const BYTE* input_data,
DWORD input_len,
BYTE* output_buffer,
DWORD output_buffer_size
) {
DecryptResult result = {0};
DATA_BLOB DataIn = {0};
DATA_BLOB DataOut = {0};
DataIn.pbData = (BYTE*)input_data;
DataIn.cbData = input_len;
result.success = CryptUnprotectData(
&DataIn,
NULL,
NULL,
NULL,
NULL,
0,
&DataOut
);
result.error_code = GetLastError();
if (!result.success) {
FormatMessageA(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
result.error_code,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
result.error_msg,
sizeof(result.error_msg),
NULL
);
return result;
}
if (DataOut.cbData > output_buffer_size) {
result.success = FALSE;
result.error_code = ERROR_INSUFFICIENT_BUFFER;
strcpy(result.error_msg, "Output buffer too small");
LocalFree(DataOut.pbData);
return result;
}
memcpy(output_buffer, DataOut.pbData, DataOut.cbData);
result.decrypted_len = DataOut.cbData;
LocalFree(DataOut.pbData);
return result;
}
*/
import "C"
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"path/filepath"
"unsafe"
)
type StateFile struct {
Version int64 `json:"version"`
State string `json:"state"`
}
type LocalState struct {
OSCrypt struct {
EncryptedKey string `json:"encrypted_key"`
} `json:"os_crypt"`
}
func decryptDPAPI(encryptedData []byte) ([]byte, error) {
if len(encryptedData) == 0 {
return nil, fmt.Errorf("no data to decrypt")
}
outputBuffer := make([]byte, len(encryptedData)*2)
result := C.decrypt_dpapi(
(*C.BYTE)(unsafe.Pointer(&encryptedData[0])),
C.DWORD(len(encryptedData)),
(*C.BYTE)(unsafe.Pointer(&outputBuffer[0])),
C.DWORD(len(outputBuffer)),
)
if result.success == 0 {
return nil, fmt.Errorf("decryption failed (code: %d): %s",
result.error_code, C.GoString(&result.error_msg[0]))
}
return outputBuffer[:result.decrypted_len], nil
}
func decryptState(encryptedState string, masterKey []byte) ([]byte, error) {
// First base64 decode the state
data, err := base64.StdEncoding.DecodeString(encryptedState)
if err != nil {
return nil, fmt.Errorf("base64 decode failed: %v", err)
}
// Verify v10 prefix
if len(data) < 3 || string(data[:3]) != "v10" {
return nil, fmt.Errorf("invalid prefix: expected v10")
}
// Skip the v10 prefix
ciphertext := data[3:]
// Create AES cipher
block, err := aes.NewCipher(masterKey)
if err != nil {
return nil, fmt.Errorf("creating AES cipher failed: %v", err)
}
// The first 12 bytes should be the nonce/iv
if len(ciphertext) < 12 {
return nil, fmt.Errorf("ciphertext too short")
}
nonce := ciphertext[:12]
ciphertext = ciphertext[12:]
// Create GCM mode
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("creating GCM mode failed: %v", err)
}
// Decrypt
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf("decryption failed: %v", err)
}
return plaintext, nil
}
func dumpHex(data []byte, offset int, length int) {
end := offset + length
if end > len(data) {
end = len(data)
}
for i := offset; i < end; i += 16 {
fmt.Printf("%08x ", i)
for j := 0; j < 16; j++ {
if i+j < end {
fmt.Printf("%02x ", data[i+j])
} else {
fmt.Print(" ")
}
if j == 7 {
fmt.Print(" ")
}
}
fmt.Print(" |")
for j := 0; j < 16; j++ {
if i+j < end {
c := data[i+j]
if c >= 32 && c <= 126 {
fmt.Printf("%c", c)
} else {
fmt.Print(".")
}
} else {
fmt.Print(" ")
}
}
fmt.Println("|")
}
}
func main() {
if len(os.Args) != 2 {
fmt.Println("Usage: electron-decrypt <path-to-state.json>")
os.Exit(1)
}
// First get the master key from Local State
localStatePath := filepath.Join(filepath.Dir(os.Args[1]), "Local State")
localStateData, err := os.ReadFile(localStatePath)
if err != nil {
fmt.Printf("Error reading Local State: %v\n", err)
os.Exit(1)
}
var localState LocalState
if err := json.Unmarshal(localStateData, &localState); err != nil {
fmt.Printf("Error parsing Local State: %v\n", err)
os.Exit(1)
}
// Decode the base64 key
encryptedKey, err := base64.StdEncoding.DecodeString(localState.OSCrypt.EncryptedKey)
if err != nil {
fmt.Printf("Error decoding encrypted key: %v\n", err)
os.Exit(1)
}
// Remove "DPAPI" prefix if present
if bytes.HasPrefix(encryptedKey, []byte("DPAPI")) {
encryptedKey = encryptedKey[5:]
}
// Decrypt the master key using DPAPI
masterKey, err := decryptDPAPI(encryptedKey)
if err != nil {
fmt.Printf("Error decrypting master key: %v\n", err)
os.Exit(1)
}
fmt.Printf("Master key (hex): %x\n", masterKey)
// Now read and parse the state file
stateData, err := os.ReadFile(os.Args[1])
if err != nil {
fmt.Printf("Error reading file: %v\n", err)
os.Exit(1)
}
var stateFile StateFile
if err := json.Unmarshal(stateData, &stateFile); err != nil {
fmt.Printf("Error parsing JSON: %v\n", err)
os.Exit(1)
}
fmt.Printf("File version: %d\n", stateFile.Version)
// Decrypt the state data
decryptedData, err := decryptState(stateFile.State, masterKey)
if err != nil {
fmt.Printf("Error decrypting state: %v\n", err)
// Print the data we tried to decrypt
encryptedBytes, _ := base64.StdEncoding.DecodeString(stateFile.State)
fmt.Println("\nFirst 64 bytes of encrypted data:")
dumpHex(encryptedBytes, 0, 64)
os.Exit(1)
}
// Try to parse as JSON
var prettyJSON bytes.Buffer
if err := json.Indent(&prettyJSON, decryptedData, "", " "); err == nil {
fmt.Println("\nDecrypted data is valid JSON:")
fmt.Println(prettyJSON.String())
} else {
fmt.Println("\nDecrypted data (first 128 bytes):")
dumpHex(decryptedData, 0, 128)
}
}
@json-m
Copy link
Author

json-m commented Nov 20, 2024

you'll need mingw at least for the cgo bits.

simply go build dec.go -> .\dec.exe .\state.json -> receive decrypted data from electron safestorage

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment