private static string DeobfuscateFileName(string fileName, uint seed) { const string keyspace = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; var sb = new StringBuilder(fileName.Length); uint key = MersenneTwister.GenRand(seed); int shift = (byte)((key >> 24) + (key >> 16) + (key >> 8) + key); for (int i = 0; i < fileName.Length; i++, shift++) { char c = fileName[i]; // the crypto is basically caesar cipher on reversed keyspace, with shifting index if (keyspace.Contains(c)) { int idx = keyspace.IndexOf(c); int reverseIdx = keyspace.Length - idx - 1; c = keyspace[mod(reverseIdx - shift, keyspace.Length)]; } sb.Append(c); // mod function, because % operator is remainder int mod(int x, int m) => (x % m + m) % m; } return(sb.ToString()); }
private static void UnobfuscateFileName(byte[] s, uint seed) { const string FWD = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const string REV = "zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA"; //MersenneTwister.Seed(seed); //uint key = MersenneTwister.GenRand(); uint key = MersenneTwister.GenRand(seed); int shift = (byte)((key >> 24) + (key >> 16) + (key >> 8) + key); for (int i = 0; i < s.Length; i++, shift++) { byte c = s[i]; if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) { int index = 0; int index2 = shift; while (REV[index2 % 0x34] != c) { if (REV[(shift + index + 1) % 0x34] == c) { index += 1; break; } if (REV[(shift + index + 2) % 0x34] == c) { index += 2; break; } if (REV[(shift + index + 3) % 0x34] == c) { index += 3; break; } index += 4; index2 += 4; if (index > 0x34) { break; } } if (index < 0x34) { s[i] = (byte)FWD[index]; } } //shift++; } return; }
/// <summary> /// Unobfuscates the <see cref="KIFENTRY.FileNameRaw"/> field using the specified seed. /// </summary> /// <param name="fileName">The raw file name in bytes.</param> /// <param name="seed"> /// The seed used to generate the unobfuscation key. /// This is the value generated by <see cref="GenerateTocSeed"/> + entryIndex. /// </param> private static void UnobfuscateFileName(byte[] fileName, uint seed) { const int Length = 52; const string FWD = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const string REV = "zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA"; uint key = MersenneTwister.GenRand(seed); int shift = (byte)((key >> 24) + (key >> 16) + (key >> 8) + key); for (int i = 0; i < fileName.Length; i++, shift++) { byte c = fileName[i]; if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) { int index = 0; int index2 = shift; while (REV[index2 % Length] != c) { if (REV[(shift + index + 1) % Length] == c) { index += 1; break; } if (REV[(shift + index + 2) % Length] == c) { index += 2; break; } if (REV[(shift + index + 3) % Length] == c) { index += 3; break; } index += 4; index2 += 4; if (index >= Length) // We're outside the array, no need to continue { break; } } if (index < Length) // Only assign if we're inside the array { fileName[i] = (byte)FWD[index]; } } } }
private static string[] IdentifyFileTypes(Stream stream, string kifintPath, string vcode2) { BinaryReader reader = new BinaryReader(stream); KIFHDR hdr = reader.ReadUnmanaged <KIFHDR>(); if (hdr.Signature != "KIF") // It's really a KIF INT file { throw new UnexpectedFileTypeException(kifintPath, "KIF"); } KIFENTRY[] entries = reader.ReadUnmanagedArray <KIFENTRY>(hdr.EntryCount); uint tocSeed = GenerateTocSeed(vcode2); uint fileKey = 0; bool decrypt = false; // Obtain the decryption file key if one exists for (int i = 0; i < hdr.EntryCount; i++) { if (entries[i].FileName == KeyFileName) { fileKey = MersenneTwister.GenRand(entries[i].Length); decrypt = true; break; } } HashSet <string> extensions = new HashSet <string>(); // Decrypt the KIFINT entries using the file key if (decrypt) { for (uint i = 0; i < hdr.EntryCount; i++) { if (entries[i].FileName == KeyFileName) { continue; } // Give the entry the correct name UnobfuscateFileName(entries[i].FileNameRaw, tocSeed + i); } } for (uint i = 0; i < hdr.EntryCount; i++) { string entryFileName = entries[i].FileName; if (entryFileName == KeyFileName) { continue; } extensions.Add(Path.GetExtension(entryFileName)); } return(extensions.ToArray()); }
public IEnumerable <IExtractableFile> LoadFilesFromArchive(string inputArchive) { // find keys from exe, if not present yet if (key1 is null || key2 is null) { (key1, key2, indexRngSeed) = LoadKeys(Path.GetDirectoryName(inputArchive)); } using (var fs = File.OpenRead(inputArchive)) using (var br = new BinaryReader(fs)) { string magic = Encoding.ASCII.GetString(br.ReadBytes(4)); if (magic != FileMagic) { throw new InvalidMagicException(); } int entries = br.ReadInt32(); BlowfishDecryptor dec = null; for (int i = 0; i < entries + 1; i++) { string fileName = br.ReadBytes(0x40).ToCString(); ulong read = br.ReadUInt64(); if (fileName == "__key__.dat") { // don't make key multiple times if (!(dec is null)) { continue; } uint key = MersenneTwister.GenRand((uint)(read >> 32)); dec = new BlowfishDecryptor(new Blowfish(BitConverter.GetBytes(key))); continue; } if (dec is null) { throw new Exception("First file entry wasn't encryption key"); } fileName = DeobfuscateFileName(fileName, (uint)unchecked (indexRngSeed + i)); read += (ulong)i; var newBytes = dec.TransformFinalBlock(BitConverter.GetBytes(read), 0, 8); uint pos = BitConverter.ToUInt32(newBytes, 0); uint len = BitConverter.ToUInt32(newBytes, 4); yield return(new EncryptedFileSlice(fileName, pos, len, inputArchive, dec)); } } }
public void TestInitFromArray() { MersenneTwister mt = new MersenneTwister(new ulong[] {0x12345, 0x23456, 0x34567, 0x45678}); Assert.AreEqual(7266447313870364031, mt.GenRand()); }
public void TestNoSeed() { MersenneTwister mt = new MersenneTwister(); Assert.AreEqual(14514284786278117030, mt.GenRand()); }
/// <summary> /// Decrypts the KIFINT archives using the known archive type, install directory, and name of executable with /// the V_CODE2 used to decrypt. /// </summary> /// <param name="type">The type of archive to look for and decrypt.</param> /// <param name="stream">The stream to the open KIFINT archive.</param> /// <param name="installDir">The installation directory for both the archives and executable.</param> /// <param name="exePath">The path to the executable to extract the V_CODE2 key from.</param> /// <returns>The <see cref="KifintLookup"/> merged with all loaded archives.</returns> /// /// <exception cref="ArgumentNullException"> /// <paramref name="stream"/>, <paramref name="kifintPath"/>, or <paramref name="exePath"/> is null. /// </exception> /// <exception cref="ObjectDisposedException"> /// The <paramref name="stream"/> is closed. /// </exception> private static KifintArchive LoadLookup(KifintType type, Stream stream, string kifintPath, string vcode2, KifintProgressArgs progress, KifintProgressCallback callback) { if (kifintPath == null) { throw new ArgumentNullException(nameof(kifintPath)); } if (vcode2 == null) { throw new ArgumentNullException(nameof(vcode2)); } BinaryReader reader = new BinaryReader(stream); KIFHDR hdr = reader.ReadUnmanaged <KIFHDR>(); UnexpectedFileTypeException.ThrowIfInvalid(hdr.Signature, KIFHDR.ExpectedSignature); KIFENTRY[] entries = reader.ReadUnmanagedArray <KIFENTRY>(hdr.EntryCount); progress.EntryName = null; progress.EntryIndex = 0; progress.EntryCount = entries.Length; // Table of contents seed uint tocSeed = GenerateTocSeed(vcode2); uint fileKey = 0; int fileKeyIndex = -1; Blowfish blowfish = null; // Obtain the decryption file key if one exists for (int i = 0; i < hdr.EntryCount; i++) { if (entries[i].FileName == KeyFileName) { fileKey = MersenneTwister.GenRand(entries[i].Length); blowfish = CatDebug.NewBlowfish(fileKey); fileKeyIndex = i; break; } } const int ProgressThreshold = 500; // Decrypt the KIFINT entries using the file key if (fileKeyIndex != -1) { for (uint i = 0; i < hdr.EntryCount; i++, progress.EntryIndex++) { if (unchecked ((int)i) == fileKeyIndex) { continue; } // Give the entry the correct name UnobfuscateFileName(entries[i].FileNameRaw, unchecked (tocSeed + i)); // Apply the extra offset before decryption entries[i].Offset += i; // Decrypt the entry's offset and length blowfish.Decrypt(ref entries[i].Info); progress.EntryName = entries[i].FileName; if (i % ProgressThreshold == 0 || i + 1 == hdr.EntryCount) { callback?.Invoke(progress); } } } return(new KifintArchive(kifintPath, entries, fileKeyIndex != -1, fileKey, type, blowfish)); }
private static bool DecryptArchive(Stream inStream, string kifintPath, string kifintBackup, string vcode2, bool isBackup, KifintProgressArgs progress, KifintProgressCallback callback) { if (kifintPath == null) { throw new ArgumentNullException(nameof(kifintPath)); } if (vcode2 == null) { throw new ArgumentNullException(nameof(vcode2)); } /*const string BackupName = "intbackup"; * string dir = Path.GetDirectoryName(kifintPath); * string name = Path.GetFileName(kifintPath); * string kifintBackup = Path.Combine(dir, BackupName, name); * bool isBackup = Path.GetFileName(dir).ToLower() == BackupName; * if (isBackup) { * dir = Path.GetDirectoryName(dir); * kifintPath = Path.Combine(dir, name); * } * else { * * }*/ Directory.CreateDirectory(Path.GetDirectoryName(kifintBackup)); BinaryReader reader = new BinaryReader(inStream); KIFHDR hdr = reader.ReadUnmanaged <KIFHDR>(); try { UnexpectedFileTypeException.ThrowIfInvalid(hdr.Signature, KIFHDR.ExpectedSignature); } catch (UnexpectedFileTypeException) { if (!isBackup && File.Exists(kifintBackup)) { inStream.Close(); // We must have stopped while decrypting an archive. Let's restart from the already-made backup using (inStream = File.OpenRead(kifintBackup)) DecryptArchive(inStream, kifintPath, kifintBackup, vcode2, true, progress, callback); return(true); } throw; } List <KIFENTRY> entries = new List <KIFENTRY>(hdr.EntryCount); entries.AddRange(reader.ReadUnmanagedArray <KIFENTRY>(hdr.EntryCount)); // Table of contents seed uint tocSeed = GenerateTocSeed(vcode2); uint fileKey = 0; bool decrypt = false; Blowfish blowfish = null; int keyIndex = -1; // Obtain the decryption file key if one exists for (int i = 0; i < hdr.EntryCount; i++) { if (entries[i].FileName == KeyFileName) { fileKey = MersenneTwister.GenRand(entries[i].Length); decrypt = true; blowfish = CatDebug.NewBlowfish(fileKey); keyIndex = i; break; } } // This archive is already decrypted, return and let the calling method know if (!decrypt) { return(false); } if (isBackup) { using (Stream outStream = File.Create(kifintPath)) DecryptArchiveSave(hdr, entries, tocSeed, fileKey, blowfish, keyIndex, inStream, outStream, progress, callback); } else { if (File.Exists(kifintBackup)) { File.Delete(kifintBackup); } inStream.Close(); File.Move(kifintPath, kifintBackup); using (inStream = File.OpenRead(kifintBackup)) using (Stream outStream = File.Create(kifintPath)) DecryptArchiveSave(hdr, entries, tocSeed, fileKey, blowfish, keyIndex, inStream, outStream, progress, callback); } return(true); }
private static void Run(Stream stream, string intFile, string exeFile, string outputDir, ExkifintCallback progress = null) { Stopwatch watch = Stopwatch.StartNew(); DateTime startTime = DateTime.UtcNow; string gameId = FindVCode2(exeFile); BinaryReader reader = new BinaryReader(stream); KIFHDR hdr = reader.ReadStruct <KIFHDR>(); if (hdr.Signature != "KIF") // It's really a KIF INT file { throw new InvalidFileException(Path.GetFileName(intFile), "INT"); } KIFENTRY[] entries = reader.ReadStructArray <KIFENTRY>(hdr.EntryCount); uint tocSeed = GenTocSeed(gameId); uint fileKey = 0; bool decrypt = false; /*const string fileName = "bom_s.hg3"; * const uint offset = 1112718577; * const int length = 1907629000; * const uint fileKey2 = 1457527205; * KIFENTRY kifEntry = new KIFENTRY { * FileNameRaw = new char[64], * Offset = offset, * Length = length, * }; * Array.Copy(fileName.ToCharArray(), kifEntry.FileNameRaw, fileName.Length); * DecryptEntry(ref kifEntry, fileKey2);*/ ExkifintArgs args = new ExkifintArgs(); for (int i = 0; i < hdr.EntryCount; i++) { if (entries[i].FileName == "__key__.dat") { if (!decrypt) { //MersenneTwister.Seed(entries[i].Length); //fileKey = MersenneTwister.GenRand(); fileKey = MersenneTwister.GenRand(entries[i].Length); decrypt = true; } } else { args.FileCount++; } } DateTime lastRefresh = DateTime.MinValue; Stopwatch writeTime = new Stopwatch(); TimeSpan refreshTime = TimeSpan.FromMilliseconds(20); //Stopwatch processTime = new Stopwatch(); for (uint i = 0; i < hdr.EntryCount; i++) { if (entries[i].FileName == "__key__.dat") { continue; } if (decrypt) { UnobfuscateFileName(entries[i].FileNameRaw, tocSeed + i); //UnobfuscateFileName(ref entries[i].FileName, tocSeed + i); entries[i].Offset += i; DecryptEntry(ref entries[i].Info, fileKey); /*Blowfish bf = new Blowfish(); * bf.Set_Key((byte*) &file_key, 4); * byte[] entry_buff = entries[i].bytes; * bf.Decrypt(entry_buff, 8); * entries[i].bytes = entry_buff;*/ } args.Ellapsed = DateTime.UtcNow - startTime; // Round to nearest hundredth args.Percent = Math.Round((double)args.FileIndex / args.FileCount * 10000) / 100; args.FileName = entries[i].FileName; TimeSpan sinceRefresh = DateTime.UtcNow - lastRefresh; if (sinceRefresh >= refreshTime) { lastRefresh = DateTime.UtcNow; writeTime.Start(); progress?.Invoke(args); writeTime.Stop(); } //processTime.Restart(); stream.Position = entries[i].Offset; byte[] buffer = reader.ReadBytes(entries[i].Length); if (decrypt) { DecryptData(buffer, entries[i].Length, fileKey); /*Blowfish bf = new Blowfish(); * bf.Set_Key((byte*)&file_key, 4); * bf.Decrypt(buff, (len / 8) * 8);*/ } string path = Path.Combine(outputDir, entries[i].FileName); File.WriteAllBytes(path, buffer); args.FileIndex++; //processTime.Stop(); //if (processTime.ElapsedMilliseconds >= 500) // Trace.WriteLine($"Large File: {buffer.Length / 1024:###,###,###,###}KB [{processTime.ElapsedMilliseconds}ms]"); } args.Ellapsed = DateTime.UtcNow - startTime; args.Percent = 100.0; progress?.Invoke(args); Trace.WriteLine($"Console Write Time: {writeTime.Elapsed:mm\\:ss\\.fff}"); }
public static void GH() { ulong[] arr = { 0x12345, 0x23456, 0x34567, 0x45678 }; MersenneTwister mt = new MersenneTwister(arr); for (int i = 0; i < 5; i++) { Console.Write(String.Format("{0:d20} ", mt.GenRand())); } Console.WriteLine(); }