-
Notifications
You must be signed in to change notification settings - Fork 3
Method: XOR cipher
An XOR cipher is a common, basic form of encryption that performs an XOR operation on each byte with another byte from a key.
In the Majiro engine, this encryption method is used by the following formats:
-
Mjo script (if file signature has
X
) -
Rct image (if file signature has
S
)
This encryption is performed on the bytecode/image data, and starts at keyOffset 0
(relative to the beginning of the data).
This is a reciprocal cipher, meaning the same function is used to perform both encryption and decryption.
When the key index is beyond the bounds of the key length, it is wrapped back to the start.
The Mjo script cipher key is simply the raw bytes (in little-endian) of the CRC-32 hash lookup table. See here for code to generate Table32
.
static byte[] MjoKey32 = Table32.SelectMany(
BitConverter.GetBytes).ToArray();
public static void Crypt32(byte[] bytes, int keyOffset = 0) {
for(int i = 0; i < bytes.Length; i++)
bytes[i] ^= MjoKey32[(keyOffset + i) % MjoKey32.Length];
}
The Rct image cipher key adds an extra step of initializing the key with a text password. The text password is converted to a seed using Crc32
, and that seed is then XOR'ed with every element in Table32
before conversion to raw bytes.
public static bytes[] InitKey32(byte[] text) {
uint seed = Crc32(text);
return Table32.Select(v => v^seed).SelectMany(
BitConverter.GetBytes).ToArray();
}
public static void Crypt32(byte[] bytes, byte[] key, int keyOffset = 0) {
for(int i = 0; i < bytes.Length; i++)
bytes[i] ^= key[(keyOffset + i) % key.Length];
}
The Rct image password is unique per-game, and is assigned by following syscall:
-
$pic_key_set(_pwd$)
(hash: $TODOTODO)
This syscall is generally found somewhere near the beginning of start.mjo
, in the following form:
ldstr "PICPASSWORD"
syscallp $pic_key_set (1)
Click to see hexdump representation
01 08 NN NN SS[N]...
35 08 HH HH HH HH 01 00 <- search for this
The Mjo script cipher key can alternatively be created in the same fashion as Rct image keys.
This 0-byte password produces a seed of 0x0
, which when XOR'ed against Table32
, causes no additional changes.
static byte[] MjoKey32 = InitKey32(new byte[0]);
An interesting aspect of the Mjo script cipher key is that the first 4 bytes are always 0
(because the first CRC-32 table value is 0
). When encountering encrypted data, the first 4 bytes (and reoccurring every 1024 bytes) will technically be unencrypted.