2
0
mirror of https://github.com/gchq/CyberChef synced 2025-12-16 00:04:20 +00:00

Compare commits

..

6 Commits

Author SHA1 Message Date
n1474335
7492b874cf 9.32.0 2021-08-18 17:23:43 +01:00
n1474335
9ea21af61f Updated CHANGELOG 2021-08-18 17:23:38 +01:00
n1474335
dd18e52993 Protobuf operations improved to enable full and partial schema support 2021-08-18 17:22:09 +01:00
n1474335
a4a13666e6 9.31.0 2021-08-10 16:51:28 +01:00
n1474335
07ef4da892 Updated CHANGELOG 2021-08-10 16:50:59 +01:00
n1474335
e9ca4dc9ca Added HASSH operations 2021-08-10 16:48:35 +01:00
15 changed files with 1121 additions and 36 deletions

View File

@@ -13,8 +13,14 @@ All major and minor version changes will be documented in this file. Details of
## Details
### [9.32.0] - 2021-08-18
- 'Protobuf Encode' operation added and decode operation modified to allow decoding with full and partial schemas [@n1474335] | [dd18e52]
### [9.31.0] - 2021-08-10
- 'HASSH Client Fingerprint' and 'HASSH Server Fingerprint' operations added [@n1474335] | [e9ca4dc]
### [9.30.0] - 2021-08-10
- 'JA3S Fingerprint' operation added [@n1474335] | [289a417d]
- 'JA3S Fingerprint' operation added [@n1474335] | [289a417]
### [9.29.0] - 2021-07-28
- 'JA3 Fingerprint' operation added [@n1474335] | [9a33498]
@@ -265,6 +271,8 @@ All major and minor version changes will be documented in this file. Details of
[9.32.0]: https://github.com/gchq/CyberChef/releases/tag/v9.32.0
[9.31.0]: https://github.com/gchq/CyberChef/releases/tag/v9.31.0
[9.30.0]: https://github.com/gchq/CyberChef/releases/tag/v9.30.0
[9.29.0]: https://github.com/gchq/CyberChef/releases/tag/v9.29.0
[9.28.0]: https://github.com/gchq/CyberChef/releases/tag/v9.28.0
@@ -379,7 +387,9 @@ All major and minor version changes will be documented in this file. Details of
[8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7
[9a33498]: https://github.com/gchq/CyberChef/commit/9a33498fed26a8df9c9f35f39a78a174bf50a513
[289a417d]: https://github.com/gchq/CyberChef/commit/289a417d
[289a417]: https://github.com/gchq/CyberChef/commit/289a417dfb5923de5e1694354ec42a08d9395bfe
[e9ca4dc]: https://github.com/gchq/CyberChef/commit/e9ca4dc9caf98f33fd986431cd400c88082a42b8
[dd18e52]: https://github.com/gchq/CyberChef/commit/dd18e529939078b89867297b181a584e8b2cc7da
[#95]: https://github.com/gchq/CyberChef/pull/299
[#173]: https://github.com/gchq/CyberChef/pull/173

89
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "9.30.0",
"version": "9.32.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -3084,6 +3084,60 @@
"integrity": "sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA==",
"dev": true
},
"@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78="
},
"@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
},
"@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
},
"@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A="
},
"@protobufjs/fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
"integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=",
"requires": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
}
},
"@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E="
},
"@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik="
},
"@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0="
},
"@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q="
},
"@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
},
"@testim/chrome-version": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.0.7.tgz",
@@ -3144,6 +3198,11 @@
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
"dev": true
},
"@types/long": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
"integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w=="
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@@ -3153,8 +3212,7 @@
"@types/node": {
"version": "14.14.22",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz",
"integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==",
"dev": true
"integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw=="
},
"@types/parse-json": {
"version": "4.0.0",
@@ -10075,6 +10133,11 @@
"loglevel": "^1.4.0"
}
},
"long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -12228,6 +12291,26 @@
"winston": "2.x"
}
},
"protobufjs": {
"version": "6.11.2",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz",
"integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==",
"requires": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/long": "^4.0.1",
"@types/node": ">=13.7.0",
"long": "^4.0.0"
}
},
"proxy-addr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "cyberchef",
"version": "9.30.0",
"version": "9.32.0",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef",
@@ -146,6 +146,7 @@
"path": "^0.12.7",
"popper.js": "^1.16.1",
"process": "^0.11.10",
"protobufjs": "^6.11.2",
"qr-image": "^3.2.0",
"scryptsy": "^2.1.0",
"snackbarjs": "^1.1.0",

3
src/core/config/Categories.json Executable file → Normal file
View File

@@ -191,10 +191,13 @@
"URL Encode",
"URL Decode",
"Protobuf Decode",
"Protobuf Encode",
"VarInt Encode",
"VarInt Decode",
"JA3 Fingerprint",
"JA3S Fingerprint",
"HASSH Client Fingerprint",
"HASSH Server Fingerprint",
"Format MAC addresses",
"Change IP format",
"Group IP addresses",

View File

@@ -1,4 +1,5 @@
import Utils from "../Utils.mjs";
import protobuf from "protobufjs";
/**
* Protobuf lib. Contains functions to decode protobuf serialised
@@ -32,9 +33,10 @@ class Protobuf {
this.MSB = 0x80;
this.VALUE = 0x7f;
// Declare offset and length
// Declare offset, length, and field type object
this.offset = 0;
this.LENGTH = data.length;
this.fieldTypes = {};
}
// Public Functions
@@ -76,15 +78,281 @@ class Protobuf {
return pb._varInt();
}
/**
* Encode input JSON according to the given schema
*
* @param {Object} input
* @param {Object []} args
* @returns {Object}
*/
static encode(input, args) {
this.updateProtoRoot(args[0]);
if (!this.mainMessageName) {
throw new Error("Schema Error: Schema not defined");
}
const message = this.parsedProto.root.nested[this.mainMessageName];
// Convert input into instance of message, and verify instance
input = message.fromObject(input);
const error = message.verify(input);
if (error) {
throw new Error("Input Error: " + error);
}
// Encode input
const output = message.encode(input).finish();
return new Uint8Array(output).buffer;
}
/**
* Parse Protobuf data
*
* @param {byteArray} input
* @returns {Object}
*/
static decode(input) {
static decode(input, args) {
this.updateProtoRoot(args[0]);
this.showUnknownFields = args[1];
this.showTypes = args[2];
return this.mergeDecodes(input);
}
/**
* Update the parsedProto, throw parsing errors
*
* @param {string} protoText
*/
static updateProtoRoot(protoText) {
try {
this.parsedProto = protobuf.parse(protoText);
if (this.parsedProto.package) {
this.parsedProto.root = this.parsedProto.root.nested[this.parsedProto.package];
}
this.updateMainMessageName();
} catch (error) {
throw new Error("Schema " + error);
}
}
/**
* Set mainMessageName to the first instance of a message defined in the schema that is not a submessage
*
*/
static updateMainMessageName() {
const messageNames = [];
const fieldTypes = [];
this.parsedProto.root.nestedArray.forEach(block => {
if (block.constructor.name === "Type") {
messageNames.push(block.name);
this.parsedProto.root.nested[block.name].fieldsArray.forEach(field => {
fieldTypes.push(field.type);
});
}
});
if (messageNames.length === 0) {
this.mainMessageName = null;
} else {
for (const name of messageNames) {
if (!fieldTypes.includes(name)) {
this.mainMessageName = name;
break;
}
}
this.mainMessageName = messageNames[0];
}
}
/**
* Decode input using Protobufjs package and raw methods, compare, and merge results
*
* @param {byteArray} input
* @returns {Object}
*/
static mergeDecodes(input) {
const pb = new Protobuf(input);
return pb._parse();
let rawDecode = pb._parse();
let message;
if (this.showTypes) {
rawDecode = this.showRawTypes(rawDecode, pb.fieldTypes);
this.parsedProto.root = this.appendTypesToFieldNames(this.parsedProto.root);
}
try {
message = this.parsedProto.root.nested[this.mainMessageName];
const packageDecode = message.toObject(message.decode(input), {
bytes: String,
longs: Number,
enums: String,
defualts: true
});
const output = {};
if (this.showUnknownFields) {
output[message.name] = packageDecode;
output["Unknown Fields"] = this.compareFields(rawDecode, message);
return output;
} else {
return packageDecode;
}
} catch (error) {
if (message) {
throw new Error("Input " + error);
} else {
return rawDecode;
}
}
}
/**
* Replace fieldnames with fieldname and type
*
* @param {Object} schemaRoot
* @returns {Object}
*/
static appendTypesToFieldNames(schemaRoot) {
for (const block of schemaRoot.nestedArray) {
if (block.constructor.name === "Type") {
for (const [fieldName, fieldData] of Object.entries(block.fields)) {
schemaRoot.nested[block.name].remove(block.fields[fieldName]);
schemaRoot.nested[block.name].add(new protobuf.Field(`${fieldName} (${fieldData.type})`, fieldData.id, fieldData.type, fieldData.rule));
}
}
}
return schemaRoot;
}
/**
* Add field type to field name for fields in the raw decoded output
*
* @param {Object} rawDecode
* @param {Object} fieldTypes
* @returns {Object}
*/
static showRawTypes(rawDecode, fieldTypes) {
for (const [fieldNum, value] of Object.entries(rawDecode)) {
const fieldType = fieldTypes[fieldNum];
let outputFieldValue;
let outputFieldType;
// Submessages
if (isNaN(fieldType)) {
outputFieldType = 2;
// Repeated submessages
if (Array.isArray(value)) {
const fieldInstances = [];
for (const instance of Object.keys(value)) {
if (typeof(value[instance]) !== "string") {
fieldInstances.push(this.showRawTypes(value[instance], fieldType));
} else {
fieldInstances.push(value[instance]);
}
}
outputFieldValue = fieldInstances;
// Single submessage
} else {
outputFieldValue = this.showRawTypes(value, fieldType);
}
// Non-submessage field
} else {
outputFieldType = fieldType;
outputFieldValue = value;
}
// Substitute fieldNum with field number and type
rawDecode[`field #${fieldNum}: ${this.getTypeInfo(outputFieldType)}`] = outputFieldValue;
delete rawDecode[fieldNum];
}
return rawDecode;
}
/**
* Compare raw decode to package decode and return discrepancies
*
* @param rawDecodedMessage
* @param schemaMessage
* @returns {Object}
*/
static compareFields(rawDecodedMessage, schemaMessage) {
// Define message data using raw decode output and schema
const schemaFieldProperties = {};
const schemaFieldNames = Object.keys(schemaMessage.fields);
schemaFieldNames.forEach(field => schemaFieldProperties[schemaMessage.fields[field].id] = field);
// Loop over each field present in the raw decode output
for (const fieldName in rawDecodedMessage) {
let fieldId;
if (isNaN(fieldName)) {
fieldId = fieldName.match(/^field #(\d+)/)[1];
} else {
fieldId = fieldName;
}
// Check if this field is defined in the schema
if (fieldId in schemaFieldProperties) {
const schemaFieldName = schemaFieldProperties[fieldId];
// Extract the current field data from the raw decode and schema
const rawFieldData = rawDecodedMessage[fieldName];
const schemaField = schemaMessage.fields[schemaFieldName];
// Check for repeated fields
if (Array.isArray(rawFieldData) && !schemaField.repeated) {
rawDecodedMessage[`(${schemaMessage.name}) ${schemaFieldName} is a repeated field`] = rawFieldData;
}
// Check for submessage fields
if (schemaField.resolvedType !== null && schemaField.resolvedType.constructor.name === "Type") {
const subMessageType = schemaMessage.fields[schemaFieldName].type;
const schemaSubMessage = this.parsedProto.root.nested[subMessageType];
const rawSubMessages = rawDecodedMessage[fieldName];
let rawDecodedSubMessage = {};
// Squash multiple submessage instances into one submessage
if (Array.isArray(rawSubMessages)) {
rawSubMessages.forEach(subMessageInstance => {
const instanceFields = Object.entries(subMessageInstance);
instanceFields.forEach(subField => {
rawDecodedSubMessage[subField[0]] = subField[1];
});
});
} else {
rawDecodedSubMessage = rawSubMessages;
}
// Treat submessage as own message and compare its fields
rawDecodedSubMessage = Protobuf.compareFields(rawDecodedSubMessage, schemaSubMessage);
if (Object.entries(rawDecodedSubMessage).length !== 0) {
rawDecodedMessage[`${schemaFieldName} (${subMessageType}) has missing fields`] = rawDecodedSubMessage;
}
}
delete rawDecodedMessage[fieldName];
}
}
return rawDecodedMessage;
}
/**
* Returns wiretype information for input wiretype number
*
* @param {number} wireType
* @returns {string}
*/
static getTypeInfo(wireType) {
switch (wireType) {
case 0:
return "VarInt (e.g. int32, bool)";
case 1:
return "64-Bit (e.g. fixed64, double)";
case 2:
return "L-delim (e.g. string, message)";
case 5:
return "32-Bit (e.g. fixed32, float)";
}
}
// Private Class Functions
@@ -143,6 +411,11 @@ class Protobuf {
const header = this._fieldHeader();
const type = header.type;
const key = header.key;
if (typeof(this.fieldTypes[key]) !== "object") {
this.fieldTypes[key] = type;
}
switch (type) {
// varint
case 0:
@@ -152,7 +425,7 @@ class Protobuf {
return { "key": key, "value": this._uint64() };
// length delimited
case 2:
return { "key": key, "value": this._lenDelim() };
return { "key": key, "value": this._lenDelim(key) };
// fixed 32
case 5:
return { "key": key, "value": this._uint32() };
@@ -237,10 +510,10 @@ class Protobuf {
* @returns {number}
*/
_uint64() {
// Read off a Uint64
let num = this.data[this.offset++] * 0x1000000 + (this.data[this.offset++] << 16) + (this.data[this.offset++] << 8) + this.data[this.offset++];
num = num * 0x100000000 + this.data[this.offset++] * 0x1000000 + (this.data[this.offset++] << 16) + (this.data[this.offset++] << 8) + this.data[this.offset++];
return num;
// Read off a Uint64 with little-endian
const lowerHalf = this.data[this.offset++] + (this.data[this.offset++] * 0x100) + (this.data[this.offset++] * 0x10000) + this.data[this.offset++] * 0x1000000;
const upperHalf = this.data[this.offset++] + (this.data[this.offset++] * 0x100) + (this.data[this.offset++] * 0x10000) + this.data[this.offset++] * 0x1000000;
return upperHalf * 0x100000000 + lowerHalf;
}
/**
@@ -249,7 +522,7 @@ class Protobuf {
* @private
* @returns {Object|string}
*/
_lenDelim() {
_lenDelim(fieldNum) {
// Read off the field length
const length = this._varInt();
const fieldBytes = this.data.slice(this.offset, this.offset + length);
@@ -258,6 +531,10 @@ class Protobuf {
// Attempt to parse as a new Protobuf Object
const pbObject = new Protobuf(fieldBytes);
field = pbObject._parse();
// Set field types object
this.fieldTypes[fieldNum] = {...this.fieldTypes[fieldNum], ...pbObject.fieldTypes};
} catch (err) {
// Otherwise treat as bytes
field = Utils.byteArrayToChars(fieldBytes);
@@ -276,7 +553,7 @@ class Protobuf {
_uint32() {
// Use a dataview to read off the integer
const dataview = new DataView(new Uint8Array(this.data.slice(this.offset, this.offset + 4)).buffer);
const value = dataview.getUint32(0);
const value = dataview.getUint32(0, true);
this.offset += 4;
return value;
}

View File

@@ -0,0 +1,166 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2021
* @license Apache-2.0
*
* HASSH created by Salesforce
* Ben Reardon (@benreardon)
* Adel Karimi (@0x4d31)
* and the JA3 crew:
* John B. Althouse
* Jeff Atkinson
* Josh Atkins
*
* Algorithm released under the BSD-3-clause licence
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import Stream from "../lib/Stream.mjs";
import {runHash} from "../lib/Hash.mjs";
/**
* HASSH Client Fingerprint operation
*/
class HASSHClientFingerprint extends Operation {
/**
* HASSHClientFingerprint constructor
*/
constructor() {
super();
this.name = "HASSH Client Fingerprint";
this.module = "Crypto";
this.description = "Generates a HASSH fingerprint to help identify SSH clients based on hashing together values from the Client Key Exchange Init message.<br><br>Input: A hex stream of the SSH_MSG_KEXINIT packet application layer from Client to Server.";
this.infoURL = "https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Input format",
type: "option",
value: ["Hex", "Base64", "Raw"]
},
{
name: "Output format",
type: "option",
value: ["Hash digest", "HASSH algorithms string", "Full details"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [inputFormat, outputFormat] = args;
input = Utils.convertToByteArray(input, inputFormat);
const s = new Stream(new Uint8Array(input));
// Length
const length = s.readInt(4);
if (s.length !== length + 4)
throw new OperationError("Incorrect packet length.");
// Padding length
const paddingLength = s.readInt(1);
// Message code
const messageCode = s.readInt(1);
if (messageCode !== 20)
throw new OperationError("Not a Key Exchange Init.");
// Cookie
s.moveForwardsBy(16);
// KEX Algorithms
const kexAlgosLength = s.readInt(4);
const kexAlgos = s.readString(kexAlgosLength);
// Server Host Key Algorithms
const serverHostKeyAlgosLength = s.readInt(4);
s.moveForwardsBy(serverHostKeyAlgosLength);
// Encryption Algorithms Client to Server
const encAlgosC2SLength = s.readInt(4);
const encAlgosC2S = s.readString(encAlgosC2SLength);
// Encryption Algorithms Server to Client
const encAlgosS2CLength = s.readInt(4);
s.moveForwardsBy(encAlgosS2CLength);
// MAC Algorithms Client to Server
const macAlgosC2SLength = s.readInt(4);
const macAlgosC2S = s.readString(macAlgosC2SLength);
// MAC Algorithms Server to Client
const macAlgosS2CLength = s.readInt(4);
s.moveForwardsBy(macAlgosS2CLength);
// Compression Algorithms Client to Server
const compAlgosC2SLength = s.readInt(4);
const compAlgosC2S = s.readString(compAlgosC2SLength);
// Compression Algorithms Server to Client
const compAlgosS2CLength = s.readInt(4);
s.moveForwardsBy(compAlgosS2CLength);
// Languages Client to Server
const langsC2SLength = s.readInt(4);
s.moveForwardsBy(langsC2SLength);
// Languages Server to Client
const langsS2CLength = s.readInt(4);
s.moveForwardsBy(langsS2CLength);
// First KEX packet follows
s.moveForwardsBy(1);
// Reserved
s.moveForwardsBy(4);
// Padding string
s.moveForwardsBy(paddingLength);
// Output
const hassh = [
kexAlgos,
encAlgosC2S,
macAlgosC2S,
compAlgosC2S
];
const hasshStr = hassh.join(";");
const hasshHash = runHash("md5", Utils.strToArrayBuffer(hasshStr));
switch (outputFormat) {
case "HASSH algorithms string":
return hasshStr;
case "Full details":
return `Hash digest:
${hasshHash}
Full HASSH algorithms string:
${hasshStr}
Key Exchange Algorithms:
${kexAlgos}
Encryption Algorithms Client to Server:
${encAlgosC2S}
MAC Algorithms Client to Server:
${macAlgosC2S}
Compression Algorithms Client to Server:
${compAlgosC2S}`;
case "Hash digest":
default:
return hasshHash;
}
}
}
export default HASSHClientFingerprint;

View File

@@ -0,0 +1,166 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2021
* @license Apache-2.0
*
* HASSH created by Salesforce
* Ben Reardon (@benreardon)
* Adel Karimi (@0x4d31)
* and the JA3 crew:
* John B. Althouse
* Jeff Atkinson
* Josh Atkins
*
* Algorithm released under the BSD-3-clause licence
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils.mjs";
import Stream from "../lib/Stream.mjs";
import {runHash} from "../lib/Hash.mjs";
/**
* HASSH Server Fingerprint operation
*/
class HASSHServerFingerprint extends Operation {
/**
* HASSHServerFingerprint constructor
*/
constructor() {
super();
this.name = "HASSH Server Fingerprint";
this.module = "Crypto";
this.description = "Generates a HASSH fingerprint to help identify SSH servers based on hashing together values from the Server Key Exchange Init message.<br><br>Input: A hex stream of the SSH_MSG_KEXINIT packet application layer from Server to Client.";
this.infoURL = "https://engineering.salesforce.com/open-sourcing-hassh-abed3ae5044c";
this.inputType = "string";
this.outputType = "string";
this.args = [
{
name: "Input format",
type: "option",
value: ["Hex", "Base64", "Raw"]
},
{
name: "Output format",
type: "option",
value: ["Hash digest", "HASSH algorithms string", "Full details"]
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const [inputFormat, outputFormat] = args;
input = Utils.convertToByteArray(input, inputFormat);
const s = new Stream(new Uint8Array(input));
// Length
const length = s.readInt(4);
if (s.length !== length + 4)
throw new OperationError("Incorrect packet length.");
// Padding length
const paddingLength = s.readInt(1);
// Message code
const messageCode = s.readInt(1);
if (messageCode !== 20)
throw new OperationError("Not a Key Exchange Init.");
// Cookie
s.moveForwardsBy(16);
// KEX Algorithms
const kexAlgosLength = s.readInt(4);
const kexAlgos = s.readString(kexAlgosLength);
// Server Host Key Algorithms
const serverHostKeyAlgosLength = s.readInt(4);
s.moveForwardsBy(serverHostKeyAlgosLength);
// Encryption Algorithms Client to Server
const encAlgosC2SLength = s.readInt(4);
s.moveForwardsBy(encAlgosC2SLength);
// Encryption Algorithms Server to Client
const encAlgosS2CLength = s.readInt(4);
const encAlgosS2C = s.readString(encAlgosS2CLength);
// MAC Algorithms Client to Server
const macAlgosC2SLength = s.readInt(4);
s.moveForwardsBy(macAlgosC2SLength);
// MAC Algorithms Server to Client
const macAlgosS2CLength = s.readInt(4);
const macAlgosS2C = s.readString(macAlgosS2CLength);
// Compression Algorithms Client to Server
const compAlgosC2SLength = s.readInt(4);
s.moveForwardsBy(compAlgosC2SLength);
// Compression Algorithms Server to Client
const compAlgosS2CLength = s.readInt(4);
const compAlgosS2C = s.readString(compAlgosS2CLength);
// Languages Client to Server
const langsC2SLength = s.readInt(4);
s.moveForwardsBy(langsC2SLength);
// Languages Server to Client
const langsS2CLength = s.readInt(4);
s.moveForwardsBy(langsS2CLength);
// First KEX packet follows
s.moveForwardsBy(1);
// Reserved
s.moveForwardsBy(4);
// Padding string
s.moveForwardsBy(paddingLength);
// Output
const hassh = [
kexAlgos,
encAlgosS2C,
macAlgosS2C,
compAlgosS2C
];
const hasshStr = hassh.join(";");
const hasshHash = runHash("md5", Utils.strToArrayBuffer(hasshStr));
switch (outputFormat) {
case "HASSH algorithms string":
return hasshStr;
case "Full details":
return `Hash digest:
${hasshHash}
Full HASSH algorithms string:
${hasshStr}
Key Exchange Algorithms:
${kexAlgos}
Encryption Algorithms Server to Client:
${encAlgosS2C}
MAC Algorithms Server to Client:
${macAlgosS2C}
Compression Algorithms Server to Client:
${compAlgosS2C}`;
case "Hash digest":
default:
return hasshHash;
}
}
}
export default HASSHServerFingerprint;

View File

@@ -30,7 +30,7 @@ class JA3Fingerprint extends Operation {
this.name = "JA3 Fingerprint";
this.module = "Crypto";
this.description = "Generates a JA3 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.<br><br>Input: A hex stream of the TLS Client Hello application layer.";
this.description = "Generates a JA3 fingerprint to help identify TLS clients based on hashing together values from the Client Hello.<br><br>Input: A hex stream of the TLS Client Hello packet application layer.";
this.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967";
this.inputType = "string";
this.outputType = "string";

View File

@@ -30,7 +30,7 @@ class JA3SFingerprint extends Operation {
this.name = "JA3S Fingerprint";
this.module = "Crypto";
this.description = "Generates a JA3S fingerprint to help identify TLS servers based on hashing together values from the Server Hello.<br><br>Input: A hex stream of the TLS Server Hello record in the application layer.";
this.description = "Generates a JA3S fingerprint to help identify TLS servers based on hashing together values from the Server Hello.<br><br>Input: A hex stream of the TLS Server Hello record application layer.";
this.infoURL = "https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967";
this.inputType = "string";
this.outputType = "string";

View File

@@ -20,12 +20,30 @@ class ProtobufDecode extends Operation {
super();
this.name = "Protobuf Decode";
this.module = "Default";
this.description = "Decodes any Protobuf encoded data to a JSON representation of the data using the field number as the field key.";
this.module = "Protobuf";
this.description = "Decodes any Protobuf encoded data to a JSON representation of the data using the field number as the field key.<br><br>If a .proto schema is defined, the encoded data will be decoded with reference to the schema. Only one message instance will be decoded. <br><br><u>Show Unknown Fields</u><br>When a schema is used, this option shows fields that are present in the input data but not defined in the schema.<br><br><u>Show Types</u><br>Show the type of a field next to its name. For undefined fields, the wiretype and example types are shown instead.";
this.infoURL = "https://wikipedia.org/wiki/Protocol_Buffers";
this.inputType = "ArrayBuffer";
this.outputType = "JSON";
this.args = [];
this.args = [
{
name: "Schema (.proto text)",
type: "text",
value: "",
rows: 8,
hint: "Drag and drop is enabled on this ingredient"
},
{
name: "Show Unknown Fields",
type: "boolean",
value: false
},
{
name: "Show Types",
type: "boolean",
value: false
}
];
}
/**
@@ -36,7 +54,7 @@ class ProtobufDecode extends Operation {
run(input, args) {
input = new Uint8Array(input);
try {
return Protobuf.decode(input);
return Protobuf.decode(input, args);
} catch (err) {
throw new OperationError(err);
}

View File

@@ -0,0 +1,54 @@
/**
* @author GCHQ Contributor [3]
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Protobuf from "../lib/Protobuf.mjs";
/**
* Protobuf Encode operation
*/
class ProtobufEncode extends Operation {
/**
* ProtobufEncode constructor
*/
constructor() {
super();
this.name = "Protobuf Encode";
this.module = "Protobuf";
this.description = "Encodes a valid JSON object into a protobuf byte array using the input .proto schema.";
this.infoURL = "https://developers.google.com/protocol-buffers/docs/encoding";
this.inputType = "JSON";
this.outputType = "ArrayBuffer";
this.args = [
{
name: "Schema (.proto text)",
type: "text",
value: "",
rows: 8,
hint: "Drag and drop is enabled on this ingredient"
}
];
}
/**
* @param {Object} input
* @param {Object[]} args
* @returns {ArrayBuffer}
*/
run(input, args) {
try {
return Protobuf.encode(input, args);
} catch (error) {
throw new OperationError(error);
}
}
}
export default ProtobufEncode;

View File

@@ -105,6 +105,8 @@ import "./tests/RSA.mjs";
import "./tests/CBOREncode.mjs";
import "./tests/CBORDecode.mjs";
import "./tests/JA3Fingerprint.mjs";
import "./tests/JA3SFingerprint.mjs";
import "./tests/HASSH.mjs";
// Cannot test operations that use the File type yet

View File

@@ -0,0 +1,33 @@
/**
* HASSH tests.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2021
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "HASSH Client Fingerprint",
input: "000003140814c639665f5425dcb80bf9f0a048380a410000007e6469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d7368613235362c6469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d736861312c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6469666669652d68656c6c6d616e2d67726f7570312d736861310000000f7373682d7273612c7373682d6473730000009d6165733132382d6374722c6165733139322d6374722c6165733235362d6374722c617263666f75723235362c617263666f75723132382c6165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c6165733139322d6362632c6165733235362d6362632c617263666f75722c72696a6e6461656c2d636263406c797361746f722e6c69752e73650000009d6165733132382d6374722c6165733139322d6374722c6165733235362d6374722c617263666f75723235362c617263666f75723132382c6165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c6165733139322d6362632c6165733235362d6362632c617263666f75722c72696a6e6461656c2d636263406c797361746f722e6c69752e736500000069686d61632d6d64352c686d61632d736861312c756d61632d3634406f70656e7373682e636f6d2c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d393600000069686d61632d6d64352c686d61632d736861312c756d61632d3634406f70656e7373682e636f6d2c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d39360000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c69620000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c6962000000000000000000000000000000000000000000",
expectedOutput: "21b457a327ce7a2d4fce5ef2c42400bd",
recipeConfig: [
{
"op": "HASSH Client Fingerprint",
"args": ["Hex", "Hash digest"]
}
],
},
{
name: "HASSH Server Fingerprint",
input: "0000027c0b142c7bb93a1da21c9e54f5862e60a5597c000000596469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d736861312c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6469666669652d68656c6c6d616e2d67726f7570312d736861310000000f7373682d7273612c7373682d647373000000876165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c617263666f75722c6165733139322d6362632c6165733235362d6362632c72696a6e6461656c2d636263406c797361746f722e6c69752e73652c6165733132382d6374722c6165733139322d6374722c6165733235362d637472000000876165733132382d6362632c336465732d6362632c626c6f77666973682d6362632c636173743132382d6362632c617263666f75722c6165733139322d6362632c6165733235362d6362632c72696a6e6461656c2d636263406c797361746f722e6c69752e73652c6165733132382d6374722c6165733139322d6374722c6165733235362d63747200000055686d61632d6d64352c686d61632d736861312c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d393600000055686d61632d6d64352c686d61632d736861312c686d61632d726970656d643136302c686d61632d726970656d64313630406f70656e7373682e636f6d2c686d61632d736861312d39362c686d61632d6d64352d3936000000096e6f6e652c7a6c6962000000096e6f6e652c7a6c6962000000000000000000000000000000000000000000000000",
expectedOutput: "f430cd6761697a6a658ee1d45ed22e49",
recipeConfig: [
{
"op": "HASSH Server Fingerprint",
"args": ["Hex", "Hash digest"]
}
],
}
]);

View File

@@ -41,15 +41,17 @@ TestRegister.addTests([
}
],
},
{
name: "JA3S Fingerprint: TLS 1.3",
input: "16030100520200004e7f123ef1609fd3f4fa8668aac5822d500fb0639b22671d0fb7258597355795511bf61301002800280024001d0020ae0e282a3b7a463e71064ecbaf671586e979b0edbebf7a4735c31678c70f660c",
expectedOutput: "986ae432c402479fe7a0c6fbe02164c1",
recipeConfig: [
{
"op": "JA3S Fingerprint",
"args": ["Hex", "Hash digest"]
}
],
},
// This Server Hello was based on draft 18 of the TLS1.3 spec which does not include a Session ID field, leading it to fail.
// The published version of TLS1.3 does require a legacy Session ID field (even if it is empty).
// {
// name: "JA3S Fingerprint: TLS 1.3",
// input: "16030100520200004e7f123ef1609fd3f4fa8668aac5822d500fb0639b22671d0fb7258597355795511bf61301002800280024001d0020ae0e282a3b7a463e71064ecbaf671586e979b0edbebf7a4735c31678c70f660c",
// expectedOutput: "986ae432c402479fe7a0c6fbe02164c1",
// recipeConfig: [
// {
// "op": "JA3S Fingerprint",
// "args": ["Hex", "Hash digest"]
// }
// ],
// },
]);

View File

@@ -10,10 +10,10 @@ import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "Protobuf Decode",
name: "Protobuf Decode: no schema",
input: "0d1c0000001203596f751a024d65202b2a0a0a066162633132331200",
expectedOutput: JSON.stringify({
"1": 469762048,
"1": 28,
"2": "You",
"3": "Me",
"4": 43,
@@ -29,7 +29,277 @@ TestRegister.addTests([
},
{
"op": "Protobuf Decode",
"args": []
"args": ["", false, false]
}
]
},
{
name: "Protobuf Decode: partial schema, no unknown fields",
input: "0d1c0000001203596f751a024d65202b2a0a0a066162633132331200",
expectedOutput: JSON.stringify({
"Apple": [
28
],
"Banana": "You",
"Carrot": [
"Me"
]
}, null, 4),
recipeConfig: [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "Protobuf Decode",
"args": [
`message Test {
repeated fixed32 Apple = 1;
optional string Banana = 2;
repeated string Carrot = 3;
}`,
false,
false
]
}
]
},
{
name: "Protobuf Decode: partial schema, show unknown fields",
input: "0d1c0000001203596f751a024d65202b2a0a0a066162633132331200",
expectedOutput: JSON.stringify({
"Test": {
"Apple": [
28
],
"Banana": "You",
"Carrot": [
"Me"
]
},
"Unknown Fields": {
"4": 43,
"5": {
"1": "abc123",
"2": {}
}
}
}, null, 4),
recipeConfig: [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "Protobuf Decode",
"args": [
`message Test {
repeated fixed32 Apple = 1;
optional string Banana = 2;
repeated string Carrot = 3;
}`,
true,
false
]
}
]
},
{
name: "Protobuf Decode: full schema, no unknown fields",
input: "0d1c0000001203596f751a024d65202b2a0a0a06616263313233120031ff00000000000000",
expectedOutput: JSON.stringify({
"Apple": [
28
],
"Banana": "You",
"Carrot": [
"Me"
],
"Date": 43,
"Elderberry": {
"Fig": "abc123",
"Grape": {}
},
"Huckleberry": 255
}, null, 4),
recipeConfig: [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "Protobuf Decode",
"args": [
`message Test {
repeated fixed32 Apple = 1;
optional string Banana = 2;
repeated string Carrot = 3;
optional int32 Date = 4;
optional subTest Elderberry = 5;
optional fixed64 Huckleberry = 6;
}
message subTest {
optional string Fig = 1;
optional subSubTest Grape = 2;
}
message subSubTest {}`,
false,
false
]
}
]
},
{
name: "Protobuf Decode: partial schema, show unknown fields, show types",
input: "0d1c0000001203596f751a024d65202b2a0a0a06616263313233120031ba32a96cc10200003801",
expectedOutput: JSON.stringify({
"Test": {
"Banana (string)": "You",
"Carrot (string)": [
"Me"
],
"Date (int32)": 43,
"Imbe (Options)": "Option1"
},
"Unknown Fields": {
"field #1: 32-Bit (e.g. fixed32, float)": 28,
"field #5: L-delim (e.g. string, message)": {
"field #1: L-delim (e.g. string, message)": "abc123",
"field #2: L-delim (e.g. string, message)": {}
},
"field #6: 64-Bit (e.g. fixed64, double)": 3029774971578
}
}, null, 4),
recipeConfig: [
{
"op": "From Hex",
"args": ["Auto"]
},
{
"op": "Protobuf Decode",
"args": [
`message Test {
optional string Banana = 2;
repeated string Carrot = 3;
optional int32 Date = 4;
optional Options Imbe = 7;
}
message subTest {
optional string Fig = 1;
optional subSubTest Grape = 2;
}
message subSubTest {}
enum Options {
Option0 = 0;
Option1 = 1;
Option2 = 2;
}`,
true,
true
]
}
]
},
{
name: "Protobuf Encode",
input: JSON.stringify({
"Apple": [
28
],
"Banana": "You",
"Carrot": [
"Me"
],
"Date": 43,
"Elderberry": {
"Fig": "abc123",
"Grape": {}
},
"Huckleberry": [3029774971578],
"Imbe": 1
}, null, 4),
expectedOutput: "0d1c0000001203596f751a024d65202b2a0a0a06616263313233120031ba32a96cc10200003801",
recipeConfig: [
{
"op": "Protobuf Encode",
"args": [
`message Test {
repeated fixed32 Apple = 1;
optional string Banana = 2;
repeated string Carrot = 3;
optional int32 Date = 4;
optional subTest Elderberry = 5;
repeated fixed64 Huckleberry = 6;
optional Options Imbe = 7;
}
message subTest {
optional string Fig = 1;
optional subSubTest Grape = 2;
}
message subSubTest {}
enum Options {
Option0 = 0;
Option1 = 1;
Option2 = 2;
}`
]
},
{
"op": "To Hex",
"args": [
"None",
0
]
}
]
},
{
name: "Protobuf Encode: incomplete schema",
input: JSON.stringify({
"Apple": [
28
],
"Banana": "You",
"Carrot": [
"Me"
],
"Date": 43,
"Elderberry": {
"Fig": "abc123",
"Grape": {}
},
"Huckleberry": [3029774971578],
"Imbe": 1
}, null, 4),
expectedOutput: "1203596f75202b2a0a0a06616263313233120031ba32a96cc1020000",
recipeConfig: [
{
"op": "Protobuf Encode",
"args": [
`message Test {
optional string Banana = 2;
optional int32 Date = 4;
optional subTest Elderberry = 5;
repeated fixed64 Huckleberry = 6;
}
message subTest {
optional string Fig = 1;
optional subSubTest Grape = 2;
}
message subSubTest {}
enum Options {
Option0 = 0;
Option1 = 1;
Option2 = 2;
}`
]
},
{
"op": "To Hex",
"args": [
"None",
0
]
}
]
},