mirror of
https://github.com/gchq/CyberChef
synced 2024-11-14 16:47:07 +00:00
Added 'Parse TCP' operation
This commit is contained in:
parent
477e4a7421
commit
a895d1d82a
14 changed files with 430 additions and 81 deletions
|
@ -217,7 +217,8 @@ module.exports = function (grunt) {
|
||||||
client: {
|
client: {
|
||||||
logging: "error",
|
logging: "error",
|
||||||
overlay: true
|
overlay: true
|
||||||
}
|
},
|
||||||
|
hot: "only"
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin(BUILD_CONSTANTS),
|
new webpack.DefinePlugin(BUILD_CONSTANTS),
|
||||||
|
|
14
package-lock.json
generated
14
package-lock.json
generated
|
@ -98,7 +98,7 @@
|
||||||
"autoprefixer": "^10.4.4",
|
"autoprefixer": "^10.4.4",
|
||||||
"babel-loader": "^8.2.4",
|
"babel-loader": "^8.2.4",
|
||||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||||
"chromedriver": "^99.0.0",
|
"chromedriver": "^101.0.0",
|
||||||
"cli-progress": "^3.10.0",
|
"cli-progress": "^3.10.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"copy-webpack-plugin": "^10.2.4",
|
"copy-webpack-plugin": "^10.2.4",
|
||||||
|
@ -4467,9 +4467,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chromedriver": {
|
"node_modules/chromedriver": {
|
||||||
"version": "99.0.0",
|
"version": "101.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-99.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-101.0.0.tgz",
|
||||||
"integrity": "sha512-pyB+5LuyZdb7EBPL3i5D5yucZUD+SlkdiUtmpjaEnLd9zAXp+SvD/hP5xF4l/ZmWvUo/1ZLxAI1YBdhazGTpgA==",
|
"integrity": "sha512-LkkWxy6KM/0YdJS8qBeg5vfkTZTRamhBfOttb4oic4echDgWvCU1E8QcBbUBOHqZpSrYMyi7WMKmKMhXFUaZ+w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -19171,9 +19171,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"chromedriver": {
|
"chromedriver": {
|
||||||
"version": "99.0.0",
|
"version": "101.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-99.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-101.0.0.tgz",
|
||||||
"integrity": "sha512-pyB+5LuyZdb7EBPL3i5D5yucZUD+SlkdiUtmpjaEnLd9zAXp+SvD/hP5xF4l/ZmWvUo/1ZLxAI1YBdhazGTpgA==",
|
"integrity": "sha512-LkkWxy6KM/0YdJS8qBeg5vfkTZTRamhBfOttb4oic4echDgWvCU1E8QcBbUBOHqZpSrYMyi7WMKmKMhXFUaZ+w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@testim/chrome-version": "^1.1.2",
|
"@testim/chrome-version": "^1.1.2",
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
"autoprefixer": "^10.4.4",
|
"autoprefixer": "^10.4.4",
|
||||||
"babel-loader": "^8.2.4",
|
"babel-loader": "^8.2.4",
|
||||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||||
"chromedriver": "^99.0.0",
|
"chromedriver": "^101.0.0",
|
||||||
"cli-progress": "^3.10.0",
|
"cli-progress": "^3.10.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"copy-webpack-plugin": "^10.2.4",
|
"copy-webpack-plugin": "^10.2.4",
|
||||||
|
|
|
@ -190,6 +190,7 @@
|
||||||
"Parse IP range",
|
"Parse IP range",
|
||||||
"Parse IPv6 address",
|
"Parse IPv6 address",
|
||||||
"Parse IPv4 header",
|
"Parse IPv4 header",
|
||||||
|
"Parse TCP",
|
||||||
"Parse UDP",
|
"Parse UDP",
|
||||||
"Parse SSH Host Key",
|
"Parse SSH Host Key",
|
||||||
"Parse URI",
|
"Parse URI",
|
||||||
|
|
|
@ -13,7 +13,7 @@ import OperationError from "../errors/OperationError.mjs";
|
||||||
/**
|
/**
|
||||||
* Convert a byte array into a binary string.
|
* Convert a byte array into a binary string.
|
||||||
*
|
*
|
||||||
* @param {Uint8Array|byteArray} data
|
* @param {Uint8Array|byteArray|number} data
|
||||||
* @param {string} [delim="Space"]
|
* @param {string} [delim="Space"]
|
||||||
* @param {number} [padding=8]
|
* @param {number} [padding=8]
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
|
@ -26,13 +26,17 @@ import OperationError from "../errors/OperationError.mjs";
|
||||||
* toBinary([10,20,30], ":");
|
* toBinary([10,20,30], ":");
|
||||||
*/
|
*/
|
||||||
export function toBinary(data, delim="Space", padding=8) {
|
export function toBinary(data, delim="Space", padding=8) {
|
||||||
if (!data) return "";
|
|
||||||
|
|
||||||
delim = Utils.charRep(delim);
|
delim = Utils.charRep(delim);
|
||||||
let output = "";
|
let output = "";
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
if (data.length) { // array
|
||||||
output += data[i].toString(2).padStart(padding, "0") + delim;
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
output += data[i].toString(2).padStart(padding, "0") + delim;
|
||||||
|
}
|
||||||
|
} else if (typeof data === "number") { // Single value
|
||||||
|
return data.toString(2).padStart(padding, "0");
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (delim.length) {
|
if (delim.length) {
|
||||||
|
|
|
@ -3778,8 +3778,8 @@ function parseDEFLATE(stream) {
|
||||||
|
|
||||||
while (!finalBlock) {
|
while (!finalBlock) {
|
||||||
// Read header
|
// Read header
|
||||||
finalBlock = stream.readBits(1);
|
finalBlock = stream.readBits(1, "le");
|
||||||
const blockType = stream.readBits(2);
|
const blockType = stream.readBits(2, "le");
|
||||||
|
|
||||||
if (blockType === 0) {
|
if (blockType === 0) {
|
||||||
/* No compression */
|
/* No compression */
|
||||||
|
@ -3798,16 +3798,16 @@ function parseDEFLATE(stream) {
|
||||||
/* Dynamic Huffman */
|
/* Dynamic Huffman */
|
||||||
|
|
||||||
// Read the number of liternal and length codes
|
// Read the number of liternal and length codes
|
||||||
const hlit = stream.readBits(5) + 257;
|
const hlit = stream.readBits(5, "le") + 257;
|
||||||
// Read the number of distance codes
|
// Read the number of distance codes
|
||||||
const hdist = stream.readBits(5) + 1;
|
const hdist = stream.readBits(5, "le") + 1;
|
||||||
// Read the number of code lengths
|
// Read the number of code lengths
|
||||||
const hclen = stream.readBits(4) + 4;
|
const hclen = stream.readBits(4, "le") + 4;
|
||||||
|
|
||||||
// Parse code lengths
|
// Parse code lengths
|
||||||
const codeLengths = new Uint8Array(huffmanOrder.length);
|
const codeLengths = new Uint8Array(huffmanOrder.length);
|
||||||
for (let i = 0; i < hclen; i++) {
|
for (let i = 0; i < hclen; i++) {
|
||||||
codeLengths[huffmanOrder[i]] = stream.readBits(3);
|
codeLengths[huffmanOrder[i]] = stream.readBits(3, "le");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse length table
|
// Parse length table
|
||||||
|
@ -3819,16 +3819,16 @@ function parseDEFLATE(stream) {
|
||||||
code = readHuffmanCode(stream, codeLengthsTable);
|
code = readHuffmanCode(stream, codeLengthsTable);
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 16:
|
case 16:
|
||||||
repeat = 3 + stream.readBits(2);
|
repeat = 3 + stream.readBits(2, "le");
|
||||||
while (repeat--) lengthTable[i++] = prev;
|
while (repeat--) lengthTable[i++] = prev;
|
||||||
break;
|
break;
|
||||||
case 17:
|
case 17:
|
||||||
repeat = 3 + stream.readBits(3);
|
repeat = 3 + stream.readBits(3, "le");
|
||||||
while (repeat--) lengthTable[i++] = 0;
|
while (repeat--) lengthTable[i++] = 0;
|
||||||
prev = 0;
|
prev = 0;
|
||||||
break;
|
break;
|
||||||
case 18:
|
case 18:
|
||||||
repeat = 11 + stream.readBits(7);
|
repeat = 11 + stream.readBits(7, "le");
|
||||||
while (repeat--) lengthTable[i++] = 0;
|
while (repeat--) lengthTable[i++] = 0;
|
||||||
prev = 0;
|
prev = 0;
|
||||||
break;
|
break;
|
||||||
|
@ -3886,11 +3886,11 @@ function parseHuffmanBlock(stream, litTab, distTab) {
|
||||||
if (code < 256) continue;
|
if (code < 256) continue;
|
||||||
|
|
||||||
// Length code
|
// Length code
|
||||||
stream.readBits(lengthExtraTable[code - 257]);
|
stream.readBits(lengthExtraTable[code - 257], "le");
|
||||||
|
|
||||||
// Dist code
|
// Dist code
|
||||||
code = readHuffmanCode(stream, distTab);
|
code = readHuffmanCode(stream, distTab);
|
||||||
stream.readBits(distanceExtraTable[code]);
|
stream.readBits(distanceExtraTable[code], "le");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3948,7 +3948,7 @@ function readHuffmanCode(stream, table) {
|
||||||
const [codeTable, maxCodeLength] = table;
|
const [codeTable, maxCodeLength] = table;
|
||||||
|
|
||||||
// Read max length
|
// Read max length
|
||||||
const bitsBuf = stream.readBits(maxCodeLength);
|
const bitsBuf = stream.readBits(maxCodeLength, "le");
|
||||||
const codeWithLength = codeTable[bitsBuf & ((1 << maxCodeLength) - 1)];
|
const codeWithLength = codeTable[bitsBuf & ((1 << maxCodeLength) - 1)];
|
||||||
const codeLength = codeWithLength >>> 16;
|
const codeLength = codeWithLength >>> 16;
|
||||||
|
|
||||||
|
|
47
src/core/lib/Protocol.mjs
Normal file
47
src/core/lib/Protocol.mjs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* Protocol parsing functions.
|
||||||
|
*
|
||||||
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import BigNumber from "bignumber.js";
|
||||||
|
import {toHexFast} from "../lib/Hex.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively displays a JSON object as an HTML table
|
||||||
|
*
|
||||||
|
* @param {Object} obj
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
export function objToTable(obj, nested=false) {
|
||||||
|
let html = `<table
|
||||||
|
class='table table-sm table-nonfluid ${nested ? "mb-0 table-borderless" : "table-bordered"}'
|
||||||
|
style='table-layout: fixed; ${nested ? "margin: -1px !important;" : ""}'>`;
|
||||||
|
if (!nested)
|
||||||
|
html += `<tr>
|
||||||
|
<th>Field</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>`;
|
||||||
|
|
||||||
|
for (const key in obj) {
|
||||||
|
html += `<tr><td style='word-wrap: break-word'>${key}</td>`;
|
||||||
|
if (typeof obj[key] === "object")
|
||||||
|
html += `<td style='padding: 0'>${objToTable(obj[key], true)}</td>`;
|
||||||
|
else
|
||||||
|
html += `<td>${obj[key]}</td>`;
|
||||||
|
html += "</tr>";
|
||||||
|
}
|
||||||
|
html += "</table>";
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts bytes into a BigNumber string
|
||||||
|
* @param {Uint8Array} bs
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function bytesToLargeNumber(bs) {
|
||||||
|
return BigNumber(toHexFast(bs), 16).toString();
|
||||||
|
}
|
|
@ -27,15 +27,17 @@ export default class Stream {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a number of bytes from the current position.
|
* Get a number of bytes from the current position, or all remaining bytes.
|
||||||
*
|
*
|
||||||
* @param {number} numBytes
|
* @param {number} [numBytes=null]
|
||||||
* @returns {Uint8Array}
|
* @returns {Uint8Array}
|
||||||
*/
|
*/
|
||||||
getBytes(numBytes) {
|
getBytes(numBytes=null) {
|
||||||
if (this.position > this.length) return undefined;
|
if (this.position > this.length) return undefined;
|
||||||
|
|
||||||
const newPosition = this.position + numBytes;
|
const newPosition = numBytes !== null ?
|
||||||
|
this.position + numBytes :
|
||||||
|
this.length;
|
||||||
const bytes = this.bytes.slice(this.position, newPosition);
|
const bytes = this.bytes.slice(this.position, newPosition);
|
||||||
this.position = newPosition;
|
this.position = newPosition;
|
||||||
this.bitPos = 0;
|
this.bitPos = 0;
|
||||||
|
@ -91,34 +93,40 @@ export default class Stream {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a number of bits from the buffer.
|
* Reads a number of bits from the buffer in big or little endian.
|
||||||
*
|
|
||||||
* @TODO Add endianness
|
|
||||||
*
|
*
|
||||||
* @param {number} numBits
|
* @param {number} numBits
|
||||||
|
* @param {string} [endianness="be"]
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
readBits(numBits) {
|
readBits(numBits, endianness="be") {
|
||||||
if (this.position > this.length) return undefined;
|
if (this.position > this.length) return undefined;
|
||||||
|
|
||||||
let bitBuf = 0,
|
let bitBuf = 0,
|
||||||
bitBufLen = 0;
|
bitBufLen = 0;
|
||||||
|
|
||||||
// Add remaining bits from current byte
|
// Add remaining bits from current byte
|
||||||
bitBuf = (this.bytes[this.position++] & bitMask(this.bitPos)) >>> this.bitPos;
|
bitBuf = this.bytes[this.position++] & bitMask(this.bitPos);
|
||||||
|
if (endianness !== "be") bitBuf >>>= this.bitPos;
|
||||||
bitBufLen = 8 - this.bitPos;
|
bitBufLen = 8 - this.bitPos;
|
||||||
this.bitPos = 0;
|
this.bitPos = 0;
|
||||||
|
|
||||||
// Not enough bits yet
|
// Not enough bits yet
|
||||||
while (bitBufLen < numBits) {
|
while (bitBufLen < numBits) {
|
||||||
bitBuf |= this.bytes[this.position++] << bitBufLen;
|
if (endianness === "be")
|
||||||
|
bitBuf = (bitBuf << bitBufLen) | this.bytes[this.position++];
|
||||||
|
else
|
||||||
|
bitBuf |= this.bytes[this.position++] << bitBufLen;
|
||||||
bitBufLen += 8;
|
bitBufLen += 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reverse back to numBits
|
// Reverse back to numBits
|
||||||
if (bitBufLen > numBits) {
|
if (bitBufLen > numBits) {
|
||||||
const excess = bitBufLen - numBits;
|
const excess = bitBufLen - numBits;
|
||||||
bitBuf &= (1 << numBits) - 1;
|
if (endianness === "be")
|
||||||
|
bitBuf >>>= excess;
|
||||||
|
else
|
||||||
|
bitBuf &= (1 << numBits) - 1;
|
||||||
bitBufLen -= excess;
|
bitBufLen -= excess;
|
||||||
this.position--;
|
this.position--;
|
||||||
this.bitPos = 8 - excess;
|
this.bitPos = 8 - excess;
|
||||||
|
@ -133,7 +141,9 @@ export default class Stream {
|
||||||
* @returns {number} The bit mask
|
* @returns {number} The bit mask
|
||||||
*/
|
*/
|
||||||
function bitMask(bitPos) {
|
function bitMask(bitPos) {
|
||||||
return 256 - (1 << bitPos);
|
return endianness === "be" ?
|
||||||
|
(1 << (8 - bitPos)) - 1 :
|
||||||
|
256 - (1 << bitPos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
245
src/core/operations/ParseTCP.mjs
Normal file
245
src/core/operations/ParseTCP.mjs
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
/**
|
||||||
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Operation from "../Operation.mjs";
|
||||||
|
import Stream from "../lib/Stream.mjs";
|
||||||
|
import {toHexFast, fromHex} from "../lib/Hex.mjs";
|
||||||
|
import {toBinary} from "../lib/Binary.mjs";
|
||||||
|
import {objToTable, bytesToLargeNumber} from "../lib/Protocol.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import BigNumber from "bignumber.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse TCP operation
|
||||||
|
*/
|
||||||
|
class ParseTCP extends Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ParseTCP constructor
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.name = "Parse TCP";
|
||||||
|
this.module = "Default";
|
||||||
|
this.description = "Parses a TCP header and payload (if present).";
|
||||||
|
this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol";
|
||||||
|
this.inputType = "string";
|
||||||
|
this.outputType = "json";
|
||||||
|
this.presentType = "html";
|
||||||
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Input format",
|
||||||
|
type: "option",
|
||||||
|
value: ["Hex", "Raw"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
|
* @returns {html}
|
||||||
|
*/
|
||||||
|
run(input, args) {
|
||||||
|
const format = args[0];
|
||||||
|
|
||||||
|
if (format === "Hex") {
|
||||||
|
input = fromHex(input);
|
||||||
|
} else if (format === "Raw") {
|
||||||
|
input = Utils.strToArrayBuffer(input);
|
||||||
|
} else {
|
||||||
|
throw new OperationError("Unrecognised input format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const s = new Stream(new Uint8Array(input));
|
||||||
|
if (s.length < 20) {
|
||||||
|
throw new OperationError("Need at least 20 bytes for a TCP Header");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse Header
|
||||||
|
const TCPPacket = {
|
||||||
|
"Source port": s.readInt(2),
|
||||||
|
"Destination port": s.readInt(2),
|
||||||
|
"Sequence number": bytesToLargeNumber(s.getBytes(4)),
|
||||||
|
"Acknowledgement number": s.readInt(4),
|
||||||
|
"Data offset": s.readBits(4),
|
||||||
|
"Flags": {
|
||||||
|
"Reserved": toBinary(s.readBits(3), "", 3),
|
||||||
|
"NS": s.readBits(1),
|
||||||
|
"CWR": s.readBits(1),
|
||||||
|
"ECE": s.readBits(1),
|
||||||
|
"URG": s.readBits(1),
|
||||||
|
"ACK": s.readBits(1),
|
||||||
|
"PSH": s.readBits(1),
|
||||||
|
"RST": s.readBits(1),
|
||||||
|
"SYN": s.readBits(1),
|
||||||
|
"FIN": s.readBits(1),
|
||||||
|
},
|
||||||
|
"Window size": s.readInt(2),
|
||||||
|
"Checksum": "0x" + toHexFast(s.getBytes(2)),
|
||||||
|
"Urgent pointer": "0x" + toHexFast(s.getBytes(2))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse options if present
|
||||||
|
let windowScaleShift = 0;
|
||||||
|
if (TCPPacket["Data offset"] > 5) {
|
||||||
|
let remainingLength = TCPPacket["Data offset"] * 4 - 20;
|
||||||
|
|
||||||
|
const options = {};
|
||||||
|
while (remainingLength > 0) {
|
||||||
|
const option = {
|
||||||
|
"Kind": s.readInt(1)
|
||||||
|
};
|
||||||
|
|
||||||
|
let opt = { name: "Reserved", length: true };
|
||||||
|
if (Object.prototype.hasOwnProperty.call(TCP_OPTION_KIND_LOOKUP, option.Kind)) {
|
||||||
|
opt = TCP_OPTION_KIND_LOOKUP[option.Kind];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Length and Value fields
|
||||||
|
if (opt.length) {
|
||||||
|
option.Length = s.readInt(1);
|
||||||
|
|
||||||
|
if (option.Length > 2) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(opt, "parser")) {
|
||||||
|
option.Value = opt.parser(s.getBytes(option.Length - 2));
|
||||||
|
} else {
|
||||||
|
option.Value = option.Length <= 6 ?
|
||||||
|
s.readInt(option.Length - 2):
|
||||||
|
"0x" + toHexFast(s.getBytes(option.Length - 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store Window Scale shift for later
|
||||||
|
if (option.Kind === 3 && option.Value) {
|
||||||
|
windowScaleShift = option.Value["Shift count"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options[opt.name] = option;
|
||||||
|
|
||||||
|
const length = option.Length || 1;
|
||||||
|
remainingLength -= length;
|
||||||
|
}
|
||||||
|
TCPPacket.Options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.hasMore()) {
|
||||||
|
TCPPacket.Data = "0x" + toHexFast(s.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Improve values
|
||||||
|
TCPPacket["Data offset"] = `${TCPPacket["Data offset"]} (${TCPPacket["Data offset"] * 4} bytes)`;
|
||||||
|
const trueWndSize = BigNumber(TCPPacket["Window size"]).multipliedBy(BigNumber(2).pow(BigNumber(windowScaleShift)));
|
||||||
|
TCPPacket["Window size"] = `${TCPPacket["Window size"]} (Scaled: ${trueWndSize})`;
|
||||||
|
|
||||||
|
return TCPPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the TCP Packet in a tabular style
|
||||||
|
* @param {Object} data
|
||||||
|
* @returns {html}
|
||||||
|
*/
|
||||||
|
present(data) {
|
||||||
|
return objToTable(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taken from https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml
|
||||||
|
// on 2022-05-30
|
||||||
|
const TCP_OPTION_KIND_LOOKUP = {
|
||||||
|
0: { name: "End of Option List", length: false },
|
||||||
|
1: { name: "No-Operation", length: false },
|
||||||
|
2: { name: "Maximum Segment Size", length: true },
|
||||||
|
3: { name: "Window Scale", length: true, parser: windowScaleParser },
|
||||||
|
4: { name: "SACK Permitted", length: true },
|
||||||
|
5: { name: "SACK", length: true },
|
||||||
|
6: { name: "Echo (obsoleted by option 8)", length: true },
|
||||||
|
7: { name: "Echo Reply (obsoleted by option 8)", length: true },
|
||||||
|
8: { name: "Timestamps", length: true, parser: tcpTimestampParser },
|
||||||
|
9: { name: "Partial Order Connection Permitted (obsolete)", length: true },
|
||||||
|
10: { name: "Partial Order Service Profile (obsolete)", length: true },
|
||||||
|
11: { name: "CC (obsolete)", length: true },
|
||||||
|
12: { name: "CC.NEW (obsolete)", length: true },
|
||||||
|
13: { name: "CC.ECHO (obsolete)", length: true },
|
||||||
|
14: { name: "TCP Alternate Checksum Request (obsolete)", length: true, parser: tcpAlternateChecksumParser },
|
||||||
|
15: { name: "TCP Alternate Checksum Data (obsolete)", length: true },
|
||||||
|
16: { name: "Skeeter", length: true },
|
||||||
|
17: { name: "Bubba", length: true },
|
||||||
|
18: { name: "Trailer Checksum Option", length: true },
|
||||||
|
19: { name: "MD5 Signature Option (obsoleted by option 29)", length: true },
|
||||||
|
20: { name: "SCPS Capabilities", length: true },
|
||||||
|
21: { name: "Selective Negative Acknowledgements", length: true },
|
||||||
|
22: { name: "Record Boundaries", length: true },
|
||||||
|
23: { name: "Corruption experienced", length: true },
|
||||||
|
24: { name: "SNAP", length: true },
|
||||||
|
25: { name: "Unassigned (released 2000-12-18)", length: true },
|
||||||
|
26: { name: "TCP Compression Filter", length: true },
|
||||||
|
27: { name: "Quick-Start Response", length: true },
|
||||||
|
28: { name: "User Timeout Option (also, other known unauthorized use)", length: true },
|
||||||
|
29: { name: "TCP Authentication Option (TCP-AO)", length: true },
|
||||||
|
30: { name: "Multipath TCP (MPTCP)", length: true },
|
||||||
|
69: { name: "Encryption Negotiation (TCP-ENO)", length: true },
|
||||||
|
70: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
|
||||||
|
76: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
|
||||||
|
77: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
|
||||||
|
78: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
|
||||||
|
253: { name: "RFC3692-style Experiment 1 (also improperly used for shipping products) ", length: true },
|
||||||
|
254: { name: "RFC3692-style Experiment 2 (also improperly used for shipping products) ", length: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the TCP Alternate Checksum Request field
|
||||||
|
* @param {Uint8Array} data
|
||||||
|
*/
|
||||||
|
function tcpAlternateChecksumParser(data) {
|
||||||
|
const lookup = {
|
||||||
|
0: "TCP Checksum",
|
||||||
|
1: "8-bit Fletchers's algorithm",
|
||||||
|
2: "16-bit Fletchers's algorithm",
|
||||||
|
3: "Redundant Checksum Avoidance"
|
||||||
|
}[data[0]];
|
||||||
|
|
||||||
|
return `${lookup} (0x${toHexFast(data)})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the TCP Timestamp field
|
||||||
|
* @param {Uint8Array} data
|
||||||
|
*/
|
||||||
|
function tcpTimestampParser(data) {
|
||||||
|
const s = new Stream(data);
|
||||||
|
|
||||||
|
if (s.length !== 8)
|
||||||
|
return `Error: Timestamp field should be 8 bytes long (received 0x${toHexFast(data)})`;
|
||||||
|
|
||||||
|
const tsval = bytesToLargeNumber(s.getBytes(4)),
|
||||||
|
tsecr = bytesToLargeNumber(s.getBytes(4));
|
||||||
|
|
||||||
|
return {
|
||||||
|
"Current Timestamp": tsval,
|
||||||
|
"Echo Reply": tsecr
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the Window Scale field
|
||||||
|
* @param {Uint8Array} data
|
||||||
|
*/
|
||||||
|
function windowScaleParser(data) {
|
||||||
|
if (data.length !== 1)
|
||||||
|
return `Error: Window Scale should be one byte long (received 0x${toHexFast(data)})`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
"Shift count": data[0],
|
||||||
|
"Multiplier": 1 << data[0]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ParseTCP;
|
|
@ -6,7 +6,9 @@
|
||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import Stream from "../lib/Stream.mjs";
|
import Stream from "../lib/Stream.mjs";
|
||||||
import {toHex} from "../lib/Hex.mjs";
|
import {toHexFast, fromHex} from "../lib/Hex.mjs";
|
||||||
|
import {objToTable} from "../lib/Protocol.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,58 +26,61 @@ class ParseUDP extends Operation {
|
||||||
this.module = "Default";
|
this.module = "Default";
|
||||||
this.description = "Parses a UDP header and payload (if present).";
|
this.description = "Parses a UDP header and payload (if present).";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol";
|
this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "string";
|
||||||
this.outputType = "json";
|
this.outputType = "json";
|
||||||
this.presentType = "html";
|
this.presentType = "html";
|
||||||
this.args = [];
|
this.args = [
|
||||||
|
{
|
||||||
|
name: "Input format",
|
||||||
|
type: "option",
|
||||||
|
value: ["Hex", "Raw"]
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ArrayBuffer} input
|
* @param {string} input
|
||||||
|
* @param {Object[]} args
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
if (input.byteLength < 8) {
|
const format = args[0];
|
||||||
throw new OperationError("Need 8 bytes for a UDP Header");
|
|
||||||
|
if (format === "Hex") {
|
||||||
|
input = fromHex(input);
|
||||||
|
} else if (format === "Raw") {
|
||||||
|
input = Utils.strToArrayBuffer(input);
|
||||||
|
} else {
|
||||||
|
throw new OperationError("Unrecognised input format.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const s = new Stream(new Uint8Array(input));
|
const s = new Stream(new Uint8Array(input));
|
||||||
|
if (s.length < 8) {
|
||||||
|
throw new OperationError("Need 8 bytes for a UDP Header");
|
||||||
|
}
|
||||||
|
|
||||||
// Parse Header
|
// Parse Header
|
||||||
const UDPPacket = {
|
const UDPPacket = {
|
||||||
"Source port": s.readInt(2),
|
"Source port": s.readInt(2),
|
||||||
"Destination port": s.readInt(2),
|
"Destination port": s.readInt(2),
|
||||||
"Length": s.readInt(2),
|
"Length": s.readInt(2),
|
||||||
"Checksum": toHex(s.getBytes(2), "")
|
"Checksum": "0x" + toHexFast(s.getBytes(2))
|
||||||
};
|
};
|
||||||
// Parse data if present
|
// Parse data if present
|
||||||
if (s.hasMore()) {
|
if (s.hasMore()) {
|
||||||
UDPPacket.Data = toHex(s.getBytes(UDPPacket.Length - 8), "");
|
UDPPacket.Data = "0x" + toHexFast(s.getBytes(UDPPacket.Length - 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
return UDPPacket;
|
return UDPPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the UDP Packet in a table style
|
* Displays the UDP Packet in a tabular style
|
||||||
* @param {Object} data
|
* @param {Object} data
|
||||||
* @returns {html}
|
* @returns {html}
|
||||||
*/
|
*/
|
||||||
present(data) {
|
present(data) {
|
||||||
const html = [];
|
return objToTable(data);
|
||||||
html.push("<table class='table table-hover table-sm table-bordered table-nonfluid' style='table-layout: fixed'>");
|
|
||||||
html.push("<tr>");
|
|
||||||
html.push("<th>Field</th>");
|
|
||||||
html.push("<th>Value</th>");
|
|
||||||
html.push("</tr>");
|
|
||||||
|
|
||||||
for (const key in data) {
|
|
||||||
html.push("<tr>");
|
|
||||||
html.push("<td style=\"word-wrap:break-word\">" + key + "</td>");
|
|
||||||
html.push("<td>" + data[key] + "</td>");
|
|
||||||
html.push("</tr>");
|
|
||||||
}
|
|
||||||
html.push("</table>");
|
|
||||||
return html.join("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,11 +109,15 @@ class OperationsWaiter {
|
||||||
const matchedOps = [];
|
const matchedOps = [];
|
||||||
const matchedDescs = [];
|
const matchedDescs = [];
|
||||||
|
|
||||||
|
// Create version with no whitespace for the fuzzy match
|
||||||
|
// Helps avoid missing matches e.g. query "TCP " would not find "Parse TCP"
|
||||||
|
const inStrNWS = inStr.replace(/\s/g, "");
|
||||||
|
|
||||||
for (const opName in this.app.operations) {
|
for (const opName in this.app.operations) {
|
||||||
const op = this.app.operations[opName];
|
const op = this.app.operations[opName];
|
||||||
|
|
||||||
// Match op name using fuzzy match
|
// Match op name using fuzzy match
|
||||||
const [nameMatch, score, idxs] = fuzzyMatch(inStr, opName);
|
const [nameMatch, score, idxs] = fuzzyMatch(inStrNWS, opName);
|
||||||
|
|
||||||
// Match description based on exact match
|
// Match description based on exact match
|
||||||
const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase());
|
const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase());
|
||||||
|
|
|
@ -96,6 +96,7 @@ import "./tests/Protobuf.mjs";
|
||||||
import "./tests/ParseSSHHostKey.mjs";
|
import "./tests/ParseSSHHostKey.mjs";
|
||||||
import "./tests/DefangIP.mjs";
|
import "./tests/DefangIP.mjs";
|
||||||
import "./tests/ParseUDP.mjs";
|
import "./tests/ParseUDP.mjs";
|
||||||
|
import "./tests/ParseTCP.mjs";
|
||||||
import "./tests/AvroToJSON.mjs";
|
import "./tests/AvroToJSON.mjs";
|
||||||
import "./tests/Lorenz.mjs";
|
import "./tests/Lorenz.mjs";
|
||||||
import "./tests/LuhnChecksum.mjs";
|
import "./tests/LuhnChecksum.mjs";
|
||||||
|
|
44
tests/operations/tests/ParseTCP.mjs
Normal file
44
tests/operations/tests/ParseTCP.mjs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Parse TCP tests.
|
||||||
|
*
|
||||||
|
* @author n1474335
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
import TestRegister from "../../lib/TestRegister.mjs";
|
||||||
|
|
||||||
|
TestRegister.addTests([
|
||||||
|
{
|
||||||
|
name: "Parse TCP: No options",
|
||||||
|
input: "c2eb0050a138132e70dc9fb9501804025ea70000",
|
||||||
|
expectedMatch: /1026 \(Scaled: 1026\)/,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Parse TCP",
|
||||||
|
args: ["Hex"],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Parse TCP: Options",
|
||||||
|
input: "c2eb0050a1380c1f000000008002faf080950000020405b40103030801010402",
|
||||||
|
expectedMatch: /1460/,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Parse TCP",
|
||||||
|
args: ["Hex"],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Parse TCP: Timestamps",
|
||||||
|
input: "9e90e11574d57b2c00000000a002ffffe5740000020405b40402080aa4e8c8f50000000001030308",
|
||||||
|
expectedMatch: /2766719221/,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
op: "Parse TCP",
|
||||||
|
args: ["Hex"],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]);
|
|
@ -2,7 +2,6 @@
|
||||||
* Parse UDP tests.
|
* Parse UDP tests.
|
||||||
*
|
*
|
||||||
* @author h345983745
|
* @author h345983745
|
||||||
*
|
|
||||||
* @copyright Crown Copyright 2019
|
* @copyright Crown Copyright 2019
|
||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
@ -12,15 +11,11 @@ TestRegister.addTests([
|
||||||
{
|
{
|
||||||
name: "Parse UDP: No Data - JSON",
|
name: "Parse UDP: No Data - JSON",
|
||||||
input: "04 89 00 35 00 2c 01 01",
|
input: "04 89 00 35 00 2c 01 01",
|
||||||
expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0101\"}",
|
expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0x0101\"}",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
|
||||||
op: "From Hex",
|
|
||||||
args: ["Auto"],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
op: "Parse UDP",
|
op: "Parse UDP",
|
||||||
args: [],
|
args: ["Hex"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: "JSON Minify",
|
op: "JSON Minify",
|
||||||
|
@ -30,15 +25,11 @@ TestRegister.addTests([
|
||||||
}, {
|
}, {
|
||||||
name: "Parse UDP: With Data - JSON",
|
name: "Parse UDP: With Data - JSON",
|
||||||
input: "04 89 00 35 00 2c 01 01 02 02",
|
input: "04 89 00 35 00 2c 01 01 02 02",
|
||||||
expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0101\",\"Data\":\"0202\"}",
|
expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0x0101\",\"Data\":\"0x0202\"}",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
|
||||||
op: "From Hex",
|
|
||||||
args: ["Auto"],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
op: "Parse UDP",
|
op: "Parse UDP",
|
||||||
args: [],
|
args: ["Hex"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: "JSON Minify",
|
op: "JSON Minify",
|
||||||
|
@ -51,13 +42,9 @@ TestRegister.addTests([
|
||||||
input: "04 89 00",
|
input: "04 89 00",
|
||||||
expectedOutput: "Need 8 bytes for a UDP Header",
|
expectedOutput: "Need 8 bytes for a UDP Header",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
|
||||||
op: "From Hex",
|
|
||||||
args: ["Auto"],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
op: "Parse UDP",
|
op: "Parse UDP",
|
||||||
args: [],
|
args: ["Hex"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: "JSON Minify",
|
op: "JSON Minify",
|
||||||
|
|
Loading…
Reference in a new issue