mirror of
https://github.com/djhackersdev/minime.git
synced 2026-07-07 20:44:06 -05:00
idz: Factor out common TCP obfuscation
This commit is contained in:
parent
01128979db
commit
2dfeeaa8bd
43
src/idz/common/aes.ts
Normal file
43
src/idz/common/aes.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { Decipher, Cipher, createCipheriv, createDecipheriv } from "crypto";
|
||||
|
||||
import { ByteStream } from "../../util/stream";
|
||||
|
||||
export const BLOCK_SIZE = 16;
|
||||
|
||||
export default class AesEcbStream implements ByteStream {
|
||||
private readonly _dec: Decipher;
|
||||
private readonly _enc: Cipher;
|
||||
|
||||
constructor(private _stm: ByteStream, keybuf: Buffer) {
|
||||
this._dec = createDecipheriv("aes-128-ecb", keybuf, null).setAutoPadding(
|
||||
false
|
||||
);
|
||||
this._enc = createCipheriv("aes-128-ecb", keybuf, null).setAutoPadding(
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
close() {
|
||||
this._stm.close();
|
||||
}
|
||||
|
||||
async read(nbytes: number): Promise<Buffer> {
|
||||
if (nbytes % BLOCK_SIZE !== 0) {
|
||||
throw new Error("Attempted to read partial cipher block");
|
||||
}
|
||||
|
||||
const ciphertext = await this._stm.read(nbytes);
|
||||
|
||||
return this._dec.update(ciphertext);
|
||||
}
|
||||
|
||||
async write(buf: Buffer): Promise<void> {
|
||||
if (buf.length % BLOCK_SIZE !== 0) {
|
||||
throw new Error("Attempted to write partial cipher block");
|
||||
}
|
||||
|
||||
const ciphertext = this._enc.update(buf);
|
||||
|
||||
await this._stm.write(ciphertext);
|
||||
}
|
||||
}
|
||||
46
src/idz/common/bigint.ts
Normal file
46
src/idz/common/bigint.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
export function readBigInt(buf: Buffer) {
|
||||
let result = 0n;
|
||||
|
||||
for (let i = 0; i < buf.length; i++) {
|
||||
const shift = 8n * BigInt(i);
|
||||
const byte = buf.readUInt8(i);
|
||||
|
||||
result |= BigInt(byte) << shift;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function writeBigInt(n: bigint, length: number) {
|
||||
const result = Buffer.alloc(length);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const shift = 8n * BigInt(i);
|
||||
const byte = (n >> shift) & 0xffn;
|
||||
|
||||
result[i] = Number(byte);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// i pick the one implementation language that doesn't have this built in
|
||||
|
||||
export function modPow(b: bigint, e: bigint, m: bigint) {
|
||||
// https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method
|
||||
|
||||
let result = 1n;
|
||||
|
||||
b = b % m;
|
||||
|
||||
while (e > 0n) {
|
||||
if ((e & 1n) === 1n) {
|
||||
result = (result * b) % m;
|
||||
}
|
||||
|
||||
e = e >> 1n;
|
||||
b = (b * b) % m;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
4
src/idz/common/index.ts
Normal file
4
src/idz/common/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import setup from "./setup";
|
||||
|
||||
export { BLOCK_SIZE } from "./aes";
|
||||
export default setup;
|
||||
70
src/idz/common/setup.ts
Normal file
70
src/idz/common/setup.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { Socket } from "net";
|
||||
|
||||
import AesEcbStream from "./aes";
|
||||
import { readBigInt, modPow, writeBigInt } from "./bigint";
|
||||
import IoByteStream from "../../util/stream";
|
||||
|
||||
interface RsaKey {
|
||||
N: bigint;
|
||||
d: bigint;
|
||||
e: bigint;
|
||||
hashN: number;
|
||||
}
|
||||
|
||||
// Proof-of-concept, so we only ever use one of the ten RSA key pairs
|
||||
const rsaKey = {
|
||||
N: 4922323266120814292574970172377860734034664704992758249880018618131907367614177800329506877981986877921220485681998287752778495334541127048495486311792061n,
|
||||
d: 1163847742215766215216916151663017691387519688859977157498780867776436010396072628219119707788340687440419444081289736279466637153082223960965411473296473n,
|
||||
e: 3961365081960959178294197133768419551060435043430437330799371731939550352626564261219865471710058480523874787120718634318364066605378505537556570049131337n,
|
||||
hashN: 2662304617,
|
||||
};
|
||||
|
||||
// Proof-of-concept, so we only use one fixed session key
|
||||
const aesKey = Buffer.from("ffddeeccbbaa99887766554433221100", "hex");
|
||||
|
||||
function writeServerHello(aesKey: Buffer, rsaKey: RsaKey) {
|
||||
const M = readBigInt(aesKey);
|
||||
const keyEnc = modPow(M, rsaKey.e, rsaKey.N);
|
||||
const result = Buffer.alloc(0x48);
|
||||
|
||||
result.set(writeBigInt(keyEnc, 0x40), 0x00);
|
||||
result.writeUInt32LE(0x01020304, 0x40); // Meaning is unknown
|
||||
result.writeUInt32LE(rsaKey.hashN, 0x44);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function readClientHello(buf: Buffer) {
|
||||
const magic = buf.readUInt32LE(0x00);
|
||||
|
||||
if (magic !== 0x01020304) {
|
||||
throw new Error(
|
||||
"Invalid magic number, cryptographic processing probably incorrect."
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
pcbId: buf.slice(0x04, 0x0f).toString("ascii"),
|
||||
protocol: buf.slice(0x10, 0x13).toString("ascii"),
|
||||
model: buf.slice(0x18, 0x0c).toString("ascii"),
|
||||
};
|
||||
}
|
||||
|
||||
export default async function setup(socket: Socket) {
|
||||
const tcpStream = new IoByteStream(socket);
|
||||
const serverHello = writeServerHello(aesKey, rsaKey);
|
||||
|
||||
await tcpStream.write(serverHello);
|
||||
|
||||
const aesStream = new AesEcbStream(tcpStream, aesKey);
|
||||
|
||||
const response = await aesStream.read(0x30);
|
||||
|
||||
if (response.length !== 0x30) {
|
||||
throw new Error("Truncated client hello");
|
||||
}
|
||||
|
||||
const clientHello = readClientHello(response);
|
||||
|
||||
return { aesStream, clientHello };
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user