Skip to content

Instantly share code, notes, and snippets.

@barncastle
Last active September 30, 2022 13:27
Show Gist options
  • Save barncastle/3c96cec175bb65c2381a7887272614b9 to your computer and use it in GitHub Desktop.
Save barncastle/3c96cec175bb65c2381a7887272614b9 to your computer and use it in GitHub Desktop.
Code for decrypting Cookie Run Kakao DBJ files
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
}
}
}
@barncastle
Copy link
Author

I have another project that should handle this here. There's a download under the Releases section.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment