The secureNew file is used by Among Us to check unlocked skins, pets, etc. It has some basic anti-tamper.
Data types used by secureNew
Name | Size (bytes) | Encodes | Comments |
---|---|---|---|
packed_u32 | 1-5 | an unsigned 32-bit integer | Encoded using LEB128. Also see BinaryReader.Read7BitEncodedInt |
String | Varies | A UTF-8 string | Prefixed by it's size as a packed_u32 |
Each byte is xor'd by it's zero-indexed offset modulo 212
After undoing the xor, the file a sequence of String
s, read until the first empty string.
The first string is the computer-specific string returned by deviceUniqueIdentifier. This is checked to detect tampering
The remaining strings are names of purchased/unlocked DLC
use std::io::{Write, Read};
pub fn read_purchase_data(data: &[u8]) -> Vec<String> {
// Decode
let decoded: Vec<u8> = data
.iter()
.enumerate()
.map(|(i, b)| b ^ (i % 212) as u8)
.collect();
let mut values = Vec::new();
let mut r = &decoded[..];
loop {
let value = r.read_string();
if value.is_empty() {
break;
}
values.push(value);
}
values
}
pub fn write_purchase_data(values: &[String]) -> Vec<u8> {
let mut w = Vec::<u8>::new();
for value in values {
w.write_string(value);
}
w.write_string("");
w.into_iter()
.enumerate()
.map(|(i, byte)| byte ^ (i % 212) as u8)
.collect()
}
// Read extensions
trait ReadExt {
fn read_u8(&mut self) -> u8;
fn read_packed_u32(&mut self) -> u32;
fn read_string(&mut self) -> String;
}
impl ReadExt for &[u8] {
fn read_u8(&mut self) -> u8 {
let mut buf = [0; 1];
self.read_exact(&mut buf).unwrap();
buf[0]
}
fn read_packed_u32(&mut self) -> u32 {
let mut value: u32 = 0;
for offset in (0..).step_by(7) {
let byte = self.read_u8();
value |= ((byte & 0b0111_1111) as u32) << offset;
// Return if "read next" bit unset
if (byte & 0b1000_0000) == 0 {
return value;
}
}
unreachable!()
}
fn read_string(&mut self) -> String {
let size = self.read_packed_u32();
let mut buffer = vec![0; size as usize];
self.read_exact(&mut buffer).unwrap();
String::from_utf8(buffer).unwrap()
}
}
trait WriteExt {
fn write_u8(&mut self, value: u8);
fn write_packed_u32(&mut self, value: u32);
fn write_string(&mut self, value: &str);
}
impl WriteExt for Vec<u8> {
fn write_u8(&mut self, value: u8) {
self.write_all(&value.to_le_bytes()).unwrap();
}
fn write_packed_u32(&mut self, mut value: u32) {
while value >= 128 {
self.write_u8(value as u8 | 128);
value >>= 7;
}
self.write_u8(value as u8);
}
fn write_string(&mut self, value: &str) {
self.write_packed_u32(value.len() as u32);
self.write_all(value.as_bytes()).unwrap();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
// Example data from a `secureNew` file
let data = [0x28,0x67,0x67,0x65,0x33,0x64,0x67,0x36,0x3a,0x38,0x33,0x33,0x6d,0x6b,0x68,0x6a,0x24,0x25,0x70,0x75,0x2d,0x25,0x77,0x26,0x7e,0x7c,0x79,0x78,0x2c,0x7b,0x27,0x2d,0x46,0x47,0x16,0x10,0x14,0x11,0x47,0x12,0x19,0x21,0x42,0x4a,0x58,0x72,0x40,0x40,0x5e,0x54,0x3f,0x5b,0x55,0x41,0x69,0x56,0x4b,0x4d,0x48,0x54,0x52,0x5c,0x4b,0x4b,0x4f,0x29,0x23,0x37,0x1b,0x27,0x27,0x34,0x2d,0x2b,0x2b,0x27,0x20,0x2e,0x2f,0x3f,0x5d,0x39,0x33,0x27,0x0b,0x37,0x24,0x36,0x31,0x37,0x29,0x37,0x29,0x3a,0x55,0x37,0x01,0x15,0x3d,0x01,0x11,0x16,0x0e,0x0f,0x09,0x1d,0x61,0x03,0x0d,0x19,0x31,0x0c,0x11,0x01,0x06,0x12,0x1d,0x1b,0x66,0x1f,0x19,0x0d,0x25,0x1f,0x13,0x08,0x1c,0x13,0xe5,0xf5,0xed,0xf3,0xec,0xe4,0xf2,0x8a,0xe0,0xe8,0xfe,0xd4,0xea,0xe1,0xe1,0xf8,0xf5,0xe3,0xe2,0xfc,0xe0,0x9e,0xfe,0xf6,0xec,0xc6,0xfd,0xf4,0xfb,0xfa,0xf2,0xfa,0xd3,0xaa,0xca,0xc2,0xd0,0xfa,0xce,0xc6,0xda,0xcd,0xc2,0xca,0xd8,0xa1,0xc6,0xce,0xc4,0xee,0xdf,0xda,0xd8,0xdc,0xc2,0xd6,0xca,0xc0,0xb6,0xd3,0xdd,0xc9,0xe1,0xcf,0xa1,0xb1,0xa7,0xb1,0xac,0xa4,0xb2,0xcb,0xa0,0xa8,0xbe,0x94,0xbc,0xac,0xbc,0xbb,0xa9,0xb9,0xb3,0xa7,0x0a,0x69,0x63,0x77,0x5b,0x75,0x69,0x6b,0x61,0x6a,0x6f,0x04,0x64,0x6c,0x7a,0x50,0x63,0x65,0x77,0x67,0x7c,0x70,0x65,0x74,0x77,0x69,0x7f,0x11,0x74,0x7c,0x6a,0x40,0x54,0x4e,0x52,0x4b,0x45,0x51,0x29,0x4f,0x49,0x5d,0x75,0x5f,0x43,0x5a,0x4b,0x43,0x47,0x58,0x48,0x52,0x46,0x51,0x3d,0x5f,0x59,0x4d,0x65,0x49,0x49,0x4e,0x4d,0x56,0x21,0x2f,0x48,0x2b,0x25,0x31,0x19,0x31,0x21,0x22,0x23,0x25,0x2b,0x46,0x26,0x2e,0x24,0x0e,0x25,0x32,0x38,0x39,0x35,0x36,0x28,0x48,0x32,0x3a,0x28,0x2e,0x01,0x31,0x05,0x16,0x1b,0x06,0x05,0x17,0x15,0x55,0x58,0x58,0x52,0x64,0x04,0x0c,0x1a,0x30,0x07,0x19,0x1b,0x07,0x11,0x01,0x19,0x07,0x10,0x18,0x0e,0x77,0x14,0x1c,0x0a,0x0c,0xdf,0xe3,0xf7,0xed,0xe0,0xe9,0xe3,0xb5,0x88];
// Read and test against known values
let values = read_purchase_data(&data);
assert_eq!(values, ["fef7aa12198affe44bf90a1fecc0f92ff4304a51", "hat_none", "hat_astronaut", "hat_baseballcap", "hat_brainslug", "hat_bushhat", "hat_captain", "hat_doubletophat", "hat_flowerpot", "hat_goggles", "hat_hardhat", "hat_military", "hat_paperhat", "hat_partyhat", "hat_police", "hat_stethescope", "hat_tophat", "hat_towelwizard", "hat_russian", "hat_viking", "hat_wallcap", "hats_newyears2018", "hat_whitetophat", "hats_bundle2"]);
// Write and test against original
let written = write_purchase_data(&values);
assert_eq!(written, data);
}
}