MacOS save file AES256 decryption fallback, otherwise error

This commit is contained in:
Ahmed Al-Taiar
2024-11-12 22:33:09 -05:00
parent 4adf90203f
commit abf122761e
6 changed files with 97 additions and 5 deletions

51
Cargo.lock generated
View File

@ -8,6 +8,17 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "ashpd"
version = "0.9.2"
@ -217,6 +228,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block-padding"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
dependencies = [
"generic-array",
]
[[package]]
name = "block2"
version = "0.5.1"
@ -272,6 +292,16 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@ -378,6 +408,15 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
[[package]]
name = "ecb"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a8bfa975b1aec2145850fcaa1c6fe269a16578c44705a532ae3edc92b8881c7"
dependencies = [
"cipher",
]
[[package]]
name = "endi"
version = "1.1.0"
@ -545,8 +584,10 @@ dependencies = [
name = "gd_save_browser"
version = "0.1.0"
dependencies = [
"aes",
"base64",
"dirs",
"ecb",
"flate2",
"phf",
"quick-xml 0.37.0",
@ -743,6 +784,16 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"block-padding",
"generic-array",
]
[[package]]
name = "itoa"
version = "1.0.11"

View File

@ -12,3 +12,5 @@ quick-xml = "0.37.0"
serde = { version = "1.0.214", features = ["derive"] }
serde_json = "1.0.132"
phf = { version = "0.11.2", features = ["macros"] }
aes = "0.8.4"
ecb = "0.1.2"

View File

@ -5,6 +5,10 @@ pub const TRUNCATE_LEVEL_DATA: bool = true;
pub const YOUTUBE_CHANNEL_BASE_URL: &str = "https://www.youtube.com/channel/";
pub const XOR_KEY: u8 = 0xB;
pub const AES_KEY: [u8; 32] = [
0x69, 0x70, 0x75, 0x39, 0x54, 0x55, 0x76, 0x35, 0x34, 0x79, 0x76, 0x5d, 0x69, 0x73, 0x46, 0x4d,
0x68, 0x35, 0x40, 0x3b, 0x74, 0x2e, 0x35, 0x77, 0x33, 0x34, 0x45, 0x32, 0x52, 0x79, 0x40, 0x7b,
];
// pub const PASSWORD_SALT: &str = "mI29fmAnxgTs";
#[derive(Copy, Clone)]
@ -105,10 +109,12 @@ pub static GAMESAVE_KEYS: Map<&'static str, &'static str> = phf_map! {
"GJA_005" => "hashed_password",
"LLM_01" => "local_levels",
"LLM_02" => "binary_version",
"LLM_03" => "local_lists",
"MDLM_001" => "song_info",
"MDLM_002" => "song_priority",
"MDLM_003" => "unknown_4",
"KBM_001" => "keybinds",
"KBM_002" => "keybinds2",
"KBM_002" => "keybinds_2",
"texQuality" => "texture_quality",
"customObjectDict" => "custom_objects",
"playerUserID" => "player_id",
@ -267,7 +273,7 @@ pub static LEVEL_KEYS: Map<&'static str, &'static str> = phf_map! {
"k43" => "two_player",
"k45" => "custom_song_id",
"k46" => "revision",
"k47" => "edited",
"k47" => "edited_outside_editor",
"k48" => "objects",
"k50" => "binary_version",
"k60" => "account_id",

View File

@ -3,6 +3,7 @@ mod util;
use constants::XOR_KEY;
use serde_json::Value;
use std::error::Error;
use std::process::exit;
use util::decrypt::*;
use util::file::*;
@ -10,8 +11,24 @@ use util::xml::*;
fn main() {
if let Some(data_encrypted) = get_dat_data() {
let data: Vec<u8> =
gz_decompress(&decode_urlsafe_base64(&xor_decrypt(&data_encrypted, XOR_KEY)).unwrap());
let decrypt_data_windows = || -> Result<Vec<u8>, Box<dyn Error>> {
Ok(gz_decompress(&decode_urlsafe_base64(&xor_decrypt(
&data_encrypted,
XOR_KEY,
))?))
};
let decrypt_data_mac =
|| -> Result<Vec<u8>, Box<dyn Error>> { Ok(decode_aes(&data_encrypted)) };
let data: Vec<u8> = if let Ok(d) = decrypt_data_windows() {
d
} else if let Ok(d) = decrypt_data_mac() {
d
} else {
println!("Decryption failed, corrupted save file?");
exit(1);
};
match validate_xml(&data) {
Ok(_) => {

View File

@ -1,10 +1,17 @@
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyInit};
use aes::Aes256;
use base64::{
engine::general_purpose::{STANDARD, URL_SAFE},
DecodeError, Engine,
};
use ecb::Decryptor;
use flate2::read::GzDecoder;
use std::io::Read;
use crate::constants::AES_KEY;
type Aes256Decryptor = Decryptor<Aes256>;
// Step 1
pub fn xor_decrypt(data: &[u8], key: u8) -> Vec<u8> {
data.iter().map(|byte| byte ^ key).collect()
@ -41,3 +48,11 @@ pub fn decode_base64(data: &str) -> String {
.map(|&byte| byte as char)
.collect()
}
pub fn decode_aes(data: &[u8]) -> Vec<u8> {
let mut buf: Vec<u8> = data.to_vec();
Aes256Decryptor::new(&AES_KEY.into())
.decrypt_padded_mut::<Pkcs7>(&mut buf)
.unwrap();
buf
}

View File

@ -122,7 +122,8 @@ fn parse_dict(
_ => Some(key.to_string()),
}
}
key if parent_key.unwrap_or_default() == "amount"
key if ["amount", "local_levels", "local_lists"]
.contains(&parent_key.unwrap_or_default())
&& key.starts_with("k_") =>
{
let replaced_key = key.replace("k_", "");