diff --git a/src/decrypt/qmc_key.test.ts b/src/decrypt/qmc_key.test.ts new file mode 100644 index 0000000..4584f9e --- /dev/null +++ b/src/decrypt/qmc_key.test.ts @@ -0,0 +1,30 @@ +import {DecryptKey, simpleMakeKey,} from "@/decrypt/qmc_key"; +import fs from "fs"; + +test("key dec: make simple key", () => { + expect( + simpleMakeKey(106, 8) + ).toStrictEqual( + [0x69, 0x56, 0x46, 0x38, 0x2b, 0x20, 0x15, 0x0b] + ) +}) + +function loadTestDataKeyDecrypt(name: string): { + cipherText: Uint8Array, + clearText: Uint8Array +} { + return { + cipherText: fs.readFileSync(`testdata/${name}_key_raw.bin`), + clearText: fs.readFileSync(`testdata/${name}_key.bin`) + } +} + +test("key dec: real file", async () => { + const cases = ["mflac_map", "mgg_map", "mflac0_rc4"] + for (const name of cases) { + const {clearText, cipherText} = loadTestDataKeyDecrypt(name) + const buf = DecryptKey(cipherText) + + expect(buf).toStrictEqual(clearText) + } +}) diff --git a/src/decrypt/qmc_key.ts b/src/decrypt/qmc_key.ts new file mode 100644 index 0000000..ba487ba --- /dev/null +++ b/src/decrypt/qmc_key.ts @@ -0,0 +1,103 @@ +import {TeaCipher} from "@/utils/tea"; + +export function simpleMakeKey(salt: number, length: number): number[] { + const keyBuf: number[] = [] + for (let i = 0; i < length; i++) { + const tmp = Math.tan(salt + i * 0.1) + keyBuf[i] = 0xff & (Math.abs(tmp) * 100.0) + } + return keyBuf +} + +const SALT_LEN = 2 +const ZERO_LEN = 7 + +function decryptTencentTea(inBuf: Uint8Array, key: Uint8Array): Uint8Array { + if (inBuf.length % 8 != 0) { + throw Error("inBuf size not a multiple of the block size") + } + if (inBuf.length < 16) { + throw Error("inBuf size too small") + } + + const blk = new TeaCipher(key, 32) + + const tmpBuf = new Uint8Array(8); + const tmpView = new DataView(tmpBuf.buffer); + + blk.decrypt(tmpView, new DataView(inBuf.buffer, inBuf.byteOffset, 8)) + + const nPadLen = tmpBuf[0] & 0x7;//只要最低三位 + /*密文格式:PadLen(1byte)+Padding(var,0-7byte)+Salt(2byte)+Body(var byte)+Zero(7byte)*/ + const outLen = inBuf.length - 1 /*PadLen*/ - nPadLen - SALT_LEN - ZERO_LEN; + const outBuf = new Uint8Array(outLen) + + let ivPrev = new Uint8Array(8); + let ivCur = inBuf.slice(0, 8); // init iv + let inBufPos = 8; + + + // 跳过 Padding Len 和 Padding + let tmpIdx = 1 + nPadLen; + + // CBC IV 处理 + const cryptBlock = () => { + ivPrev = ivCur; + ivCur = inBuf.slice(inBufPos, inBufPos + 8) + for (let j = 0; j < 8; j++) { + tmpBuf[j] ^= ivCur[j] + } + blk.decrypt(tmpView, tmpView) + inBufPos += 8; + tmpIdx = 0; + } + + // 跳过 Salt + for (let i = 1; i <= SALT_LEN;) { + if (tmpIdx < 8) { + tmpIdx++; + i++; + } else { + cryptBlock() + } + } + + // 还原明文 + let outBufPos = 0; + while (outBufPos < outLen) { + if (tmpIdx < 8) { + outBuf[outBufPos] = tmpBuf[tmpIdx] ^ ivPrev[tmpIdx]; + outBufPos++ + tmpIdx++; + } else { + cryptBlock() + } + } + + // 校验Zero + for (let i = 1; i <= ZERO_LEN; i++) { + if (tmpBuf[tmpIdx] != ivPrev[tmpIdx]) { + throw Error("zero check failed") + } + } + return outBuf +} + +export function DecryptKey(raw: Uint8Array): Uint8Array { + const rawDec = Buffer.from(raw.toString(), 'base64') + let n = rawDec.length; + if (n < 16) { + throw Error("key length is too short") + } + + const simpleKey = simpleMakeKey(106, 8) + let teaKey = new Uint8Array(16); + for (let i = 0; i < 8; i++) { + teaKey[i << 1] = simpleKey[i]; + teaKey[(i << 1) + 1] = rawDec[i]; + } + const sub = decryptTencentTea(rawDec.subarray(8), teaKey) + rawDec.set(sub, 8) + return rawDec.subarray(0, 8 + sub.length) + +} diff --git a/testdata/mflac0_rc4_key.bin b/testdata/mflac0_rc4_key.bin new file mode 100644 index 0000000..3bd0914 --- /dev/null +++ b/testdata/mflac0_rc4_key.bin @@ -0,0 +1 @@ +dRzX3p5ZYqAlp7lLSs9Zr0rw1iEZy23bB670x4ch2w97x14Zwpk1UXbKU4C2sOS7uZ0NB5QM7ve9GnSrr2JHxP74hVNONwVV77CdOOVb807317KvtI5Yd6h08d0c5W88rdV46C235YGDjUSZj5314YTzy0b6vgh4102P7E273r911Nl464XV83Hr00rkAHkk791iMGSJH95GztN28u2Nv5s9Xx38V69o4a8aIXxbx0g1EM0623OEtbtO9zsqCJfj6MhU7T8iVS6M3q19xhq6707E6r7wzPO6Yp4BwBmgg4F95Lfl0vyF7YO6699tb5LMnr7iFx29o98hoh3O3Rd8h9Juu8P1wG7vdnO5YtRlykhUluYQblNn7XwjBJ53HAyKVraWN5dG7pv7OMl1s0RykPh0p23qfYzAAMkZ1M422pEd07TA9OCKD1iybYxWH06xj6A8mzmcnYGT9P1a5Ytg2EF5LG3IknL2r3AUz99Y751au6Cr401mfAWK68WyEBe5 \ No newline at end of file diff --git a/testdata/mflac0_rc4_key_raw.bin b/testdata/mflac0_rc4_key_raw.bin new file mode 100644 index 0000000..39c8a3b --- /dev/null +++ b/testdata/mflac0_rc4_key_raw.bin @@ -0,0 +1 @@ +ZFJ6WDNwNVrjEJZB1o6QjkQV2ZbHSw/2Eb00q1+4z9SVWYyFWO1PcSQrJ5326ubLklmk2ab3AEyIKNUu8DFoAoAc9dpzpTmc+pdkBHjM/bW2jWx+dCyC8vMTHE+DHwaK14UEEGW47ZXMDi7PRCQ2Jpm/oXVdHTIlyrc+bRmKfMith0L2lFQ+nW8CCjV6ao5ydwkZhhNOmRdrCDcUXSJH9PveYwra9/wAmGKWSs9nemuMWKnbjp1PkcxNQexicirVTlLX7PVgRyFyzNyUXgu+R2S4WTmLwjd8UsOyW/dc2mEoYt+vY2lq1X4hFBtcQGOAZDeC+mxrN0EcW8tjS6P4TjOjiOKNMxIfMGSWkSKL3H7z5K7nR1AThW20H2bP/LcpsdaL0uZ/js1wFGpdIfFx9rnLC78itL0WwDleIqp9TBMX/NwakGgIPIbjBwfgyD8d8XKYuLEscIH0ZGdjsadB5XjybgdE3ppfeFEcQiqpnodlTaQRm3KDIF9ATClP0mTl8XlsSojsZ468xseS1Ib2iinx/0SkK3UtJDwp8DH3/+ELisgXd69Bf0pve7wbrQzzMUs9/Ogvvo6ULsIkQfApJ8cSegDYklzGXiLNH7hZYnXDLLSNejD7NvQouULSmGsBbGzhZ5If0NP/6AhSbpzqWLDlabTDgeWWnFeZpBnlK6SMxo+YFFk1Y0XLKsd69+jj \ No newline at end of file diff --git a/testdata/mflac0_rc4_raw.bin b/testdata/mflac0_rc4_raw.bin new file mode 100644 index 0000000..fd7e4af Binary files /dev/null and b/testdata/mflac0_rc4_raw.bin differ diff --git a/testdata/mflac0_rc4_suffix.bin b/testdata/mflac0_rc4_suffix.bin new file mode 100644 index 0000000..63a168a Binary files /dev/null and b/testdata/mflac0_rc4_suffix.bin differ diff --git a/testdata/mflac0_rc4_target.bin b/testdata/mflac0_rc4_target.bin new file mode 100644 index 0000000..a7f86c7 Binary files /dev/null and b/testdata/mflac0_rc4_target.bin differ diff --git a/testdata/mflac_map_key.bin b/testdata/mflac_map_key.bin index e69de29..7fd7a72 100644 --- a/testdata/mflac_map_key.bin +++ b/testdata/mflac_map_key.bin @@ -0,0 +1 @@ +yw7xWOyNQ8585Jwx3hjB49QLPKi38F89awnrQ0fq66NT9TDq1ppHNrFqhaDrU5AFk916D58I53h86304GqOFCCyFzBem68DqiXJ81bILEQwG3P3MOnoNzM820kNW9Lv9IJGNn9Xo497p82BLTm4hAX8JLBs0T2pilKvT429sK9jfg508GSk4d047Jxdz5Fou4aa33OkyFRBU3x430mgNBn04Lc9BzXUI2IGYXv3FGa9qE4Vb54kSjVv8ogbg47j3 \ No newline at end of file diff --git a/testdata/mflac_map_key_raw.bin b/testdata/mflac_map_key_raw.bin index e69de29..f088613 100644 --- a/testdata/mflac_map_key_raw.bin +++ b/testdata/mflac_map_key_raw.bin @@ -0,0 +1 @@ +eXc3eFdPeU6+3f7GVeF35bMpIEIQj5JWOWt7G+jsR68Hx3BUFBavkTQ8dpPdP0XBIwPe+OfdsnTGVQqPyg3GCtQSrkgA0mwSQdr4DPzKLkEZFX+Cf1V6ChyipOuC6KT37eAxWMdV1UHf9/OCvydr1dc6SWK1ijRUcP6IAHQhiB+mZLay7XXrSPo32WjdBkn9c9sa2SLtI48atj5kfZ4oOq6QGeld2JA3Z+3wwCe6uTHthKaEHY8ufDYodEe3qqrjYpzkdx55pCtxCQa1JiNqFmJigWm4m3CDzhuJ7YqnjbD+mXxLi7BP1+z4L6nccE2h+DGHVqpGjR9+4LBpe4WHB4DrAzVp2qQRRQJxeHd1v88= \ No newline at end of file diff --git a/testdata/mgg_map_key.bin b/testdata/mgg_map_key.bin index e69de29..fecf089 100644 --- a/testdata/mgg_map_key.bin +++ b/testdata/mgg_map_key.bin @@ -0,0 +1 @@ +zGxNk54pKJ0hDkAo80wHE80ycSWQ7z4m4E846zVy2sqCn14F42Y5S7GqeR11WpOV75sDLbE5dFP992t88l0pHy1yAQ49YK6YX6c543drBYLo55Hc4Y0Fyic6LQPiGqu2bG31r8vaq9wS9v63kg0X5VbnOD6RhO4t0RRhk3ajrA7p0iIy027z0L70LZjtw6E18H0D41nz6ASTx71otdF9z1QNC0JmCl51xvnb39zPExEXyKkV47S6QsK5hFh884QJ \ No newline at end of file diff --git a/testdata/mgg_map_key_raw.bin b/testdata/mgg_map_key_raw.bin index e69de29..bea6675 100644 --- a/testdata/mgg_map_key_raw.bin +++ b/testdata/mgg_map_key_raw.bin @@ -0,0 +1 @@ +ekd4Tms1NHC53JEDO/AKVyF+I0bj0hHB7CZeoLDGSApaQB9Oo/pJTBGA/RO+nk5RXLXdHsffLiY4e8kt3LNo6qMl7S89vkiSFxx4Uoq4bGDJ7Jc+bYL6lLsa3M4sBvXS4XcPChrMDz+LmrJMGG6ua2fYyIz1d6TCRUBf1JJgCIkBbDAEeMVYc13qApitiz/apGAPmAnveCaDhfD5GxWsF+RfQ2OcnvrnIXe80Feh/0jx763DlsOBI3eIede6t5zYHokWkZmVEF1jMrnlvsgbQK2EzUWMblmLMsTKNILyZazEoKUyulqmyLO/c/KYE+USPOXPcbjlYFmLhSGHK7sQB5aBR153Yp+xh61ooh2NGAA= \ No newline at end of file