Created
November 20, 2024 21:24
-
-
Save json-m/f0984612ce37a55ec6bca733c73e38a7 to your computer and use it in GitHub Desktop.
decrypts eve online launcher account data
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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