cyberchef 9.37.3 → 9.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -13,6 +13,9 @@ All major and minor version changes will be documented in this file. Details of
13
13
 
14
14
  ## Details
15
15
 
16
+ ### [9.38.0] - 2022-05-30
17
+ - Added 'Parse TCP' operation [@n1474335] | [a895d1d]
18
+
16
19
  ### [9.37.0] - 2022-03-29
17
20
  - 'SM4 Encrypt' and 'SM4 Decrypt' operations added [@swesven] | [#1189]
18
21
  - NoPadding options added for CBC and ECB modes in AES, DES and Triple DES Decrypt operations [@swesven] | [#1189]
@@ -135,7 +138,7 @@ All major and minor version changes will be documented in this file. Details of
135
138
 
136
139
  <details>
137
140
  <summary>Click to expand v8 minor versions</summary>
138
-
141
+
139
142
  ### [8.38.0] - 2019-07-03
140
143
  - 'Streebog' and 'GOST hash' operations added [@MShwed] [@n1474335] | [#530]
141
144
 
@@ -288,6 +291,7 @@ All major and minor version changes will be documented in this file. Details of
288
291
 
289
292
 
290
293
 
294
+ [9.38.0]: https://github.com/gchq/CyberChef/releases/tag/v9.38.0
291
295
  [9.37.0]: https://github.com/gchq/CyberChef/releases/tag/v9.37.0
292
296
  [9.36.0]: https://github.com/gchq/CyberChef/releases/tag/v9.36.0
293
297
  [9.35.0]: https://github.com/gchq/CyberChef/releases/tag/v9.35.0
@@ -416,6 +420,7 @@ All major and minor version changes will be documented in this file. Details of
416
420
  [289a417]: https://github.com/gchq/CyberChef/commit/289a417dfb5923de5e1694354ec42a08d9395bfe
417
421
  [e9ca4dc]: https://github.com/gchq/CyberChef/commit/e9ca4dc9caf98f33fd986431cd400c88082a42b8
418
422
  [dd18e52]: https://github.com/gchq/CyberChef/commit/dd18e529939078b89867297b181a584e8b2cc7da
423
+ [a895d1d]: https://github.com/gchq/CyberChef/commit/a895d1d82a2f92d440a0c5eca2bc7c898107b737
419
424
 
420
425
  [#95]: https://github.com/gchq/CyberChef/pull/299
421
426
  [#173]: https://github.com/gchq/CyberChef/pull/173
@@ -502,4 +507,4 @@ All major and minor version changes will be documented in this file. Details of
502
507
  [#1242]: https://github.com/gchq/CyberChef/pull/1242
503
508
  [#1244]: https://github.com/gchq/CyberChef/pull/1244
504
509
  [#1313]: https://github.com/gchq/CyberChef/pull/1313
505
- [#1326]: https://github.com/gchq/CyberChef/pull/1326
510
+ [#1326]: https://github.com/gchq/CyberChef/pull/1326
package/Gruntfile.js CHANGED
@@ -217,7 +217,8 @@ module.exports = function (grunt) {
217
217
  client: {
218
218
  logging: "error",
219
219
  overlay: true
220
- }
220
+ },
221
+ hot: "only"
221
222
  },
222
223
  plugins: [
223
224
  new webpack.DefinePlugin(BUILD_CONSTANTS),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyberchef",
3
- "version": "9.37.3",
3
+ "version": "9.38.0",
4
4
  "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
5
5
  "author": "n1474335 <n1474335@gmail.com>",
6
6
  "homepage": "https://gchq.github.io/CyberChef",
@@ -48,7 +48,7 @@
48
48
  "autoprefixer": "^10.4.4",
49
49
  "babel-loader": "^8.2.4",
50
50
  "babel-plugin-dynamic-import-node": "^2.3.3",
51
- "chromedriver": "^99.0.0",
51
+ "chromedriver": "^101.0.0",
52
52
  "cli-progress": "^3.10.0",
53
53
  "colors": "^1.4.0",
54
54
  "copy-webpack-plugin": "^10.2.4",
@@ -190,6 +190,7 @@
190
190
  "Parse IP range",
191
191
  "Parse IPv6 address",
192
192
  "Parse IPv4 header",
193
+ "Parse TCP",
193
194
  "Parse UDP",
194
195
  "Parse SSH Host Key",
195
196
  "Parse URI",
@@ -9518,6 +9518,25 @@
9518
9518
  }
9519
9519
  ]
9520
9520
  },
9521
+ "Parse TCP": {
9522
+ "module": "Default",
9523
+ "description": "Parses a TCP header and payload (if present).",
9524
+ "infoURL": "https://wikipedia.org/wiki/Transmission_Control_Protocol",
9525
+ "inputType": "string",
9526
+ "outputType": "html",
9527
+ "flowControl": false,
9528
+ "manualBake": false,
9529
+ "args": [
9530
+ {
9531
+ "name": "Input format",
9532
+ "type": "option",
9533
+ "value": [
9534
+ "Hex",
9535
+ "Raw"
9536
+ ]
9537
+ }
9538
+ ]
9539
+ },
9521
9540
  "Parse TLV": {
9522
9541
  "module": "Default",
9523
9542
  "description": "Converts a Type-Length-Value (TLV) encoded string into a JSON object. Can optionally include a <code>Key</code> / <code>Type</code> entry. <br><br>Tags: Key-Length-Value, KLV, Length-Value, LV",
@@ -9548,11 +9567,20 @@
9548
9567
  "module": "Default",
9549
9568
  "description": "Parses a UDP header and payload (if present).",
9550
9569
  "infoURL": "https://wikipedia.org/wiki/User_Datagram_Protocol",
9551
- "inputType": "ArrayBuffer",
9570
+ "inputType": "string",
9552
9571
  "outputType": "html",
9553
9572
  "flowControl": false,
9554
9573
  "manualBake": false,
9555
- "args": []
9574
+ "args": [
9575
+ {
9576
+ "name": "Input format",
9577
+ "type": "option",
9578
+ "value": [
9579
+ "Hex",
9580
+ "Raw"
9581
+ ]
9582
+ }
9583
+ ]
9556
9584
  },
9557
9585
  "Parse UNIX file permissions": {
9558
9586
  "module": "Default",
@@ -97,6 +97,7 @@ import ParseIPRange from "../../operations/ParseIPRange.mjs";
97
97
  import ParseIPv4Header from "../../operations/ParseIPv4Header.mjs";
98
98
  import ParseIPv6Address from "../../operations/ParseIPv6Address.mjs";
99
99
  import ParseSSHHostKey from "../../operations/ParseSSHHostKey.mjs";
100
+ import ParseTCP from "../../operations/ParseTCP.mjs";
100
101
  import ParseTLV from "../../operations/ParseTLV.mjs";
101
102
  import ParseUDP from "../../operations/ParseUDP.mjs";
102
103
  import ParseUNIXFilePermissions from "../../operations/ParseUNIXFilePermissions.mjs";
@@ -263,6 +264,7 @@ OpModules.Default = {
263
264
  "Parse IPv4 header": ParseIPv4Header,
264
265
  "Parse IPv6 address": ParseIPv6Address,
265
266
  "Parse SSH Host Key": ParseSSHHostKey,
267
+ "Parse TCP": ParseTCP,
266
268
  "Parse TLV": ParseTLV,
267
269
  "Parse UDP": ParseUDP,
268
270
  "Parse UNIX file permissions": ParseUNIXFilePermissions,
@@ -13,7 +13,7 @@ import OperationError from "../errors/OperationError.mjs";
13
13
  /**
14
14
  * Convert a byte array into a binary string.
15
15
  *
16
- * @param {Uint8Array|byteArray} data
16
+ * @param {Uint8Array|byteArray|number} data
17
17
  * @param {string} [delim="Space"]
18
18
  * @param {number} [padding=8]
19
19
  * @returns {string}
@@ -26,13 +26,17 @@ import OperationError from "../errors/OperationError.mjs";
26
26
  * toBinary([10,20,30], ":");
27
27
  */
28
28
  export function toBinary(data, delim="Space", padding=8) {
29
- if (!data) return "";
30
-
31
29
  delim = Utils.charRep(delim);
32
30
  let output = "";
33
31
 
34
- for (let i = 0; i < data.length; i++) {
35
- output += data[i].toString(2).padStart(padding, "0") + delim;
32
+ if (data.length) { // array
33
+ for (let i = 0; i < data.length; i++) {
34
+ output += data[i].toString(2).padStart(padding, "0") + delim;
35
+ }
36
+ } else if (typeof data === "number") { // Single value
37
+ return data.toString(2).padStart(padding, "0");
38
+ } else {
39
+ return "";
36
40
  }
37
41
 
38
42
  if (delim.length) {
@@ -3778,8 +3778,8 @@ function parseDEFLATE(stream) {
3778
3778
 
3779
3779
  while (!finalBlock) {
3780
3780
  // Read header
3781
- finalBlock = stream.readBits(1);
3782
- const blockType = stream.readBits(2);
3781
+ finalBlock = stream.readBits(1, "le");
3782
+ const blockType = stream.readBits(2, "le");
3783
3783
 
3784
3784
  if (blockType === 0) {
3785
3785
  /* No compression */
@@ -3798,16 +3798,16 @@ function parseDEFLATE(stream) {
3798
3798
  /* Dynamic Huffman */
3799
3799
 
3800
3800
  // Read the number of liternal and length codes
3801
- const hlit = stream.readBits(5) + 257;
3801
+ const hlit = stream.readBits(5, "le") + 257;
3802
3802
  // Read the number of distance codes
3803
- const hdist = stream.readBits(5) + 1;
3803
+ const hdist = stream.readBits(5, "le") + 1;
3804
3804
  // Read the number of code lengths
3805
- const hclen = stream.readBits(4) + 4;
3805
+ const hclen = stream.readBits(4, "le") + 4;
3806
3806
 
3807
3807
  // Parse code lengths
3808
3808
  const codeLengths = new Uint8Array(huffmanOrder.length);
3809
3809
  for (let i = 0; i < hclen; i++) {
3810
- codeLengths[huffmanOrder[i]] = stream.readBits(3);
3810
+ codeLengths[huffmanOrder[i]] = stream.readBits(3, "le");
3811
3811
  }
3812
3812
 
3813
3813
  // Parse length table
@@ -3819,16 +3819,16 @@ function parseDEFLATE(stream) {
3819
3819
  code = readHuffmanCode(stream, codeLengthsTable);
3820
3820
  switch (code) {
3821
3821
  case 16:
3822
- repeat = 3 + stream.readBits(2);
3822
+ repeat = 3 + stream.readBits(2, "le");
3823
3823
  while (repeat--) lengthTable[i++] = prev;
3824
3824
  break;
3825
3825
  case 17:
3826
- repeat = 3 + stream.readBits(3);
3826
+ repeat = 3 + stream.readBits(3, "le");
3827
3827
  while (repeat--) lengthTable[i++] = 0;
3828
3828
  prev = 0;
3829
3829
  break;
3830
3830
  case 18:
3831
- repeat = 11 + stream.readBits(7);
3831
+ repeat = 11 + stream.readBits(7, "le");
3832
3832
  while (repeat--) lengthTable[i++] = 0;
3833
3833
  prev = 0;
3834
3834
  break;
@@ -3886,11 +3886,11 @@ function parseHuffmanBlock(stream, litTab, distTab) {
3886
3886
  if (code < 256) continue;
3887
3887
 
3888
3888
  // Length code
3889
- stream.readBits(lengthExtraTable[code - 257]);
3889
+ stream.readBits(lengthExtraTable[code - 257], "le");
3890
3890
 
3891
3891
  // Dist code
3892
3892
  code = readHuffmanCode(stream, distTab);
3893
- stream.readBits(distanceExtraTable[code]);
3893
+ stream.readBits(distanceExtraTable[code], "le");
3894
3894
  }
3895
3895
  }
3896
3896
 
@@ -3948,7 +3948,7 @@ function readHuffmanCode(stream, table) {
3948
3948
  const [codeTable, maxCodeLength] = table;
3949
3949
 
3950
3950
  // Read max length
3951
- const bitsBuf = stream.readBits(maxCodeLength);
3951
+ const bitsBuf = stream.readBits(maxCodeLength, "le");
3952
3952
  const codeWithLength = codeTable[bitsBuf & ((1 << maxCodeLength) - 1)];
3953
3953
  const codeLength = codeWithLength >>> 16;
3954
3954
 
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Protocol parsing functions.
3
+ *
4
+ * @author n1474335 [n1474335@gmail.com]
5
+ * @copyright Crown Copyright 2022
6
+ * @license Apache-2.0
7
+ */
8
+
9
+ import BigNumber from "bignumber.js";
10
+ import {toHexFast} from "../lib/Hex.mjs";
11
+
12
+ /**
13
+ * Recursively displays a JSON object as an HTML table
14
+ *
15
+ * @param {Object} obj
16
+ * @returns string
17
+ */
18
+ export function objToTable(obj, nested=false) {
19
+ let html = `<table
20
+ class='table table-sm table-nonfluid ${nested ? "mb-0 table-borderless" : "table-bordered"}'
21
+ style='table-layout: fixed; ${nested ? "margin: -1px !important;" : ""}'>`;
22
+ if (!nested)
23
+ html += `<tr>
24
+ <th>Field</th>
25
+ <th>Value</th>
26
+ </tr>`;
27
+
28
+ for (const key in obj) {
29
+ html += `<tr><td style='word-wrap: break-word'>${key}</td>`;
30
+ if (typeof obj[key] === "object")
31
+ html += `<td style='padding: 0'>${objToTable(obj[key], true)}</td>`;
32
+ else
33
+ html += `<td>${obj[key]}</td>`;
34
+ html += "</tr>";
35
+ }
36
+ html += "</table>";
37
+ return html;
38
+ }
39
+
40
+ /**
41
+ * Converts bytes into a BigNumber string
42
+ * @param {Uint8Array} bs
43
+ * @returns {string}
44
+ */
45
+ export function bytesToLargeNumber(bs) {
46
+ return BigNumber(toHexFast(bs), 16).toString();
47
+ }
@@ -27,15 +27,17 @@ export default class Stream {
27
27
  }
28
28
 
29
29
  /**
30
- * Get a number of bytes from the current position.
30
+ * Get a number of bytes from the current position, or all remaining bytes.
31
31
  *
32
- * @param {number} numBytes
32
+ * @param {number} [numBytes=null]
33
33
  * @returns {Uint8Array}
34
34
  */
35
- getBytes(numBytes) {
35
+ getBytes(numBytes=null) {
36
36
  if (this.position > this.length) return undefined;
37
37
 
38
- const newPosition = this.position + numBytes;
38
+ const newPosition = numBytes !== null ?
39
+ this.position + numBytes :
40
+ this.length;
39
41
  const bytes = this.bytes.slice(this.position, newPosition);
40
42
  this.position = newPosition;
41
43
  this.bitPos = 0;
@@ -91,34 +93,40 @@ export default class Stream {
91
93
  }
92
94
 
93
95
  /**
94
- * Reads a number of bits from the buffer.
95
- *
96
- * @TODO Add endianness
96
+ * Reads a number of bits from the buffer in big or little endian.
97
97
  *
98
98
  * @param {number} numBits
99
+ * @param {string} [endianness="be"]
99
100
  * @returns {number}
100
101
  */
101
- readBits(numBits) {
102
+ readBits(numBits, endianness="be") {
102
103
  if (this.position > this.length) return undefined;
103
104
 
104
105
  let bitBuf = 0,
105
106
  bitBufLen = 0;
106
107
 
107
108
  // Add remaining bits from current byte
108
- bitBuf = (this.bytes[this.position++] & bitMask(this.bitPos)) >>> this.bitPos;
109
+ bitBuf = this.bytes[this.position++] & bitMask(this.bitPos);
110
+ if (endianness !== "be") bitBuf >>>= this.bitPos;
109
111
  bitBufLen = 8 - this.bitPos;
110
112
  this.bitPos = 0;
111
113
 
112
114
  // Not enough bits yet
113
115
  while (bitBufLen < numBits) {
114
- bitBuf |= this.bytes[this.position++] << bitBufLen;
116
+ if (endianness === "be")
117
+ bitBuf = (bitBuf << bitBufLen) | this.bytes[this.position++];
118
+ else
119
+ bitBuf |= this.bytes[this.position++] << bitBufLen;
115
120
  bitBufLen += 8;
116
121
  }
117
122
 
118
123
  // Reverse back to numBits
119
124
  if (bitBufLen > numBits) {
120
125
  const excess = bitBufLen - numBits;
121
- bitBuf &= (1 << numBits) - 1;
126
+ if (endianness === "be")
127
+ bitBuf >>>= excess;
128
+ else
129
+ bitBuf &= (1 << numBits) - 1;
122
130
  bitBufLen -= excess;
123
131
  this.position--;
124
132
  this.bitPos = 8 - excess;
@@ -133,7 +141,9 @@ export default class Stream {
133
141
  * @returns {number} The bit mask
134
142
  */
135
143
  function bitMask(bitPos) {
136
- return 256 - (1 << bitPos);
144
+ return endianness === "be" ?
145
+ (1 << (8 - bitPos)) - 1 :
146
+ 256 - (1 << bitPos);
137
147
  }
138
148
  }
139
149
 
@@ -0,0 +1,245 @@
1
+ /**
2
+ * @author n1474335 [n1474335@gmail.com]
3
+ * @copyright Crown Copyright 2022
4
+ * @license Apache-2.0
5
+ */
6
+
7
+ import Operation from "../Operation.mjs";
8
+ import Stream from "../lib/Stream.mjs";
9
+ import {toHexFast, fromHex} from "../lib/Hex.mjs";
10
+ import {toBinary} from "../lib/Binary.mjs";
11
+ import {objToTable, bytesToLargeNumber} from "../lib/Protocol.mjs";
12
+ import Utils from "../Utils.mjs";
13
+ import OperationError from "../errors/OperationError.mjs";
14
+ import BigNumber from "bignumber.js";
15
+
16
+ /**
17
+ * Parse TCP operation
18
+ */
19
+ class ParseTCP extends Operation {
20
+
21
+ /**
22
+ * ParseTCP constructor
23
+ */
24
+ constructor() {
25
+ super();
26
+
27
+ this.name = "Parse TCP";
28
+ this.module = "Default";
29
+ this.description = "Parses a TCP header and payload (if present).";
30
+ this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol";
31
+ this.inputType = "string";
32
+ this.outputType = "json";
33
+ this.presentType = "html";
34
+ this.args = [
35
+ {
36
+ name: "Input format",
37
+ type: "option",
38
+ value: ["Hex", "Raw"]
39
+ }
40
+ ];
41
+ }
42
+
43
+ /**
44
+ * @param {string} input
45
+ * @param {Object[]} args
46
+ * @returns {html}
47
+ */
48
+ run(input, args) {
49
+ const format = args[0];
50
+
51
+ if (format === "Hex") {
52
+ input = fromHex(input);
53
+ } else if (format === "Raw") {
54
+ input = Utils.strToArrayBuffer(input);
55
+ } else {
56
+ throw new OperationError("Unrecognised input format.");
57
+ }
58
+
59
+ const s = new Stream(new Uint8Array(input));
60
+ if (s.length < 20) {
61
+ throw new OperationError("Need at least 20 bytes for a TCP Header");
62
+ }
63
+
64
+ // Parse Header
65
+ const TCPPacket = {
66
+ "Source port": s.readInt(2),
67
+ "Destination port": s.readInt(2),
68
+ "Sequence number": bytesToLargeNumber(s.getBytes(4)),
69
+ "Acknowledgement number": s.readInt(4),
70
+ "Data offset": s.readBits(4),
71
+ "Flags": {
72
+ "Reserved": toBinary(s.readBits(3), "", 3),
73
+ "NS": s.readBits(1),
74
+ "CWR": s.readBits(1),
75
+ "ECE": s.readBits(1),
76
+ "URG": s.readBits(1),
77
+ "ACK": s.readBits(1),
78
+ "PSH": s.readBits(1),
79
+ "RST": s.readBits(1),
80
+ "SYN": s.readBits(1),
81
+ "FIN": s.readBits(1),
82
+ },
83
+ "Window size": s.readInt(2),
84
+ "Checksum": "0x" + toHexFast(s.getBytes(2)),
85
+ "Urgent pointer": "0x" + toHexFast(s.getBytes(2))
86
+ };
87
+
88
+ // Parse options if present
89
+ let windowScaleShift = 0;
90
+ if (TCPPacket["Data offset"] > 5) {
91
+ let remainingLength = TCPPacket["Data offset"] * 4 - 20;
92
+
93
+ const options = {};
94
+ while (remainingLength > 0) {
95
+ const option = {
96
+ "Kind": s.readInt(1)
97
+ };
98
+
99
+ let opt = { name: "Reserved", length: true };
100
+ if (Object.prototype.hasOwnProperty.call(TCP_OPTION_KIND_LOOKUP, option.Kind)) {
101
+ opt = TCP_OPTION_KIND_LOOKUP[option.Kind];
102
+ }
103
+
104
+ // Add Length and Value fields
105
+ if (opt.length) {
106
+ option.Length = s.readInt(1);
107
+
108
+ if (option.Length > 2) {
109
+ if (Object.prototype.hasOwnProperty.call(opt, "parser")) {
110
+ option.Value = opt.parser(s.getBytes(option.Length - 2));
111
+ } else {
112
+ option.Value = option.Length <= 6 ?
113
+ s.readInt(option.Length - 2):
114
+ "0x" + toHexFast(s.getBytes(option.Length - 2));
115
+ }
116
+
117
+ // Store Window Scale shift for later
118
+ if (option.Kind === 3 && option.Value) {
119
+ windowScaleShift = option.Value["Shift count"];
120
+ }
121
+ }
122
+ }
123
+ options[opt.name] = option;
124
+
125
+ const length = option.Length || 1;
126
+ remainingLength -= length;
127
+ }
128
+ TCPPacket.Options = options;
129
+ }
130
+
131
+ if (s.hasMore()) {
132
+ TCPPacket.Data = "0x" + toHexFast(s.getBytes());
133
+ }
134
+
135
+ // Improve values
136
+ TCPPacket["Data offset"] = `${TCPPacket["Data offset"]} (${TCPPacket["Data offset"] * 4} bytes)`;
137
+ const trueWndSize = BigNumber(TCPPacket["Window size"]).multipliedBy(BigNumber(2).pow(BigNumber(windowScaleShift)));
138
+ TCPPacket["Window size"] = `${TCPPacket["Window size"]} (Scaled: ${trueWndSize})`;
139
+
140
+ return TCPPacket;
141
+ }
142
+
143
+ /**
144
+ * Displays the TCP Packet in a tabular style
145
+ * @param {Object} data
146
+ * @returns {html}
147
+ */
148
+ present(data) {
149
+ return objToTable(data);
150
+ }
151
+
152
+ }
153
+
154
+ // Taken from https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml
155
+ // on 2022-05-30
156
+ const TCP_OPTION_KIND_LOOKUP = {
157
+ 0: { name: "End of Option List", length: false },
158
+ 1: { name: "No-Operation", length: false },
159
+ 2: { name: "Maximum Segment Size", length: true },
160
+ 3: { name: "Window Scale", length: true, parser: windowScaleParser },
161
+ 4: { name: "SACK Permitted", length: true },
162
+ 5: { name: "SACK", length: true },
163
+ 6: { name: "Echo (obsoleted by option 8)", length: true },
164
+ 7: { name: "Echo Reply (obsoleted by option 8)", length: true },
165
+ 8: { name: "Timestamps", length: true, parser: tcpTimestampParser },
166
+ 9: { name: "Partial Order Connection Permitted (obsolete)", length: true },
167
+ 10: { name: "Partial Order Service Profile (obsolete)", length: true },
168
+ 11: { name: "CC (obsolete)", length: true },
169
+ 12: { name: "CC.NEW (obsolete)", length: true },
170
+ 13: { name: "CC.ECHO (obsolete)", length: true },
171
+ 14: { name: "TCP Alternate Checksum Request (obsolete)", length: true, parser: tcpAlternateChecksumParser },
172
+ 15: { name: "TCP Alternate Checksum Data (obsolete)", length: true },
173
+ 16: { name: "Skeeter", length: true },
174
+ 17: { name: "Bubba", length: true },
175
+ 18: { name: "Trailer Checksum Option", length: true },
176
+ 19: { name: "MD5 Signature Option (obsoleted by option 29)", length: true },
177
+ 20: { name: "SCPS Capabilities", length: true },
178
+ 21: { name: "Selective Negative Acknowledgements", length: true },
179
+ 22: { name: "Record Boundaries", length: true },
180
+ 23: { name: "Corruption experienced", length: true },
181
+ 24: { name: "SNAP", length: true },
182
+ 25: { name: "Unassigned (released 2000-12-18)", length: true },
183
+ 26: { name: "TCP Compression Filter", length: true },
184
+ 27: { name: "Quick-Start Response", length: true },
185
+ 28: { name: "User Timeout Option (also, other known unauthorized use)", length: true },
186
+ 29: { name: "TCP Authentication Option (TCP-AO)", length: true },
187
+ 30: { name: "Multipath TCP (MPTCP)", length: true },
188
+ 69: { name: "Encryption Negotiation (TCP-ENO)", length: true },
189
+ 70: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
190
+ 76: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
191
+ 77: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
192
+ 78: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true },
193
+ 253: { name: "RFC3692-style Experiment 1 (also improperly used for shipping products) ", length: true },
194
+ 254: { name: "RFC3692-style Experiment 2 (also improperly used for shipping products) ", length: true }
195
+ };
196
+
197
+ /**
198
+ * Parses the TCP Alternate Checksum Request field
199
+ * @param {Uint8Array} data
200
+ */
201
+ function tcpAlternateChecksumParser(data) {
202
+ const lookup = {
203
+ 0: "TCP Checksum",
204
+ 1: "8-bit Fletchers's algorithm",
205
+ 2: "16-bit Fletchers's algorithm",
206
+ 3: "Redundant Checksum Avoidance"
207
+ }[data[0]];
208
+
209
+ return `${lookup} (0x${toHexFast(data)})`;
210
+ }
211
+
212
+ /**
213
+ * Parses the TCP Timestamp field
214
+ * @param {Uint8Array} data
215
+ */
216
+ function tcpTimestampParser(data) {
217
+ const s = new Stream(data);
218
+
219
+ if (s.length !== 8)
220
+ return `Error: Timestamp field should be 8 bytes long (received 0x${toHexFast(data)})`;
221
+
222
+ const tsval = bytesToLargeNumber(s.getBytes(4)),
223
+ tsecr = bytesToLargeNumber(s.getBytes(4));
224
+
225
+ return {
226
+ "Current Timestamp": tsval,
227
+ "Echo Reply": tsecr
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Parses the Window Scale field
233
+ * @param {Uint8Array} data
234
+ */
235
+ function windowScaleParser(data) {
236
+ if (data.length !== 1)
237
+ return `Error: Window Scale should be one byte long (received 0x${toHexFast(data)})`;
238
+
239
+ return {
240
+ "Shift count": data[0],
241
+ "Multiplier": 1 << data[0]
242
+ };
243
+ }
244
+
245
+ export default ParseTCP;
@@ -6,7 +6,9 @@
6
6
 
7
7
  import Operation from "../Operation.mjs";
8
8
  import Stream from "../lib/Stream.mjs";
9
- import {toHex} from "../lib/Hex.mjs";
9
+ import {toHexFast, fromHex} from "../lib/Hex.mjs";
10
+ import {objToTable} from "../lib/Protocol.mjs";
11
+ import Utils from "../Utils.mjs";
10
12
  import OperationError from "../errors/OperationError.mjs";
11
13
 
12
14
  /**
@@ -24,58 +26,61 @@ class ParseUDP extends Operation {
24
26
  this.module = "Default";
25
27
  this.description = "Parses a UDP header and payload (if present).";
26
28
  this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol";
27
- this.inputType = "ArrayBuffer";
29
+ this.inputType = "string";
28
30
  this.outputType = "json";
29
31
  this.presentType = "html";
30
- this.args = [];
32
+ this.args = [
33
+ {
34
+ name: "Input format",
35
+ type: "option",
36
+ value: ["Hex", "Raw"]
37
+ }
38
+ ];
31
39
  }
32
40
 
33
41
  /**
34
- * @param {ArrayBuffer} input
42
+ * @param {string} input
43
+ * @param {Object[]} args
35
44
  * @returns {Object}
36
45
  */
37
46
  run(input, args) {
38
- if (input.byteLength < 8) {
39
- throw new OperationError("Need 8 bytes for a UDP Header");
47
+ const format = args[0];
48
+
49
+ if (format === "Hex") {
50
+ input = fromHex(input);
51
+ } else if (format === "Raw") {
52
+ input = Utils.strToArrayBuffer(input);
53
+ } else {
54
+ throw new OperationError("Unrecognised input format.");
40
55
  }
41
56
 
42
57
  const s = new Stream(new Uint8Array(input));
58
+ if (s.length < 8) {
59
+ throw new OperationError("Need 8 bytes for a UDP Header");
60
+ }
61
+
43
62
  // Parse Header
44
63
  const UDPPacket = {
45
64
  "Source port": s.readInt(2),
46
65
  "Destination port": s.readInt(2),
47
66
  "Length": s.readInt(2),
48
- "Checksum": toHex(s.getBytes(2), "")
67
+ "Checksum": "0x" + toHexFast(s.getBytes(2))
49
68
  };
50
69
  // Parse data if present
51
70
  if (s.hasMore()) {
52
- UDPPacket.Data = toHex(s.getBytes(UDPPacket.Length - 8), "");
71
+ UDPPacket.Data = "0x" + toHexFast(s.getBytes(UDPPacket.Length - 8));
53
72
  }
54
73
 
55
74
  return UDPPacket;
56
75
  }
57
76
 
58
77
  /**
59
- * Displays the UDP Packet in a table style
78
+ * Displays the UDP Packet in a tabular style
60
79
  * @param {Object} data
61
80
  * @returns {html}
62
81
  */
63
82
  present(data) {
64
- const html = [];
65
- html.push("<table class='table table-hover table-sm table-bordered table-nonfluid' style='table-layout: fixed'>");
66
- html.push("<tr>");
67
- html.push("<th>Field</th>");
68
- html.push("<th>Value</th>");
69
- html.push("</tr>");
70
-
71
- for (const key in data) {
72
- html.push("<tr>");
73
- html.push("<td style=\"word-wrap:break-word\">" + key + "</td>");
74
- html.push("<td>" + data[key] + "</td>");
75
- html.push("</tr>");
76
- }
77
- html.push("</table>");
78
- return html.join("");
83
+ return objToTable(data);
79
84
  }
80
85
 
81
86
  }
@@ -229,6 +229,7 @@ import ParseIPv6Address from "./ParseIPv6Address.mjs";
229
229
  import ParseObjectIDTimestamp from "./ParseObjectIDTimestamp.mjs";
230
230
  import ParseQRCode from "./ParseQRCode.mjs";
231
231
  import ParseSSHHostKey from "./ParseSSHHostKey.mjs";
232
+ import ParseTCP from "./ParseTCP.mjs";
232
233
  import ParseTLV from "./ParseTLV.mjs";
233
234
  import ParseUDP from "./ParseUDP.mjs";
234
235
  import ParseUNIXFilePermissions from "./ParseUNIXFilePermissions.mjs";
@@ -601,6 +602,7 @@ export {
601
602
  ParseObjectIDTimestamp,
602
603
  ParseQRCode,
603
604
  ParseSSHHostKey,
605
+ ParseTCP,
604
606
  ParseTLV,
605
607
  ParseUDP,
606
608
  ParseUNIXFilePermissions,
@@ -230,6 +230,7 @@ import {
230
230
  ParseObjectIDTimestamp as core_ParseObjectIDTimestamp,
231
231
  ParseQRCode as core_ParseQRCode,
232
232
  ParseSSHHostKey as core_ParseSSHHostKey,
233
+ ParseTCP as core_ParseTCP,
233
234
  ParseTLV as core_ParseTLV,
234
235
  ParseUDP as core_ParseUDP,
235
236
  ParseUNIXFilePermissions as core_ParseUNIXFilePermissions,
@@ -602,6 +603,7 @@ function generateChef() {
602
603
  "parseObjectIDTimestamp": _wrap(core_ParseObjectIDTimestamp),
603
604
  "parseQRCode": _wrap(core_ParseQRCode),
604
605
  "parseSSHHostKey": _wrap(core_ParseSSHHostKey),
606
+ "parseTCP": _wrap(core_ParseTCP),
605
607
  "parseTLV": _wrap(core_ParseTLV),
606
608
  "parseUDP": _wrap(core_ParseUDP),
607
609
  "parseUNIXFilePermissions": _wrap(core_ParseUNIXFilePermissions),
@@ -991,6 +993,7 @@ const parseIPv6Address = chef.parseIPv6Address;
991
993
  const parseObjectIDTimestamp = chef.parseObjectIDTimestamp;
992
994
  const parseQRCode = chef.parseQRCode;
993
995
  const parseSSHHostKey = chef.parseSSHHostKey;
996
+ const parseTCP = chef.parseTCP;
994
997
  const parseTLV = chef.parseTLV;
995
998
  const parseUDP = chef.parseUDP;
996
999
  const parseUNIXFilePermissions = chef.parseUNIXFilePermissions;
@@ -1365,6 +1368,7 @@ const operations = [
1365
1368
  parseObjectIDTimestamp,
1366
1369
  parseQRCode,
1367
1370
  parseSSHHostKey,
1371
+ parseTCP,
1368
1372
  parseTLV,
1369
1373
  parseUDP,
1370
1374
  parseUNIXFilePermissions,
@@ -1743,6 +1747,7 @@ export {
1743
1747
  parseObjectIDTimestamp,
1744
1748
  parseQRCode,
1745
1749
  parseSSHHostKey,
1750
+ parseTCP,
1746
1751
  parseTLV,
1747
1752
  parseUDP,
1748
1753
  parseUNIXFilePermissions,
@@ -109,11 +109,15 @@ class OperationsWaiter {
109
109
  const matchedOps = [];
110
110
  const matchedDescs = [];
111
111
 
112
+ // Create version with no whitespace for the fuzzy match
113
+ // Helps avoid missing matches e.g. query "TCP " would not find "Parse TCP"
114
+ const inStrNWS = inStr.replace(/\s/g, "");
115
+
112
116
  for (const opName in this.app.operations) {
113
117
  const op = this.app.operations[opName];
114
118
 
115
119
  // Match op name using fuzzy match
116
- const [nameMatch, score, idxs] = fuzzyMatch(inStr, opName);
120
+ const [nameMatch, score, idxs] = fuzzyMatch(inStrNWS, opName);
117
121
 
118
122
  // Match description based on exact match
119
123
  const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase());
@@ -96,6 +96,7 @@ import "./tests/Protobuf.mjs";
96
96
  import "./tests/ParseSSHHostKey.mjs";
97
97
  import "./tests/DefangIP.mjs";
98
98
  import "./tests/ParseUDP.mjs";
99
+ import "./tests/ParseTCP.mjs";
99
100
  import "./tests/AvroToJSON.mjs";
100
101
  import "./tests/Lorenz.mjs";
101
102
  import "./tests/LuhnChecksum.mjs";
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Parse TCP tests.
3
+ *
4
+ * @author n1474335
5
+ * @copyright Crown Copyright 2022
6
+ * @license Apache-2.0
7
+ */
8
+ import TestRegister from "../../lib/TestRegister.mjs";
9
+
10
+ TestRegister.addTests([
11
+ {
12
+ name: "Parse TCP: No options",
13
+ input: "c2eb0050a138132e70dc9fb9501804025ea70000",
14
+ expectedMatch: /1026 \(Scaled: 1026\)/,
15
+ recipeConfig: [
16
+ {
17
+ op: "Parse TCP",
18
+ args: ["Hex"],
19
+ }
20
+ ],
21
+ },
22
+ {
23
+ name: "Parse TCP: Options",
24
+ input: "c2eb0050a1380c1f000000008002faf080950000020405b40103030801010402",
25
+ expectedMatch: /1460/,
26
+ recipeConfig: [
27
+ {
28
+ op: "Parse TCP",
29
+ args: ["Hex"],
30
+ }
31
+ ],
32
+ },
33
+ {
34
+ name: "Parse TCP: Timestamps",
35
+ input: "9e90e11574d57b2c00000000a002ffffe5740000020405b40402080aa4e8c8f50000000001030308",
36
+ expectedMatch: /2766719221/,
37
+ recipeConfig: [
38
+ {
39
+ op: "Parse TCP",
40
+ args: ["Hex"],
41
+ }
42
+ ],
43
+ }
44
+ ]);
@@ -2,7 +2,6 @@
2
2
  * Parse UDP tests.
3
3
  *
4
4
  * @author h345983745
5
- *
6
5
  * @copyright Crown Copyright 2019
7
6
  * @license Apache-2.0
8
7
  */
@@ -12,15 +11,11 @@ TestRegister.addTests([
12
11
  {
13
12
  name: "Parse UDP: No Data - JSON",
14
13
  input: "04 89 00 35 00 2c 01 01",
15
- expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0101\"}",
14
+ expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0x0101\"}",
16
15
  recipeConfig: [
17
- {
18
- op: "From Hex",
19
- args: ["Auto"],
20
- },
21
16
  {
22
17
  op: "Parse UDP",
23
- args: [],
18
+ args: ["Hex"],
24
19
  },
25
20
  {
26
21
  op: "JSON Minify",
@@ -30,15 +25,11 @@ TestRegister.addTests([
30
25
  }, {
31
26
  name: "Parse UDP: With Data - JSON",
32
27
  input: "04 89 00 35 00 2c 01 01 02 02",
33
- expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0101\",\"Data\":\"0202\"}",
28
+ expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0x0101\",\"Data\":\"0x0202\"}",
34
29
  recipeConfig: [
35
- {
36
- op: "From Hex",
37
- args: ["Auto"],
38
- },
39
30
  {
40
31
  op: "Parse UDP",
41
- args: [],
32
+ args: ["Hex"],
42
33
  },
43
34
  {
44
35
  op: "JSON Minify",
@@ -51,13 +42,9 @@ TestRegister.addTests([
51
42
  input: "04 89 00",
52
43
  expectedOutput: "Need 8 bytes for a UDP Header",
53
44
  recipeConfig: [
54
- {
55
- op: "From Hex",
56
- args: ["Auto"],
57
- },
58
45
  {
59
46
  op: "Parse UDP",
60
- args: [],
47
+ args: ["Hex"],
61
48
  },
62
49
  {
63
50
  op: "JSON Minify",