refactor code

This commit is contained in:
DavidVentura
2024-01-26 17:21:38 +01:00
parent f891ca1ff9
commit 2e1d761e28
7 changed files with 380 additions and 225 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ venv: requirements.txt
touch venv
run: node_modules
./node_modules/.bin/ts-node --esm impl.ts
./node_modules/.bin/ts-node --esm server.ts
hook: bundle.js venv
./venv/bin/python3 -u loader3.py
+136
View File
@@ -0,0 +1,136 @@
import { sock } from "./server.js";
import { Commands, CommandsByValue } from "./func_replacements.js";
import { createWriteStream } from "node:fs";
import { SendUsrAck, SendUsrChk, create_P2pRdy } from "./impl.js";
let image_fds = [];
let cur_image_index = 0;
let size_so_far = 0;
export const notImpl = (_: sock, dv: DataView) => {
const raw = dv.readU16();
const cmd = CommandsByValue[raw];
console.log(`^^ ${cmd} (${raw.toString(16)}) and it's not implemented yet`);
};
export const noop = (_: sock, __: DataView) => {};
const create_P2pAliveAck = (): DataView => {
const outbuf = new DataView(new Uint8Array(4).buffer);
outbuf.writeU16(Commands.P2PAliveAck);
outbuf.add(2).writeU16(0);
return outbuf;
};
export const handle_P2PAlive = (sock: sock, _: DataView) => {
const b = create_P2pAliveAck();
sock.send(b);
};
export const handle_PunchPkt = (sock: sock, dv: DataView) => {
const punchCmd = dv.readU16();
const len = dv.add(2).readU16();
const prefix = dv.add(4).readString(4);
const serial = dv.add(8).readU64().toString();
const suffix = dv.add(16).readString(4);
// f141 20 BATC 609531 EXLV
sock.send(create_P2pRdy(dv.add(4).readByteArray(len)));
};
const deal_with_control = (sock: sock, dv: DataView) => {
const start_type = dv.add(8).readU16(); // 0xa11 on control; data starts here on DATA pkt
const cmd_id = dv.add(10).readU16(); // 0x1120
if (start_type != 0x110a) {
console.error(`Expected start_type to be 0xa11, got 0x${start_type.toString(16)}`);
return;
}
if (cmd_id == ControlCommands.ConnectUserAck) {
/*
00000000 f1 d0 00 18 d1 00 00 00 11 0a 20 11 0c 00 ff 00 .......... .....
00000010 00 00 00 00 34 54 63 4d fe 01 01 01 ....4TcM....
^^^^^^^^^^^
some kind of challenge
need to send the 0x3010
command with these 4 bytes 'encrypted'
*/
let challenge = [0, 0, 0, 0];
challenge[0] = dv.add(0x14).readU8();
challenge[1] = dv.add(0x15).readU8();
challenge[2] = dv.add(0x16).readU8();
challenge[3] = dv.add(0x17).readU8();
const buf = SendUsrAck(challenge);
sock.send(buf);
}
};
const deal_with_data = (dv: DataView) => {
const pkt_len = dv.add(2).readU16();
// data
const JPEG_HEADER = [0xff, 0xd8, 0xff, 0xdb];
const AUDIO_HEADER = [0x55, 0xaa, 0x15, 0xa8];
const m_hdr = dv.add(8).readByteArray(4);
let is_new_image = true;
let audio = true;
for (let i = 0; i < 4; i++) {
is_new_image = is_new_image && m_hdr.add(i).readU8() == JPEG_HEADER[i];
audio = audio && m_hdr.add(i).readU8() == AUDIO_HEADER[i];
}
if (audio) {
// TODO audio pkt
} else {
if (is_new_image) {
size_so_far = 0;
if (cur_image_index > 0) {
image_fds[cur_image_index - 1].close();
}
const fname = `captures/${cur_image_index.toString().padStart(4, "0")}.jpg`;
let cur_image = createWriteStream(fname);
cur_image.cork();
image_fds[cur_image_index] = cur_image;
cur_image_index++;
}
const data = dv.add(8).readByteArray(pkt_len - 4);
image_fds[cur_image_index - 1].write(Buffer.from(data.buffer));
size_so_far += pkt_len - 4;
}
};
const ControlCommands = {
ConnectUser: 0x2010,
ConnectUserAck: 0x2011,
};
const makeDrwAck = (dv: DataView): DataView => {
const pkt_id = dv.add(6).readU16();
const m_stream = dv.add(5).readU8(); // data = 1, control = 0
const item_count = 1; // TODO coalesce acks
const reply_len = item_count * 2 + 4; // 4 hdr, 2b per item
const outbuf = new DataView(new Uint8Array(32).buffer);
outbuf.writeU16(Commands.DrwAck);
outbuf.add(2).writeU16(reply_len);
outbuf.add(4).writeU8(0xd2);
outbuf.add(5).writeU8(m_stream);
outbuf.add(6).writeU16(item_count);
for (let i = 0; i < item_count; i++) {
outbuf.add(8 + i * 2).writeU16(pkt_id);
}
return outbuf;
};
export const handle_Drw = (sock: sock, dv: DataView) => {
const ack = makeDrwAck(dv);
sock.send(ack);
const m_stream = dv.add(5).readU8(); // data = 1, control = 0
if (m_stream == 1) {
deal_with_data(dv);
} else {
deal_with_control(sock, dv);
}
};
export const handle_P2PRdy = (sock: sock, _: DataView) => {
const b = SendUsrChk("admin", "admin");
sock.send(b);
};
+105
View File
@@ -0,0 +1,105 @@
// hacked hexdump from frida to work on normal ArrayBuffers
import "./shim.ts";
export const hexdump = (target, options) => {
options = options || {};
const startOffset = options.offset || 0;
let length = options.length;
const showHeader = options.hasOwnProperty("header") ? options.header : true;
const useAnsi = options.hasOwnProperty("ansi") ? options.ansi : false;
const ansiColor = options.hasOwnProperty("ansiColor") ? options.ansiColor : 0;
let buffer;
if (target instanceof ArrayBuffer) {
if (length === undefined) length = target.byteLength;
else length = Math.min(length, target.byteLength);
buffer = target;
} else {
throw "Gimme array buffer";
}
const startAddress = 0;
const endAddress = target.byteLength;
const bytes = new Uint8Array(buffer);
const columnPadding = " ";
const leftColumnWidth = Math.max(endAddress.toString(16).length, 8);
const hexLegend = " 0 1 2 3 4 5 6 7 8 9 A B C D E F";
const asciiLegend = "0123456789ABCDEF";
let resetColor, offsetColor, dataColor, newlineColor;
if (useAnsi) {
resetColor = "\x1b[0m";
offsetColor = "\x1b[0;32m";
dataColor = ansiColor == 0 ? "\x1b[0;33m" : "\x1b[0;34m";
newlineColor = resetColor;
} else {
resetColor = "";
offsetColor = "";
dataColor = "";
newlineColor = "";
}
const result = [];
if (showHeader) {
result.push(pad(" ", leftColumnWidth, " "), columnPadding, hexLegend, columnPadding, asciiLegend, "\n");
}
let offset = startOffset;
for (let bufferOffset = 0; bufferOffset < length; bufferOffset += 16) {
if (bufferOffset !== 0) result.push("\n");
result.push(
offsetColor,
pad((startAddress + offset).toString(16), leftColumnWidth, "0"),
resetColor,
columnPadding,
);
const asciiChars = [];
const lineSize = Math.min(length - offset, 16);
for (let lineOffset = 0; lineOffset !== lineSize; lineOffset++) {
const value = bytes[offset++];
const isNewline = value === 10;
const hexPair = pad(value.toString(16), 2, "0");
if (lineOffset !== 0) result.push(" ");
result.push(isNewline ? newlineColor : dataColor, hexPair, resetColor);
asciiChars.push(
isNewline ? newlineColor : dataColor,
value >= 32 && value <= 126 ? String.fromCharCode(value) : ".",
resetColor,
);
}
for (let lineOffset = lineSize; lineOffset !== 16; lineOffset++) {
result.push(" ");
asciiChars.push(" ");
}
result.push(columnPadding);
Array.prototype.push.apply(result, asciiChars);
}
let trailingSpaceCount = 0;
for (let tailOffset = result.length - 1; tailOffset >= 0 && result[tailOffset] === " "; tailOffset--) {
trailingSpaceCount++;
}
return result.slice(0, result.length - trailingSpaceCount).join("");
};
function pad(str, width, fill) {
const result = [];
const paddingSize = Math.max(width - str.length, 0);
for (let index = 0; index !== paddingSize; index++) {
result.push(fill);
}
return result.join("") + str;
}
+35 -217
View File
@@ -1,9 +1,6 @@
import "./shim.ts";
import dgram from "node:dgram";
import { Commands, CommandsByValue, create_LanSearch, XqBytesEnc } from "./func_replacements.js";
import { hexdump } from "./hexdump.js";
import { Commands, XqBytesEnc } from "./func_replacements.js";
import { u16_swap } from "./utils.js";
const str2byte = (s: string): number[] => {
@@ -29,6 +26,39 @@ const DrwHdr = (cmd: number, len: number, d1_or_d2: 0xd1 | 0xd2, m_chan: number)
return retret;
};
export const SendUsrAck = (challenge: number[]): DataView => {
// TODO: extract SendUsrChk
let buf = new DataView(new Uint8Array(0x18).buffer);
const seq = 0x1;
let bytes = [
0xf1,
0xd0,
0x01,
0x14,
0xd1,
0x00,
0x00,
seq,
0x11,
0x0a,
0x10,
0x30,
0x08,
0x01,
0x00,
0x00,
challenge[0] % 2 == 0 ? challenge[0] + 1 : challenge[0] - 1,
challenge[1] % 2 == 0 ? challenge[1] + 1 : challenge[1] - 1,
challenge[2] % 2 == 0 ? challenge[2] + 1 : challenge[2] - 1,
challenge[3] % 2 == 0 ? challenge[3] + 1 : challenge[3] - 1,
0x01,
0x01,
0x01,
0x01,
];
buf.writeByteArray(bytes);
return buf;
};
export const SendUsrChk = (username: string, password: string): DataView => {
// type is char account[0x20]; char password[0x80];
let buf = new Uint8Array(0x20 + 0x80);
@@ -55,60 +85,7 @@ export const SendUsrChk = (username: string, password: string): DataView => {
return retret;
};
const EstablishSession = () => {
const ls = create_LanSearch(); // Broadcast
};
type sock = { send: (msg: DataView) => void; broadcast: (msg: DataView) => void };
const MakeSock = (cb: (msg: Buffer, rinfo: any) => void): sock => {
const server = dgram.createSocket("udp4");
server.on("error", (err) => {
console.error(`server error:\n${err.stack}`);
server.close();
});
server.on("message", cb);
server.on("listening", () => {
const address = server.address();
console.log(`server listening ${address.address}:${address.port}`);
server.setBroadcast(true);
});
const RECV_PORT = 49512; // important?
const DST_IP = "192.168.1.1";
const BCAST_IP = "192.168.1.255";
const SEND_PORT = 32108;
server.bind(RECV_PORT);
return {
send: (msg: DataView) => {
const raw = msg.readU16();
const cmd = CommandsByValue[raw];
console.log(`>> ${cmd}`);
console.log(hexdump(msg.buffer, { ansi: true, ansiColor: 0 }));
server.send(new Uint8Array(msg.buffer), SEND_PORT, DST_IP);
},
broadcast: (msg: DataView) => server.send(new Uint8Array(msg.buffer), SEND_PORT, BCAST_IP),
};
};
const notImpl = (sock: sock, dv: DataView) => {
const raw = dv.readU16();
const cmd = CommandsByValue[raw];
console.log(`^^ ${cmd} (${raw.toString(16)}) and it's not implemented yet`);
};
const noop = (sock: sock, dv: DataView) => {};
const create_P2pAliveAck = (): DataView => {
const outbuf = new DataView(new Uint8Array(4).buffer);
outbuf.writeU16(Commands.P2PAliveAck);
outbuf.add(2).writeU16(0);
return outbuf;
};
const create_P2pRdy = (inbuf: DataView): DataView => {
export const create_P2pRdy = (inbuf: DataView): DataView => {
const P2PRDY_SIZE = 0x14;
const outbuf = new DataView(new Uint8Array(P2PRDY_SIZE + 4).buffer);
outbuf.writeU16(Commands.P2pRdy);
@@ -116,162 +93,3 @@ const create_P2pRdy = (inbuf: DataView): DataView => {
outbuf.add(4).writeByteArray(new Uint8Array(inbuf.readByteArray(P2PRDY_SIZE).buffer));
return outbuf;
};
let seen = [0];
const handle_Drw = (sock: sock, dv: DataView) => {
// TODO
// INPUT
// byte 4 = d1 or d2, just add 1
// byte 5 = stream??
// byte 6-7 = pkt id
// OUTPUT
// f1d1
// 2b len
// d1/d2 +1 (always d2?)
// 2b ack'd packets (1 for now, no coalescing)
// N times 2b with packet id
/*
*
00000000 f1 d0 00 18 d1 00 00 00 11 0a 20 11 0c 00 ff 00 .......... .....
00000010 00 00 00 00 34 54 63 4d fe 01 01 01 ....4TcM....
^^^^^^^^^^^^^^
some kind of challenge
need to send the 0x3010 command with this
but add 1 to every byte
*/
const should_be_d1 = dv.add(4).readU8();
const m_stream = dv.add(5).readU8();
const pkt_id = dv.add(6).readU16();
const start_type = dv.add(8).readU16(); // 0xa11
const cmd_id = dv.add(10).readU16(); // 0x1120
console.log("DRW", should_be_d1, m_stream, pkt_id, start_type.toString(16), cmd_id.toString(16));
if (cmd_id == 0x2011) {
challenge[0] = dv.add(0x14).readU8();
challenge[1] = dv.add(0x15).readU8();
challenge[2] = dv.add(0x16).readU8();
challenge[3] = dv.add(0x17).readU8();
}
const item_count = 1; // TODO
const reply_len = item_count * 2 + 4; // 4 hdr, 2b per item
const outbuf = new DataView(new Uint8Array(32).buffer);
outbuf.writeU16(Commands.DrwAck);
outbuf.add(2).writeU16(reply_len);
outbuf.add(4).writeU8(0xd2);
outbuf.add(5).writeU8(m_stream);
outbuf.add(6).writeU16(item_count);
for (let i = 0; i < item_count; i++) {
outbuf.add(8 + i * 2).writeU16(pkt_id);
}
sock.send(outbuf);
// CSession_Drw_Deal
// set some stuff?
// return a counter with pkt # ??
// stream: 0 control
// 1 data?? video+aud
// Send_Pkt_DrwAck(10,0xd2,channel,1,&cmd_,sock_fd,ipaddr_);
};
const challenge = [0, 0, 0, 0];
const handle_P2PRdy = (sock: sock, dv: DataView) => {
const b = SendUsrChk("admin", "admin");
sock.send(b);
setTimeout(() => {
/*
00000000 f1 d0 01 14 d1 00 00 90 11 0a 10 30 08 01 00 00 ...........0....
00000010 77 78 35 69 01 01 01 01 wx5i....
00000000 f1 d0 01 14 d1 00 00 04 11 0a 10 30 08 01 00 00 ...........0....
00000010 55 58 36 59 01 01 01 01 UX6Y....
*/
let buf = new DataView(new Uint8Array(0x18).buffer);
const seq = 0x1;
let bytes = [
0xf1,
0xd0,
0x01,
0x14,
0xd1,
0x00,
0x00,
seq,
0x11,
0x0a,
0x10,
0x30,
0x08,
0x01,
0x00,
0x00,
challenge[0] % 2 == 0 ? challenge[0] + 1 : challenge[0] - 1,
challenge[1] % 2 == 0 ? challenge[1] + 1 : challenge[1] - 1,
challenge[2] % 2 == 0 ? challenge[2] + 1 : challenge[2] - 1,
challenge[3] % 2 == 0 ? challenge[3] + 1 : challenge[3] - 1,
0x01,
0x01,
0x01,
0x01,
];
buf.writeByteArray(bytes);
sock.send(buf);
}, 100);
};
const handle_P2PAlive = (sock: sock, dv: DataView) => {
const b = create_P2pAliveAck();
sock.send(b);
};
const handle_PunchPkt = (sock: sock, dv: DataView) => {
console.log(`Got a nice punchpkt`);
const punchCmd = dv.readU16();
const len = dv.add(2).readU16();
const prefix = dv.add(4).readString(4);
const serial = dv.add(8).readU64().toString();
const suffix = dv.add(16).readString(4);
// f141 20 BATC 609531 EXLV
console.log(punchCmd.toString(16), len, prefix, serial, suffix);
sock.send(create_P2pRdy(dv.add(4).readByteArray(len)));
};
export const Handlers: Record<keyof typeof Commands, (sock: sock, dv: DataView) => void> = {
// FIXME: keys are 'any' bc import?
PunchPkt: handle_PunchPkt,
Close: notImpl,
LanSearchExt: notImpl,
LanSearch: notImpl,
P2PAlive: handle_P2PAlive,
P2PAliveAck: notImpl,
Hello: notImpl,
P2pRdy: handle_P2PRdy,
P2pReq: notImpl,
LstReq: notImpl,
DrwAck: noop,
Drw: handle_Drw,
// From CSession_CtrlPkt_Proc, incomplete
PunchTo: notImpl,
HelloAck: notImpl,
RlyTo: notImpl,
DevLgnAck: notImpl,
P2PReqAck: notImpl,
ListenReqAck: notImpl,
RlyHelloAck: notImpl, // always
RlyHelloAck2: notImpl, // if len >1??
};
const sock = MakeSock((msg, rinfo) => {
const ab = new Uint8Array(msg).buffer;
const dv = new DataView(ab);
const cmd = CommandsByValue[dv.readU16()];
// ${rinfo.address}:${rinfo.port}
console.log(`<< ${cmd}`);
console.log(hexdump(ab, { useAnsi: true, ansiColor: 1 }));
Handlers[cmd](sock, dv);
});
const int = setInterval(() => {
let buf = new DataView(new Uint8Array(4).buffer);
create_LanSearch(buf);
sock.broadcast(buf);
// sock.send(buf);
}, 1000);
+1 -1
View File
@@ -24,7 +24,7 @@ title: Stream audio/video
---
sequenceDiagram
App->>Cam: StreamStart (with Token, 0x3010)
App->>Cam: StreamStart (with Token)
loop
Cam-->>+App: Audio/Video Payload
+102
View File
@@ -0,0 +1,102 @@
import dgram from "node:dgram";
import { create_LanSearch, Commands, CommandsByValue } from "./func_replacements.js";
import { handle_P2PAlive, handle_PunchPkt, handle_P2PRdy, handle_Drw, notImpl, noop } from "./handlers.js";
import { hexdump } from "./hexdump.js";
export type sock = {
send: (msg: DataView) => void;
broadcast: (msg: DataView) => void;
};
type opt = {
debug: boolean;
ansi: boolean;
};
type msgCb = (msg: Buffer, rinfo: any, options: opt) => void;
type connCb = () => void;
const MakeSock = (cb: msgCb, connCb: connCb, options?: opt): sock => {
const server = dgram.createSocket("udp4");
server.on("error", (err) => {
console.error(`server error:\n${err.stack}`);
server.close();
});
server.on("message", (msg, rinfo) => cb(msg, rinfo, options));
server.on("listening", () => {
const address = server.address();
console.log(`server listening ${address.address}:${address.port}`);
server.setBroadcast(true);
connCb();
});
const RECV_PORT = 49512; // important?
const DST_IP = "192.168.1.1";
const BCAST_IP = "192.168.1.255";
const SEND_PORT = 32108;
server.bind(RECV_PORT);
return {
send: (msg: DataView) => {
const raw = msg.readU16();
const cmd = CommandsByValue[raw];
if (options.debug) {
console.log(`>> ${cmd}`);
console.log(hexdump(msg.buffer, { ansi: options.ansi, ansiColor: 0 }));
}
server.send(new Uint8Array(msg.buffer), SEND_PORT, DST_IP);
},
broadcast: (msg: DataView) => server.send(new Uint8Array(msg.buffer), SEND_PORT, BCAST_IP),
};
};
const Handlers: Record<keyof typeof Commands, (sock: sock, dv: DataView) => void> = {
// FIXME: keys are 'any' bc import?
PunchPkt: handle_PunchPkt,
Close: notImpl,
LanSearchExt: notImpl,
LanSearch: notImpl,
P2PAlive: handle_P2PAlive,
P2PAliveAck: notImpl,
Hello: notImpl,
P2pRdy: handle_P2PRdy,
P2pReq: notImpl,
LstReq: notImpl,
DrwAck: noop,
Drw: handle_Drw,
// From CSession_CtrlPkt_Proc, incomplete
PunchTo: notImpl,
HelloAck: notImpl,
RlyTo: notImpl,
DevLgnAck: notImpl,
P2PReqAck: notImpl,
ListenReqAck: notImpl,
RlyHelloAck: notImpl, // always
RlyHelloAck2: notImpl, // if len >1??
};
const sock = MakeSock(
(msg, rinfo, options) => {
const ab = new Uint8Array(msg).buffer;
const dv = new DataView(ab);
const cmd = CommandsByValue[dv.readU16()];
if (options.debug) {
console.log(`<< ${cmd}`);
console.log(hexdump(msg.buffer, { ansi: options.ansi, ansiColor: 1 }));
}
Handlers[cmd](sock, dv);
},
() => {
const int = setInterval(() => {
let buf = new DataView(new Uint8Array(4).buffer);
create_LanSearch(buf);
sock.broadcast(buf);
}, 1000);
},
{ debug: false, ansi: false },
);
-6
View File
@@ -47,12 +47,6 @@ DataView.prototype.readString = function (len) {
return String.fromCharCode.apply(null, new Uint8Array(ba.buffer));
};
const Memory = {
alloc: (len: number) => new DataView(new ArrayBuffer(len + 1)),
copy: (outbuf: DataView, inbuf: DataView, len: number) =>
outbuf.writeByteArray(new Uint8Array(inbuf.readByteArray(len).buffer)),
};
declare global {
interface DataView {
add(offset: number): DataView;