Instantly share code, notes, and snippets.
Last active
September 30, 2022 13:27
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save barncastle/3c96cec175bb65c2381a7887272614b9 to your computer and use it in GitHub Desktop.
Code for decrypting Cookie Run Kakao DBJ files
This file contains 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
using System; | |
using System.IO; | |
using System.Runtime.InteropServices; | |
using System.Security.Cryptography; | |
namespace ConsoleAppFramework | |
{ | |
public unsafe class CookieRunDJBF | |
{ | |
private static readonly byte[] Key = new byte[] | |
{ | |
0xC0, 0x01, 0xC1, 0xE1, 0x26, 0x11, 0x10, 0xDA, | |
0x90, 0x90, 0x35, 0x81, 0xFE, 0xBA, 0xA9, 0x7F, | |
0xA1, 0x45, 0x1C, 0x4F, 0x97, 0x88, 0x71, 0xFA, | |
0xC3, 0xF1, 0xF8, 0x29, 0x3D, 0xDE, 0xE2, 0xB3 | |
}; | |
private static readonly byte[] IV = new byte[] | |
{ | |
0x58, 0xA8, 0xB9, 0xDD, 0x13, 0x61, 0x62, 0xAA, | |
0x99, 0x88, 0x7A, 0x1F, 0xF2, 0x3F, 0x7C, 0x91 | |
}; | |
public static byte[] Decrypt(string path) | |
{ | |
using (var fs = File.OpenRead(path)) | |
{ | |
var header = fs.Read<Header>(); | |
// swap endian | |
header.Version = (ushort)((header.Version >> 8) | ((header.Version & 0xFF) << 8)); | |
// flag fixes for various versions | |
if (header.Version < 0x0101) | |
header.Flags &= ~Flags.FastLZ; | |
if (header.Version < 0x0102) | |
header.Flags &= ~Flags.AES_CBC; | |
// ver 0x0100 files have bad data if 128 bit aligned | |
if (header.DataSuffixSize > 0xF) | |
header.DataSuffixSize = 0; | |
// copy out the remaining data and append the suffix bytes | |
var dataSize = (int)(fs.Length - fs.Position); | |
var buffer = new byte[dataSize + header.DataSuffixSize]; | |
fs.Read(buffer, 0, dataSize); | |
Array.Copy(header.DataSuffix, 0, buffer, dataSize, header.DataSuffixSize); | |
// AES decrypt | |
if ((header.Flags & (Flags.AES_CBC | Flags.AES_ECB)) != 0) | |
buffer = AESDecrypt(header, buffer); | |
// FastLZ decompress | |
if (header.Flags.HasFlag(Flags.FastLZ)) | |
buffer = FastLZDecompress(header, buffer); | |
var checksum = CRCUnsafe.Crc32(buffer); | |
if (checksum != header.Checksum) | |
throw new Exception($"Checksum :: {checksum:X8} != {header.Checksum:X8}"); | |
return buffer; | |
} | |
} | |
private static byte[] AESDecrypt(Header header, byte[] data) | |
{ | |
// create AES instance with salted IV | |
var aes = new RijndaelManaged() | |
{ | |
Key = Key, | |
IV = CreateIV(header.Checksum), | |
Padding = PaddingMode.None | |
}; | |
// set mode and created decryptor | |
ICryptoTransform decryptor; | |
if (header.Flags.HasFlag(Flags.AES_ECB)) | |
{ | |
aes.Mode = CipherMode.ECB; | |
decryptor = aes.CreateDecryptor(aes.Key, null); | |
} | |
else | |
{ | |
aes.Mode = CipherMode.CBC; | |
decryptor = aes.CreateDecryptor(aes.Key, aes.IV); | |
} | |
// decrypt | |
using (var msIn = new MemoryStream(data)) | |
using (var msOut = new MemoryStream()) | |
using (var cs = new CryptoStream(msIn, decryptor, CryptoStreamMode.Read)) | |
{ | |
cs.CopyTo(msOut); | |
// truncate padding | |
if (msOut.Length > header.DataSizeLo) | |
msOut.SetLength(header.DataSizeLo); | |
return msOut.ToArray(); | |
} | |
} | |
private static byte[] FastLZDecompress(Header header, byte[] data) | |
{ | |
var buffer = new byte[header.DataSizeLo]; | |
fixed (byte* input = &data[0]) | |
fixed (byte* output = &buffer[0]) | |
{ | |
var read = FastLZ_Decompress(input, data.Length, output, buffer.Length); | |
if (read != header.DataSizeLo) | |
throw new Exception($"Fast_LZ : {read} != {header.DataSizeLo}"); | |
return buffer; | |
} | |
} | |
/// <summary> | |
/// <see cref="IV"/> is salted by adding the first byte | |
/// of the checksum to all values | |
/// </summary> | |
private static byte[] CreateIV(uint checksum) | |
{ | |
var result = new byte[16]; | |
var salt = checksum & 0xFF; | |
for (var i = 0; i < IV.Length; i++) | |
result[i] = (byte)((IV[i] + salt) & 0xFF); | |
return result; | |
} | |
/// <summary> | |
/// Custom implementation that supports both levels | |
/// <para>https://github.com/ariya/FastLZ</para> | |
/// </summary> | |
private static int FastLZ_Decompress(byte* input, int length, byte* output, int maxout) | |
{ | |
const uint MAX_L2_DISTANCE = 8191; | |
// magic identifier for compression level | |
int level = ((*input) >> 5) + 1; | |
byte* ip = input; | |
byte* ip_limit = ip + length; | |
byte* ip_bound = ip_limit - 2; | |
byte* op = output; | |
byte* op_limit = op + maxout; | |
uint ctrl = (uint)*ip++ & 31; | |
while (true) | |
{ | |
if (ctrl >= 32) | |
{ | |
uint len = (ctrl >> 5) - 1; | |
uint ofs = (ctrl & 31) << 8; | |
byte* ref_ = op - ofs - 1; | |
byte code; | |
if (len == 7 - 1) | |
{ | |
do | |
{ | |
if (ip > ip_bound) | |
break; | |
code = *ip++; | |
len += code; | |
} | |
while (level == 2 && code == 255); | |
} | |
code = *ip++; | |
ref_ -= code; | |
len += 3; | |
// match from 16-bit distance | |
if (level == 2 && code == 255) | |
{ | |
if (ofs == (31 << 8)) | |
{ | |
if (ip > ip_bound) | |
break; | |
ofs = (uint)*ip++ << 8; | |
ofs += *ip++; | |
ref_ = op - ofs - MAX_L2_DISTANCE - 1; | |
} | |
} | |
if (op + len > op_limit) | |
break; | |
if (ref_ < output) | |
break; | |
FastLZ_MemMove(op, ref_, len); | |
op += len; | |
} | |
else | |
{ | |
ctrl++; | |
if (op + ctrl > op_limit) | |
break; | |
if (ip + ctrl > ip_limit) | |
break; | |
FastLZ_MemMove(op, ip, ctrl); | |
ip += ctrl; | |
op += ctrl; | |
} | |
if (level == 2 && ip >= ip_limit) | |
break; | |
if (level == 1 && ip > ip_bound) | |
break; | |
ctrl = *ip++; | |
} | |
return (int)(op - output); | |
} | |
private static void FastLZ_MemMove(byte* dest, byte* src, uint count) | |
{ | |
do *dest++ = *src++; | |
while (--count != 0); | |
} | |
[StructLayout(LayoutKind.Sequential, Pack = 1)] | |
public struct Header | |
{ | |
public uint Magic; | |
public ushort Version; // BE | |
public ushort Reserved; | |
public uint Checksum; | |
public int DataSizeLo; | |
public int DataSizeHi; | |
public Flags Flags; | |
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)] | |
public byte[] DataSuffix; | |
public byte DataSuffixSize; | |
} | |
[Flags] | |
public enum Flags : byte | |
{ | |
AES_ECB = 0x1, | |
AES_CBC = 0x2, // ≥ 0x0102 | |
FastLZ = 0x80, // ≥ 0x0101 | |
} | |
} | |
} |
This file contains 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
DJBF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I have another project that should handle this here. There's a download under the Releases section.