Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@esterTion
Created November 15, 2022 11:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save esterTion/c624b7e985f2f7102226d7e8b6afaf08 to your computer and use it in GitHub Desktop.
Save esterTion/c624b7e985f2f7102226d7e8b6afaf08 to your computer and use it in GitHub Desktop.
<?php
/*
A network packet decoder for Danmaku Kagura (東方ダンマクカグラ)
RIP dankagu 2021.8.4-2022.10.28
*/
class DankaguNetEnc {
private $key;
private $keyUint;
private $round;
function __construct(string $key, string $nonce) {
if (strlen($key) !== 32) throw new Exception('Invalid key');
if (strlen($nonce) !== 12) throw new Exception('Invalid nonce');
$this->key = "zG]RTQVC{zkpV]/T" . $key . "\0\0\0\0" . $nonce;
$this->keyUint = array_values(unpack('V*', $this->key));
$this->round = $this->calcRound();
}
function calcRound() {
$roundGap = "1341133243221442";
$idx = (($this->keyUint[ 9] + $this->keyUint[ 4]) ^ $this->keyUint[0xb])
+ (($this->keyUint[0xe] + $this->keyUint[0xd]) ^ $this->keyUint[0xf]);
return $roundGap[($idx >> 7) & 2 | ($idx >> 2) & 1 | ($idx >> 13) & 4 | ($idx >> 2) & 8];
}
function crypt($data) {
$out = str_repeat("\0", strlen($data));
$round = 10 - $this->round;
$offset = 0;
$blockNo = 0;
for ($remaining = strlen($data); $remaining > 0; $remaining -= $blockSize, $offset += $blockSize, $blockNo++) {
$blockSize = $remaining >= 64 ? 64 : $remaining;
$this->keyUint[12] = $blockNo;
$cipherState = array_values($this->keyUint);
// contains & 0xffffffff to force uint32
for ($i=0; $i<$round; $i++) {
$cipherState[0] = ($cipherState[0] + $cipherState[4]) & 0xffffffff;
$val = $cipherState[12] ^ $cipherState[0]; $val = ($val << 32) | $val;
$cipherState[12] = ($val >> 16) & 0xffffffff;
$cipherState[8] = ($cipherState[8] + $cipherState[12]) & 0xffffffff;
$val = $cipherState[4] ^ $cipherState[8]; $val = ($val << 32) | $val;
$cipherState[4] = ($val >> 20) & 0xffffffff;
$cipherState[0] = ($cipherState[0] + $cipherState[4]) & 0xffffffff;
$val = $cipherState[12] ^ $cipherState[0]; $val = ($val << 32) | $val;
$cipherState[12] = ($val >> 24) & 0xffffffff;
$cipherState[8] = ($cipherState[8] + $cipherState[12]) & 0xffffffff;
$val = $cipherState[4] ^ $cipherState[8]; $val = ($val << 32) | $val;
$cipherState[4] = ($val >> 25) & 0xffffffff;
$cipherState[1] = ($cipherState[1] + $cipherState[5]) & 0xffffffff;
$val = $cipherState[13] ^ $cipherState[1]; $val = ($val << 32) | $val;
$cipherState[13] = ($val >> 16) & 0xffffffff;
$cipherState[9] = ($cipherState[9] + $cipherState[13]) & 0xffffffff;
$val = $cipherState[5] ^ $cipherState[9]; $val = ($val << 32) | $val;
$cipherState[5] = ($val >> 20) & 0xffffffff;
$cipherState[1] = ($cipherState[1] + $cipherState[5]) & 0xffffffff;
$val = $cipherState[13] ^ $cipherState[1]; $val = ($val << 32) | $val;
$cipherState[13] = ($val >> 24) & 0xffffffff;
$cipherState[9] = ($cipherState[9] + $cipherState[13]) & 0xffffffff;
$val = $cipherState[5] ^ $cipherState[9]; $val = ($val << 32) | $val;
$cipherState[5] = ($val >> 25) & 0xffffffff;
$cipherState[2] = ($cipherState[2] + $cipherState[6]) & 0xffffffff;
$val = $cipherState[14] ^ $cipherState[2]; $val = ($val << 32) | $val;
$cipherState[14] = ($val >> 16) & 0xffffffff;
$cipherState[10] = ($cipherState[10] + $cipherState[14]) & 0xffffffff;
$val = $cipherState[6] ^ $cipherState[10]; $val = ($val << 32) | $val;
$cipherState[6] = ($val >> 20) & 0xffffffff;
$cipherState[2] = ($cipherState[2] + $cipherState[6]) & 0xffffffff;
$val = $cipherState[14] ^ $cipherState[2]; $val = ($val << 32) | $val;
$cipherState[14] = ($val >> 24) & 0xffffffff;
$cipherState[10] = ($cipherState[10] + $cipherState[14]) & 0xffffffff;
$val = $cipherState[6] ^ $cipherState[10]; $val = ($val << 32) | $val;
$cipherState[6] = ($val >> 25) & 0xffffffff;
$cipherState[3] = ($cipherState[3] + $cipherState[7]) & 0xffffffff;
$val = $cipherState[15] ^ $cipherState[3]; $val = ($val << 32) | $val;
$cipherState[15] = ($val >> 16) & 0xffffffff;
$cipherState[11] = ($cipherState[11] + $cipherState[15]) & 0xffffffff;
$val = $cipherState[7] ^ $cipherState[11]; $val = ($val << 32) | $val;
$cipherState[7] = ($val >> 20) & 0xffffffff;
$cipherState[3] = ($cipherState[3] + $cipherState[7]) & 0xffffffff;
$val = $cipherState[15] ^ $cipherState[3]; $val = ($val << 32) | $val;
$cipherState[15] = ($val >> 24) & 0xffffffff;
$cipherState[11] = ($cipherState[11] + $cipherState[15]) & 0xffffffff;
$val = $cipherState[7] ^ $cipherState[11]; $val = ($val << 32) | $val;
$cipherState[7] = ($val >> 25) & 0xffffffff;
$cipherState[0] = ($cipherState[0] + $cipherState[5]) & 0xffffffff;
$val = $cipherState[15] ^ $cipherState[0]; $val = ($val << 32) | $val;
$cipherState[15] = ($val >> 16) & 0xffffffff;
$cipherState[10] = ($cipherState[10] + $cipherState[15]) & 0xffffffff;
$val = $cipherState[5] ^ $cipherState[10]; $val = ($val << 32) | $val;
$cipherState[5] = ($val >> 20) & 0xffffffff;
$cipherState[0] = ($cipherState[0] + $cipherState[5]) & 0xffffffff;
$val = $cipherState[15] ^ $cipherState[0]; $val = ($val << 32) | $val;
$cipherState[15] = ($val >> 24) & 0xffffffff;
$cipherState[10] = ($cipherState[10] + $cipherState[15]) & 0xffffffff;
$val = $cipherState[5] ^ $cipherState[10]; $val = ($val << 32) | $val;
$cipherState[5] = ($val >> 25) & 0xffffffff;
$cipherState[1] = ($cipherState[1] + $cipherState[6]) & 0xffffffff;
$val = $cipherState[12] ^ $cipherState[1]; $val = ($val << 32) | $val;
$cipherState[12] = ($val >> 16) & 0xffffffff;
$cipherState[11] = ($cipherState[11] + $cipherState[12]) & 0xffffffff;
$val = $cipherState[6] ^ $cipherState[11]; $val = ($val << 32) | $val;
$cipherState[6] = ($val >> 20) & 0xffffffff;
$cipherState[1] = ($cipherState[1] + $cipherState[6]) & 0xffffffff;
$val = $cipherState[12] ^ $cipherState[1]; $val = ($val << 32) | $val;
$cipherState[12] = ($val >> 24) & 0xffffffff;
$cipherState[11] = ($cipherState[11] + $cipherState[12]) & 0xffffffff;
$val = $cipherState[6] ^ $cipherState[11]; $val = ($val << 32) | $val;
$cipherState[6] = ($val >> 25) & 0xffffffff;
$cipherState[2] = ($cipherState[2] + $cipherState[7]) & 0xffffffff;
$val = $cipherState[13] ^ $cipherState[2]; $val = ($val << 32) | $val;
$cipherState[13] = ($val >> 16) & 0xffffffff;
$cipherState[8] = ($cipherState[8] + $cipherState[13]) & 0xffffffff;
$val = $cipherState[7] ^ $cipherState[8]; $val = ($val << 32) | $val;
$cipherState[7] = ($val >> 20) & 0xffffffff;
$cipherState[2] = ($cipherState[2] + $cipherState[7]) & 0xffffffff;
$val = $cipherState[13] ^ $cipherState[2]; $val = ($val << 32) | $val;
$cipherState[13] = ($val >> 24) & 0xffffffff;
$cipherState[8] = ($cipherState[8] + $cipherState[13]) & 0xffffffff;
$val = $cipherState[7] ^ $cipherState[8]; $val = ($val << 32) | $val;
$cipherState[7] = ($val >> 25) & 0xffffffff;
$cipherState[3] = ($cipherState[3] + $cipherState[4]) & 0xffffffff;
$val = $cipherState[14] ^ $cipherState[3]; $val = ($val << 32) | $val;
$cipherState[14] = ($val >> 16) & 0xffffffff;
$cipherState[9] = ($cipherState[9] + $cipherState[14]) & 0xffffffff;
$val = $cipherState[4] ^ $cipherState[9]; $val = ($val << 32) | $val;
$cipherState[4] = ($val >> 20) & 0xffffffff;
$cipherState[3] = ($cipherState[3] + $cipherState[4]) & 0xffffffff;
$val = $cipherState[14] ^ $cipherState[3]; $val = ($val << 32) | $val;
$cipherState[14] = ($val >> 24) & 0xffffffff;
$cipherState[9] = ($cipherState[9] + $cipherState[14]) & 0xffffffff;
$val = $cipherState[4] ^ $cipherState[9]; $val = ($val << 32) | $val;
$cipherState[4] = ($val >> 25) & 0xffffffff;
}
for ($i=0; $i<16; $i++) {
$cipherState[$i] = ($cipherState[$i] + $this->keyUint[$i]) & 0xffffffff;
}
if ($remaining > 0) {
$cipherStateChar = implode('', array_map(function ($i) { return pack('V', $i); }, $cipherState));
for ($i=0; $i < $blockSize; $i++) {
$out[$offset + $i] = $data[$offset + $i] ^ $cipherStateChar[$i];
}
}
}
return $out;
}
}
class DankaguNetPacket {
private static $commonKey;
static function initialize() {
$key = hash('sha256', "\x23\xDB\x91\xFF\xE9\xAA\xAF\x5D\x6C\x76\x64\x7F\xED\x65\x79\x7E", true);
$nonce = hash('sha256', "\x99\x48\x85\xA2\xB7\x1F\xA5\x7C\xAD\xCD\x12\xA6\x3E\xDA\x75\x18", true);
$dne = new DankaguNetEnc($key, substr($nonce, 0, 12));
DankaguNetPacket::$commonKey = $dne->crypt(base64_decode('D8jVj0AeZ4qx04o5EEVHBvNubk4ll/BxOuTTyxZQJ1U='));
}
static function unpack($message) {
$nonce = substr($message, 0, 12);
$dne = new DankaguNetEnc(DankaguNetPacket::$commonKey, $nonce);
$decrypted = $dne->crypt(substr($message, 12));
// php's usage of zlib does not allow streaming deflate data, so we add an empty "final" block
$decompressed = gzinflate($decrypted.hex2bin('010000ffff'));
$hash = substr($decompressed, 0, 32);
$body = substr($decompressed, 32);
$realHash = hash_hmac('sha256', $body, DankaguNetPacket::$commonKey . $nonce, true);
if ($realHash !== $hash) {
throw new Exception('Data corrupted');
}
return $body;
}
}
//var_dump(bin2hex($commonKey));
//echo $commonKey;
function usageExample() {
DankaguNetPacket::initialize();
$message = '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';
$message = DankaguNetPacket::unpack(base64_decode($message));
file_put_contents('test.dat', $message);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment