idz: Factor out common TCP obfuscation

This commit is contained in:
Tau 2020-03-11 17:27:50 -04:00 committed by Tau
parent 01128979db
commit 2dfeeaa8bd
4 changed files with 163 additions and 0 deletions

43
src/idz/common/aes.ts Normal file
View 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
View 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
View 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
View 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 };
}