From 040887e9df372a9d2f99a350f87c52ba1ad81b77 Mon Sep 17 00:00:00 2001 From: extremeheat Date: Sat, 30 Jan 2021 02:36:34 -0500 Subject: [PATCH] fix zlib decryption issue --- data/newproto.json | 8 ++-- src/serverTest.js | 9 ++-- src/transforms/encryption.js | 91 +++++++++++++++++++++++++----------- 3 files changed, 72 insertions(+), 36 deletions(-) diff --git a/data/newproto.json b/data/newproto.json index 81d1621..2af7f0b 100644 --- a/data/newproto.json +++ b/data/newproto.json @@ -30,7 +30,7 @@ "BehaviourPackInfos": [ "array", { - "countType": "varint", + "countType": "li16", "type": [ "container", [ @@ -69,7 +69,7 @@ "TexturePackInfos": [ "array", { - "countType": "varint", + "countType": "li16", "type": [ "container", [ @@ -2402,11 +2402,11 @@ "type": "bool" }, { - "name": "behaviorpackidversions", + "name": "behavior_pack_id_versions", "type": "ResourcePackIdVersions" }, { - "name": "resourcepackidversions", + "name": "resource_pack_id_versions", "type": "ResourcePackIdVersions" }, { diff --git a/src/serverTest.js b/src/serverTest.js index eece259..226cd22 100644 --- a/src/serverTest.js +++ b/src/serverTest.js @@ -184,6 +184,7 @@ class Player extends EventEmitter { if (buffer[0] == 0xfe) { // wrapper if (this.encryptionEnabled) { + // console.log('READING ENCRYPTED PACKET', buffer) this.decrypt(buffer.slice(1)) } else { const stream = new BinaryStream(buffer) @@ -247,8 +248,7 @@ class Server extends EventEmitter { const buffer = encapsulated.buffer const client = this.clients[this.getAddrHash(inetAddr)] if (!client) { - console.warn('packet from unknown inet addr', inetAddr.address, inetAddr.port) - return + throw new Error(`packet from unknown inet addr: ${inetAddr.address}/${inetAddr.port}`) } client.handle(buffer) } @@ -273,9 +273,8 @@ let server = new Server({ }) server.create('0.0.0.0', 19130) -server.on('connect', (data) => { - // TODO: send other packets needed to login... - const client = data.client +server.on('connect', ({ client }) => { + /** @type {Player} */ client.on('join', () => { console.log('Client joined', client.getData()) diff --git a/src/transforms/encryption.js b/src/transforms/encryption.js index 92acd20..491fe09 100644 --- a/src/transforms/encryption.js +++ b/src/transforms/encryption.js @@ -72,9 +72,9 @@ function createEncryptor(client, iv) { // A packet is encrypted via AES256(plaintext + SHA256(send_counter + plaintext + secret_key)[0:8]). // The send counter is represented as a little-endian 64-bit long and incremented after each packet. - const encryptor = new Transform({ + const addChecksum = new Transform({ // append checksum transform(chunk, enc, cb) { - console.log('Encryptor called', chunk) + console.log('Encryptor: checking checksum', chunk) // Here we concat the payload + checksum before the encryption const packet = Buffer.concat([chunk, computeCheckSum(chunk, client.sendCounter, client.secretKeyBytes)]) client.sendCounter++ @@ -84,51 +84,74 @@ function createEncryptor(client, iv) { }) // https://stackoverflow.com/q/25971715/11173996 - // TODO: Fix deflate stream - for some reason using .pipe() doesn't work here - // so we deflate it outside the pipe - const compressor = Zlib.createDeflateRaw({ level: 7, chunkSize: 1024 * 1024 * 2, flush: Zlib.Z_SYNC_FLUSH }) - const writableStream = new PassThrough() + // TODO: Fix deflate stream - for some reason using .pipe() doesn't work using zlib.createDeflateRaw() + // so we define our own compressor transform + // const compressor = Zlib.createDeflateRaw({ level: 7, chunkSize: 1024 * 1024 * 2, flush: Zlib.Z_SYNC_FLUSH }) + const compressor = new Transform({ + transform(chunk, enc, cb) { + Zlib.deflateRaw(chunk, { level: 7 }, (err, res) => { + if (err) { + console.error(err) + throw new Error(`Failed to deflate stream`) + } + this.push(res) + cb() + }) + } + }) - writableStream - // .pipe(compressor) - .pipe(encryptor).pipe(client.cipher).on('data', client.onEncryptedPacket) + + const stream = new PassThrough() + + stream + .pipe(compressor) + .pipe(addChecksum).pipe(client.cipher).on('data', client.onEncryptedPacket) return (blob) => { - // console.log('ENCRYPTING') - Zlib.deflateRaw(blob, { level: 7 }, (err, res) => { - if (err) { - console.error(err) - throw new Error(`Failed to deflate stream`) - } - writableStream.write(res) - }) - // writableStream.write(blob) + stream.write(blob) } } + function createDecryptor(client, iv) { client.decipher = createDecipher(client.secretKeyBytes, iv) client.receiveCounter = client.receiveCounter || 0n - const decryptor = new Transform({ + const verifyChecksum = new Transform({ // verify checksum transform(chunk, encoding, cb) { - console.log('Got transform', chunk) + console.log('Decryptor: checking checksum', chunk) const packet = chunk.slice(0, chunk.length - 8); const checksum = chunk.slice(chunk.length - 8); const computedCheckSum = computeCheckSum(packet, client.receiveCounter, client.secretKeyBytes) console.assert(checksum.toString("hex") == computedCheckSum.toString("hex"), 'checksum mismatch') client.receiveCounter++ - if (checksum.toString("hex") == computedCheckSum.toString("hex")) this.push(packet) - else console.log('FAILED', checksum.toString("hex"), computedCheckSum.toString("hex")) - // else process.exit(`Checksum mismatch ${checksum.toString("hex")} - ${computedCheckSum.toString("hex")}`) // TODO: remove + if (checksum.toString("hex") == computedCheckSum.toString("hex")) { + this.push(packet) + } else { + throw Error(`Checksum mismatch ${checksum.toString("hex")} != ${computedCheckSum.toString("hex")}`) + } + // console.log('Calling cb') cb() } }) - client.decipher.pipe(decryptor) - .pipe(Zlib.createInflateRaw({ chunkSize: 1024 * 1024 * 2 })) - .on('data', client.onDecryptedPacket) - // .on('end', () => console.log('Finished!')) + const inflator = new Transform({ + transform(chunk, enc, cb) { + // console.log('INFLATING') + Zlib.inflateRaw(chunk, { chunkSize: 1024 * 1024 * 2 }, (err, buf) => { + if (err) throw err + this.push(buf) + cb() + }) + } + }) + + client.decipher.pipe(verifyChecksum) + .pipe(inflator) + // .pipe(Zlib.createInflateRaw({ chunkSize: 1024 * 1024 * 2 })) + .on('data', (...args) => client.onDecryptedPacket(...args)) + .on('end', () => console.log('Decryptor: finish pipeline')) + return (blob) => { client.decipher.write(blob) @@ -138,3 +161,17 @@ function createDecryptor(client, iv) { module.exports = { createCipher, createDecipher, createEncryptor, createDecryptor } + +function testDecrypt() { + const client = { + secretKeyBytes: Buffer.from('ZOBpyzki/M8UZv5tiBih048eYOBVPkQE3r5Fl0gmUP4=', 'base64'), + onDecryptedPacket: (...data) => console.log('Decrypted', data) + } + const iv = Buffer.from('ZOBpyzki/M8UZv5tiBih0w==', 'base64') + + const decrypt = createDecryptor(client, iv) + console.log('Dec', decrypt(Buffer.from('4B4FCA0C2A4114155D67F8092154AAA5EF', 'hex'))) + console.log('Dec 2', decrypt(Buffer.from('DF53B9764DB48252FA1AE3AEE4', 'hex'))) +} + +// testDecrypt() \ No newline at end of file