cyberchef 10.22.1 → 10.24.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.
Files changed (118) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/CONTRIBUTING.md +40 -0
  3. package/Dockerfile +2 -0
  4. package/Gruntfile.js +2 -28
  5. package/README.md +1 -1
  6. package/babel.config.js +0 -6
  7. package/package.json +64 -63
  8. package/src/core/Chef.mjs +8 -1
  9. package/src/core/Ingredient.mjs +5 -2
  10. package/src/core/Operation.mjs +6 -1
  11. package/src/core/Recipe.mjs +10 -5
  12. package/src/core/config/Categories.json +20 -4
  13. package/src/core/config/OperationConfig.json +550 -26
  14. package/src/core/config/modules/Ciphers.mjs +6 -0
  15. package/src/core/config/modules/Crypto.mjs +6 -0
  16. package/src/core/config/modules/Default.mjs +8 -0
  17. package/src/core/config/modules/Shellcode.mjs +2 -0
  18. package/src/core/config/scripts/fixCryptoApiImports.mjs +54 -0
  19. package/src/core/config/scripts/fixSnackBarMarkup.mjs +28 -0
  20. package/src/core/lib/AudioBytes.mjs +103 -0
  21. package/src/core/lib/AudioMetaSchema.mjs +82 -0
  22. package/src/core/lib/AudioParsers.mjs +630 -0
  23. package/src/core/lib/BigIntUtils.mjs +73 -0
  24. package/src/core/lib/Extract.mjs +5 -0
  25. package/src/core/lib/Modhex.mjs +2 -0
  26. package/src/core/lib/ParityBit.mjs +50 -0
  27. package/src/core/lib/QRCode.mjs +30 -10
  28. package/src/core/lib/RC6.mjs +625 -0
  29. package/src/core/operations/A1Z26CipherDecode.mjs +1 -1
  30. package/src/core/operations/AddTextToImage.mjs +116 -64
  31. package/src/core/operations/AnalyseUUID.mjs +109 -6
  32. package/src/core/operations/BlurImage.mjs +10 -12
  33. package/src/core/operations/ContainImage.mjs +50 -40
  34. package/src/core/operations/ConvertImageFormat.mjs +33 -39
  35. package/src/core/operations/CoverImage.mjs +39 -37
  36. package/src/core/operations/CropImage.mjs +35 -21
  37. package/src/core/operations/DisassembleARM.mjs +193 -0
  38. package/src/core/operations/DitherImage.mjs +8 -8
  39. package/src/core/operations/EscapeUnicodeCharacters.mjs +0 -17
  40. package/src/core/operations/ExtractAudioMetadata.mjs +175 -0
  41. package/src/core/operations/ExtractEmailAddresses.mjs +2 -3
  42. package/src/core/operations/ExtractLSB.mjs +17 -11
  43. package/src/core/operations/ExtractRGBA.mjs +12 -10
  44. package/src/core/operations/FlaskSessionDecode.mjs +80 -0
  45. package/src/core/operations/FlaskSessionSign.mjs +89 -0
  46. package/src/core/operations/FlaskSessionVerify.mjs +136 -0
  47. package/src/core/operations/FlipImage.mjs +14 -10
  48. package/src/core/operations/GenerateImage.mjs +39 -32
  49. package/src/core/operations/ImageBrightnessContrast.mjs +10 -10
  50. package/src/core/operations/ImageFilter.mjs +14 -13
  51. package/src/core/operations/ImageHueSaturationLightness.mjs +22 -20
  52. package/src/core/operations/ImageOpacity.mjs +6 -8
  53. package/src/core/operations/InvertImage.mjs +4 -6
  54. package/src/core/operations/Jq.mjs +12 -4
  55. package/src/core/operations/NormaliseImage.mjs +5 -7
  56. package/src/core/operations/OffsetChecker.mjs +1 -1
  57. package/src/core/operations/ParityBit.mjs +128 -0
  58. package/src/core/operations/ParseEthernetFrame.mjs +112 -0
  59. package/src/core/operations/ParseIPv4Header.mjs +23 -6
  60. package/src/core/operations/ParseQRCode.mjs +13 -13
  61. package/src/core/operations/PseudoRandomIntegerGenerator.mjs +164 -0
  62. package/src/core/operations/RC6Decrypt.mjs +119 -0
  63. package/src/core/operations/RC6Encrypt.mjs +119 -0
  64. package/src/core/operations/RandomizeColourPalette.mjs +11 -11
  65. package/src/core/operations/RegularExpression.mjs +2 -1
  66. package/src/core/operations/RenderMarkdown.mjs +35 -4
  67. package/src/core/operations/ResizeImage.mjs +30 -23
  68. package/src/core/operations/RotateImage.mjs +8 -9
  69. package/src/core/operations/SQLBeautify.mjs +21 -3
  70. package/src/core/operations/SharpenImage.mjs +94 -62
  71. package/src/core/operations/SplitColourChannels.mjs +47 -21
  72. package/src/core/operations/TextIntegerConverter.mjs +123 -0
  73. package/src/core/operations/UnescapeUnicodeCharacters.mjs +17 -0
  74. package/src/core/operations/ViewBitPlane.mjs +16 -20
  75. package/src/core/operations/index.mjs +22 -0
  76. package/src/node/index.mjs +55 -0
  77. package/src/web/HTMLIngredient.mjs +24 -43
  78. package/src/web/HTMLOperation.mjs +8 -2
  79. package/src/web/Manager.mjs +1 -0
  80. package/src/web/html/index.html +6 -6
  81. package/src/web/static/fonts/bmfonts/Roboto72White.fnt +491 -485
  82. package/src/web/static/fonts/bmfonts/RobotoBlack72White.fnt +494 -488
  83. package/src/web/static/fonts/bmfonts/RobotoMono72White.fnt +110 -103
  84. package/src/web/static/fonts/bmfonts/RobotoSlab72White.fnt +498 -492
  85. package/src/web/stylesheets/layout/_banner.css +30 -0
  86. package/src/web/stylesheets/layout/_modals.css +5 -0
  87. package/src/web/stylesheets/utils/_overrides.css +7 -0
  88. package/src/web/waiters/ControlsWaiter.mjs +82 -0
  89. package/src/web/waiters/InputWaiter.mjs +12 -6
  90. package/src/web/waiters/OperationsWaiter.mjs +30 -13
  91. package/src/web/waiters/RecipeWaiter.mjs +2 -2
  92. package/tests/browser/02_ops.js +23 -3
  93. package/tests/node/index.mjs +1 -0
  94. package/tests/node/tests/lib/BigIntUtils.mjs +150 -0
  95. package/tests/node/tests/operations.mjs +11 -10
  96. package/tests/operations/index.mjs +13 -0
  97. package/tests/operations/tests/A1Z26CipherDecode.mjs +33 -0
  98. package/tests/operations/tests/AnalyseUUID.mjs +66 -0
  99. package/tests/operations/tests/DisassembleARM.mjs +377 -0
  100. package/tests/operations/tests/ExtractAudioMetadata.mjs +287 -0
  101. package/tests/operations/tests/ExtractEmailAddresses.mjs +38 -12
  102. package/tests/operations/tests/Fernet.mjs +18 -3
  103. package/tests/operations/tests/FlaskSession.mjs +246 -0
  104. package/tests/operations/tests/GenerateQRCode.mjs +67 -0
  105. package/tests/operations/tests/JWTSign.mjs +83 -8
  106. package/tests/operations/tests/Jq.mjs +32 -0
  107. package/tests/operations/tests/Modhex.mjs +20 -0
  108. package/tests/operations/tests/ParityBit.mjs +147 -0
  109. package/tests/operations/tests/ParseEthernetFrame.mjs +45 -0
  110. package/tests/operations/tests/RC6.mjs +487 -0
  111. package/tests/operations/tests/RegularExpression.mjs +75 -0
  112. package/tests/operations/tests/RenderMarkdown.mjs +110 -0
  113. package/tests/operations/tests/SQLBeautify.mjs +54 -0
  114. package/tests/operations/tests/TextIntegerConverter.mjs +199 -0
  115. package/tests/samples/Audio.mjs +73 -0
  116. package/tests/samples/Images.mjs +0 -12
  117. package/webpack.config.js +10 -7
  118. package/src/core/lib/ImageManipulation.mjs +0 -251
@@ -0,0 +1,193 @@
1
+ /**
2
+ * @author MedjedThomasXM
3
+ * @copyright Crown Copyright 2024
4
+ * @license Apache-2.0
5
+ */
6
+
7
+ import Operation from "../Operation.mjs";
8
+ import OperationError from "../errors/OperationError.mjs";
9
+ import { isWorkerEnvironment } from "../Utils.mjs";
10
+ import cs from "@alexaltea/capstone-js/dist/capstone.min.js";
11
+
12
+ /**
13
+ * Disassemble ARM operation
14
+ */
15
+ class DisassembleARM extends Operation {
16
+
17
+ /**
18
+ * DisassembleARM constructor
19
+ */
20
+ constructor() {
21
+ super();
22
+
23
+ this.name = "Disassemble ARM";
24
+ this.module = "Shellcode";
25
+ this.description = "Disassembles ARM machine code into assembly language.<br><br>Supports ARM (32-bit), Thumb, and ARM64 (AArch64) architectures using the Capstone disassembly framework.<br><br>Input should be in hexadecimal.";
26
+ this.infoURL = "https://wikipedia.org/wiki/ARM_architecture_family";
27
+ this.inputType = "string";
28
+ this.outputType = "string";
29
+ this.args = [
30
+ {
31
+ "name": "Architecture",
32
+ "type": "option",
33
+ "value": ["ARM (32-bit)", "ARM64 (AArch64)"]
34
+ },
35
+ {
36
+ "name": "Mode",
37
+ "type": "option",
38
+ "value": ["ARM", "Thumb", "Thumb + Cortex-M", "ARMv8"]
39
+ },
40
+ {
41
+ "name": "Endianness",
42
+ "type": "option",
43
+ "value": ["Little Endian", "Big Endian"]
44
+ },
45
+ {
46
+ "name": "Starting address (hex)",
47
+ "type": "number",
48
+ "value": 0
49
+ },
50
+ {
51
+ "name": "Show instruction hex",
52
+ "type": "boolean",
53
+ "value": true
54
+ },
55
+ {
56
+ "name": "Show instruction position",
57
+ "type": "boolean",
58
+ "value": true
59
+ }
60
+ ];
61
+ }
62
+
63
+ /**
64
+ * @param {string} input
65
+ * @param {Object[]} args
66
+ * @returns {string}
67
+ */
68
+ async run(input, args) {
69
+ const [
70
+ architecture,
71
+ mode,
72
+ endianness,
73
+ startAddress,
74
+ showHex,
75
+ showPosition
76
+ ] = args;
77
+
78
+ // Remove whitespace from input
79
+ const hexInput = input.replace(/\s/g, "");
80
+
81
+ // Validate hex input
82
+ if (!/^[0-9a-fA-F]*$/.test(hexInput)) {
83
+ throw new OperationError("Invalid hexadecimal input. Please provide valid hex characters only.");
84
+ }
85
+
86
+ if (hexInput.length === 0) {
87
+ return "";
88
+ }
89
+
90
+ if (hexInput.length % 2 !== 0) {
91
+ throw new OperationError("Invalid hexadecimal input. Length must be even.");
92
+ }
93
+
94
+ // Convert hex string to byte array
95
+ const bytes = [];
96
+ for (let i = 0; i < hexInput.length; i += 2) {
97
+ bytes.push(parseInt(hexInput.substr(i, 2), 16));
98
+ }
99
+
100
+ // Determine architecture constant
101
+ let arch;
102
+ if (architecture === "ARM64 (AArch64)") {
103
+ arch = cs.ARCH_ARM64;
104
+ } else {
105
+ arch = cs.ARCH_ARM;
106
+ }
107
+
108
+ // Determine mode constant
109
+ let modeValue = cs.MODE_LITTLE_ENDIAN;
110
+
111
+ if (architecture === "ARM (32-bit)") {
112
+ switch (mode) {
113
+ case "ARM":
114
+ modeValue = cs.MODE_ARM;
115
+ break;
116
+ case "Thumb":
117
+ modeValue = cs.MODE_THUMB;
118
+ break;
119
+ case "Thumb + Cortex-M":
120
+ modeValue = cs.MODE_THUMB | cs.MODE_MCLASS;
121
+ break;
122
+ case "ARMv8":
123
+ modeValue = cs.MODE_ARM | cs.MODE_V8;
124
+ break;
125
+ default:
126
+ modeValue = cs.MODE_ARM;
127
+ }
128
+ } else {
129
+ // ARM64 only has one mode (ARM mode is default for ARM64)
130
+ modeValue = cs.MODE_ARM;
131
+ }
132
+
133
+ // Add endianness
134
+ if (endianness === "Big Endian") {
135
+ modeValue |= cs.MODE_BIG_ENDIAN;
136
+ }
137
+
138
+ if (isWorkerEnvironment()) {
139
+ self.sendStatusMessage("Disassembling...");
140
+ }
141
+
142
+ let disassembler;
143
+ try {
144
+ disassembler = new cs.Capstone(arch, modeValue);
145
+ } catch (e) {
146
+ throw new OperationError(`Failed to initialise Capstone disassembler: ${e}`);
147
+ }
148
+
149
+ let instructions;
150
+ try {
151
+ instructions = disassembler.disasm(bytes, startAddress);
152
+ } catch (e) {
153
+ disassembler.close();
154
+ // Check if it's a "no valid instructions" error (code 0 means OK but nothing decoded)
155
+ if (e && e.includes && e.includes("code 0:")) {
156
+ throw new OperationError(`No valid ${architecture} instructions found in input. The bytes may be for a different architecture or mode.`);
157
+ }
158
+ throw new OperationError(`Disassembly failed: ${e}`);
159
+ }
160
+
161
+ // Format output
162
+ const output = [];
163
+ for (const insn of instructions) {
164
+ let line = "";
165
+
166
+ if (showPosition) {
167
+ // Format address as hex with 0x prefix
168
+ const addrHex = "0x" + insn.address.toString(16).padStart(8, "0");
169
+ line += addrHex + " ";
170
+ }
171
+
172
+ if (showHex) {
173
+ // Format instruction bytes as hex
174
+ const bytesHex = insn.bytes.map(b => b.toString(16).padStart(2, "0")).join("");
175
+ line += bytesHex.padEnd(16, " ") + " ";
176
+ }
177
+
178
+ line += insn.mnemonic;
179
+ if (insn.op_str) {
180
+ line += " " + insn.op_str;
181
+ }
182
+
183
+ output.push(line);
184
+ }
185
+
186
+ disassembler.close();
187
+
188
+ return output.join("\n");
189
+ }
190
+
191
+ }
192
+
193
+ export default DisassembleARM;
@@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
9
9
  import { isImage } from "../lib/FileType.mjs";
10
10
  import { toBase64 } from "../lib/Base64.mjs";
11
11
  import { isWorkerEnvironment } from "../Utils.mjs";
12
- import Jimp from "jimp/es/index.js";
12
+ import { Jimp, JimpMime } from "jimp";
13
13
 
14
14
  /**
15
15
  * Image Dither operation
16
16
  */
17
17
  class DitherImage extends Operation {
18
-
19
18
  /**
20
19
  * DitherImage constructor
21
20
  */
@@ -51,17 +50,19 @@ class DitherImage extends Operation {
51
50
  try {
52
51
  if (isWorkerEnvironment())
53
52
  self.sendStatusMessage("Applying dither to image...");
54
- image.dither565();
53
+ image.dither();
55
54
 
56
55
  let imageBuffer;
57
- if (image.getMIME() === "image/gif") {
58
- imageBuffer = await image.getBufferAsync(Jimp.MIME_PNG);
56
+ if (image.mime === "image/gif") {
57
+ imageBuffer = await image.getBuffer(JimpMime.png);
59
58
  } else {
60
- imageBuffer = await image.getBufferAsync(Jimp.AUTO);
59
+ imageBuffer = await image.getBuffer(image.mime);
61
60
  }
62
61
  return imageBuffer.buffer;
63
62
  } catch (err) {
64
- throw new OperationError(`Error applying dither to image. (${err})`);
63
+ throw new OperationError(
64
+ `Error applying dither to image. (${err})`,
65
+ );
65
66
  }
66
67
  }
67
68
 
@@ -81,7 +82,6 @@ class DitherImage extends Operation {
81
82
 
82
83
  return `<img src="data:${type};base64,${toBase64(dataArray)}">`;
83
84
  }
84
-
85
85
  }
86
86
 
87
87
  export default DitherImage;
@@ -44,23 +44,6 @@ class EscapeUnicodeCharacters extends Operation {
44
44
  "value": true
45
45
  }
46
46
  ];
47
- this.checks = [
48
- {
49
- pattern: "\\\\u(?:[\\da-f]{4,6})",
50
- flags: "i",
51
- args: ["\\u"]
52
- },
53
- {
54
- pattern: "%u(?:[\\da-f]{4,6})",
55
- flags: "i",
56
- args: ["%u"]
57
- },
58
- {
59
- pattern: "U\\+(?:[\\da-f]{4,6})",
60
- flags: "i",
61
- args: ["U+"]
62
- }
63
- ];
64
47
  }
65
48
 
66
49
  /**
@@ -0,0 +1,175 @@
1
+ /**
2
+ * @author d0s1nt [d0s1nt@cyberchefaudio]
3
+ * @copyright Crown Copyright 2025
4
+ * @license Apache-2.0
5
+ */
6
+
7
+ import Operation from "../Operation.mjs";
8
+ import OperationError from "../errors/OperationError.mjs";
9
+ import Utils from "../Utils.mjs";
10
+ import { makeEmptyReport, sniffContainer } from "../lib/AudioMetaSchema.mjs";
11
+ import {
12
+ parseMp3, parseRiffWave, parseFlac, parseOgg,
13
+ parseMp4BestEffort, parseAiffBestEffort,
14
+ parseAacAdts, parseAc3, parseWmaAsf,
15
+ } from "../lib/AudioParsers.mjs";
16
+
17
+ /**
18
+ * Extract Audio Metadata operation.
19
+ */
20
+ class ExtractAudioMetadata extends Operation {
21
+ /** Creates the Extract Audio Metadata operation. */
22
+ constructor() {
23
+ super();
24
+
25
+ this.name = "Extract Audio Metadata";
26
+ this.module = "Default";
27
+ this.description =
28
+ "Extract common audio metadata across MP3 (ID3v2/ID3v1/GEOB), WAV/BWF/BW64 (INFO/bext/iXML/axml), FLAC (Vorbis Comment/Picture), OGG (Vorbis/OpusTags), AAC (ADTS), AC3 (Dolby Digital), WMA (ASF), plus best-effort MP4/M4A and AIFF scanning. Outputs normalized JSON.";
29
+ this.infoURL = "https://wikipedia.org/wiki/Audio_file_format";
30
+ this.inputType = "ArrayBuffer";
31
+ this.outputType = "JSON";
32
+ this.presentType = "html";
33
+
34
+ this.args = [
35
+ { name: "Filename (optional)", type: "string", value: "" },
36
+ { name: "Max embedded text bytes (iXML/axml/etc)", type: "number", value: 1024 * 512 },
37
+ ];
38
+ }
39
+
40
+ /**
41
+ * @param {ArrayBuffer} input
42
+ * @param {Object[]} args
43
+ * @returns {Object}
44
+ */
45
+ run(input, args) {
46
+ const filename = (args?.[0] || "").trim() || null;
47
+ const maxTextBytes = Number.isFinite(args?.[1]) ? Math.max(1024, args[1]) : 1024 * 512;
48
+
49
+ if (!(input instanceof ArrayBuffer) || input.byteLength === 0)
50
+ throw new OperationError("No input data. Load an audio file (drag/drop or use the open file button).");
51
+
52
+ const bytes = new Uint8Array(input);
53
+ const container = sniffContainer(bytes);
54
+ const report = makeEmptyReport(filename, bytes.length, container);
55
+
56
+ try {
57
+ const parsers = {
58
+ mp3: () => parseMp3(bytes, report),
59
+ wav: () => parseRiffWave(bytes, report, maxTextBytes),
60
+ bw64: () => parseRiffWave(bytes, report, maxTextBytes),
61
+ flac: () => parseFlac(bytes, report, maxTextBytes),
62
+ ogg: () => parseOgg(bytes, report),
63
+ opus: () => parseOgg(bytes, report),
64
+ mp4: () => parseMp4BestEffort(bytes, report),
65
+ m4a: () => parseMp4BestEffort(bytes, report),
66
+ aiff: () => parseAiffBestEffort(bytes, report, maxTextBytes),
67
+ aac: () => parseAacAdts(bytes, report),
68
+ ac3: () => parseAc3(bytes, report),
69
+ wma: () => parseWmaAsf(bytes, report),
70
+ };
71
+ if (parsers[container.type]) {
72
+ parsers[container.type]();
73
+ } else {
74
+ report.errors.push({ stage: "sniff", message: "Unknown/unsupported container (best-effort scan not implemented)." });
75
+ }
76
+ } catch (e) {
77
+ report.errors.push({ stage: "parse", message: String(e?.message || e) });
78
+ }
79
+
80
+ return report;
81
+ }
82
+
83
+ /** Renders the extracted metadata as an HTML table. */
84
+ present(data) {
85
+ if (!data || typeof data !== "object") return JSON.stringify(data, null, 4);
86
+
87
+ const esc = Utils.escapeHtml;
88
+ const row = (k, v) => `<tr><td>${esc(String(k))}</td><td>${esc(String(v ?? ""))}</td></tr>\n`;
89
+ const section = (title) => `<tr><th colspan="2" style="background:#e9ecef;text-align:center">${esc(title)}</th></tr>\n`;
90
+ const objRows = (obj, filter = (v) => v !== null) => {
91
+ for (const [k, v] of Object.entries(obj)) {
92
+ if (filter(v)) html += row(k, v);
93
+ }
94
+ };
95
+ const objSection = (obj, title, filter) => {
96
+ if (!obj) return;
97
+ html += section(title);
98
+ objRows(obj, filter);
99
+ };
100
+ const listSection = (arr, title, fmt) => {
101
+ if (!arr?.length) return;
102
+ html += section(title);
103
+ for (const item of arr) html += fmt(item);
104
+ };
105
+
106
+ let html = `<table class="table table-hover table-sm table-bordered table-nonfluid">\n`;
107
+
108
+ html += section("Artifact");
109
+ html += row("Filename", data.artifact?.filename || "(none)");
110
+ html += row("Size", `${(data.artifact?.byte_length ?? 0).toLocaleString()} bytes`);
111
+ html += row("Container", data.artifact?.container?.type);
112
+ html += row("MIME", data.artifact?.container?.mime);
113
+ if (data.artifact?.container?.brand) html += row("Brand", data.artifact.container.brand);
114
+
115
+ html += section("Detections");
116
+ html += row("Metadata systems", (data.detections?.metadata_systems || []).join(", ") || "None");
117
+ html += row("Provenance systems", (data.detections?.provenance_systems || []).join(", ") || "None");
118
+
119
+ const common = data.tags?.common || {};
120
+ html += section("Common Tags");
121
+ if (Object.values(common).some((v) => v !== null)) {
122
+ for (const [key, val] of Object.entries(common)) {
123
+ if (val !== null) html += row(key.charAt(0).toUpperCase() + key.slice(1), val);
124
+ }
125
+ } else {
126
+ html += row("(none)", "No common tags found");
127
+ }
128
+
129
+ listSection(data.tags?.raw?.id3v2?.frames, "ID3v2 Frames", (f) => {
130
+ const val = typeof f.decoded === "object" ? JSON.stringify(f.decoded) : (f.decoded ?? `(${f.size} bytes)`);
131
+ return row(f.id + (f.description ? ` \u2014 ${f.description}` : ""), val);
132
+ });
133
+ objSection(data.tags?.raw?.id3v1, "ID3v1", (v) => !!v);
134
+ listSection(data.tags?.raw?.apev2?.items, "APEv2 Tags", (i) => row(i.key, i.value));
135
+
136
+ if (data.tags?.raw?.vorbis_comments?.comments?.length) {
137
+ html += section("Vorbis Comments");
138
+ html += row("Vendor", data.tags.raw.vorbis_comments.vendor);
139
+ for (const c of data.tags.raw.vorbis_comments.comments) html += row(c.key, c.value);
140
+ }
141
+
142
+ objSection(data.tags?.raw?.riff?.info, "RIFF INFO", () => true);
143
+ objSection(data.tags?.raw?.riff?.bext, "BWF bext");
144
+ listSection(data.tags?.raw?.riff?.chunks, "RIFF Chunks", (c) => row(c.id, `${c.size} bytes @ offset ${c.offset}`));
145
+ listSection(data.tags?.raw?.flac?.blocks, "FLAC Metadata Blocks", (b) => row(b.type, `${b.length} bytes`));
146
+
147
+ if (data.tags?.raw?.mp4?.top_level_atoms?.length) {
148
+ html += section("MP4 Top-Level Atoms");
149
+ const atoms = data.tags.raw.mp4.top_level_atoms;
150
+ for (const a of atoms.slice(0, 50)) html += row(a.type, `${a.size} bytes @ offset ${a.offset}`);
151
+ if (atoms.length > 50) html += row("...", `${atoms.length - 50} more atoms`);
152
+ }
153
+
154
+ listSection(data.tags?.raw?.aiff?.chunks, "AIFF Chunks", (c) => row(c.id, c.value));
155
+ objSection(data.tags?.raw?.aac, "AAC ADTS");
156
+ objSection(data.tags?.raw?.ac3, "AC3 (Dolby Digital)");
157
+ objSection(data.tags?.raw?.asf?.content_description, "ASF Content Description", (v) => !!v);
158
+ listSection(data.tags?.raw?.asf?.extended_content, "ASF Extended Content", (d) => row(d.name, d.value));
159
+ listSection(data.embedded, "Embedded Objects", (e) => row(e.id, `${e.content_type || "unknown"} \u2014 ${(e.byte_length ?? 0).toLocaleString()} bytes`));
160
+
161
+ if (data.provenance?.c2pa?.present) {
162
+ html += section("C2PA Provenance");
163
+ html += row("Present", "Yes");
164
+ for (const emb of (data.provenance.c2pa.embedding || []))
165
+ html += row("Carrier", `${emb.carrier} \u2014 ${(emb.byte_length ?? 0).toLocaleString()} bytes`);
166
+ }
167
+
168
+ listSection(data.errors, "Errors", (e) => row(e.stage, e.message));
169
+
170
+ html += "</table>";
171
+ return html;
172
+ }
173
+ }
174
+
175
+ export default ExtractAudioMetadata;
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import Operation from "../Operation.mjs";
8
- import { search } from "../lib/Extract.mjs";
8
+ import { EMAIL_REGEX, search } from "../lib/Extract.mjs";
9
9
  import { caseInsensitiveSort } from "../lib/Sort.mjs";
10
10
 
11
11
  /**
@@ -50,8 +50,7 @@ class ExtractEmailAddresses extends Operation {
50
50
  */
51
51
  run(input, args) {
52
52
  const [displayTotal, sort, unique] = args,
53
- // email regex from: https://www.regextester.com/98066
54
- regex = /(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?\.)+[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9](?:[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9-]*[\u00A0-\uD7FF\uE000-\uFFFFa-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\])/ig;
53
+ regex = EMAIL_REGEX;
55
54
 
56
55
  const results = search(
57
56
  input,
@@ -9,13 +9,12 @@ import OperationError from "../errors/OperationError.mjs";
9
9
  import Utils from "../Utils.mjs";
10
10
  import { fromBinary } from "../lib/Binary.mjs";
11
11
  import { isImage } from "../lib/FileType.mjs";
12
- import Jimp from "jimp/es/index.js";
12
+ import { Jimp } from "jimp";
13
13
 
14
14
  /**
15
15
  * Extract LSB operation
16
16
  */
17
17
  class ExtractLSB extends Operation {
18
-
19
18
  /**
20
19
  * ExtractLSB constructor
21
20
  */
@@ -24,8 +23,10 @@ class ExtractLSB extends Operation {
24
23
 
25
24
  this.name = "Extract LSB";
26
25
  this.module = "Image";
27
- this.description = "Extracts the Least Significant Bit data from each pixel in an image. This is a common way to hide data in Steganography.";
28
- this.infoURL = "https://wikipedia.org/wiki/Bit_numbering#Least_significant_bit_in_digital_steganography";
26
+ this.description =
27
+ "Extracts the Least Significant Bit data from each pixel in an image. This is a common way to hide data in Steganography.";
28
+ this.infoURL =
29
+ "https://wikipedia.org/wiki/Bit_numbering#Least_significant_bit_in_digital_steganography";
29
30
  this.inputType = "ArrayBuffer";
30
31
  this.outputType = "byteArray";
31
32
  this.args = [
@@ -57,8 +58,8 @@ class ExtractLSB extends Operation {
57
58
  {
58
59
  name: "Bit",
59
60
  type: "number",
60
- value: 0
61
- }
61
+ value: 0,
62
+ },
62
63
  ];
63
64
  }
64
65
 
@@ -68,21 +69,27 @@ class ExtractLSB extends Operation {
68
69
  * @returns {byteArray}
69
70
  */
70
71
  async run(input, args) {
71
- if (!isImage(input)) throw new OperationError("Please enter a valid image file.");
72
+ if (!isImage(input))
73
+ throw new OperationError("Please enter a valid image file.");
72
74
 
73
75
  const bit = 7 - args.pop(),
74
76
  pixelOrder = args.pop(),
75
- colours = args.filter(option => option !== "").map(option => COLOUR_OPTIONS.indexOf(option)),
77
+ colours = args
78
+ .filter((option) => option !== "")
79
+ .map((option) => COLOUR_OPTIONS.indexOf(option)),
76
80
  parsedImage = await Jimp.read(input),
77
81
  width = parsedImage.bitmap.width,
78
82
  height = parsedImage.bitmap.height,
79
83
  rgba = parsedImage.bitmap.data;
80
84
 
81
85
  if (bit < 0 || bit > 7) {
82
- throw new OperationError("Error: Bit argument must be between 0 and 7");
86
+ throw new OperationError(
87
+ "Error: Bit argument must be between 0 and 7",
88
+ );
83
89
  }
84
90
 
85
- let i, combinedBinary = "";
91
+ let i,
92
+ combinedBinary = "";
86
93
 
87
94
  if (pixelOrder === "Row") {
88
95
  for (i = 0; i < rgba.length; i += 4) {
@@ -106,7 +113,6 @@ class ExtractLSB extends Operation {
106
113
 
107
114
  return fromBinary(combinedBinary);
108
115
  }
109
-
110
116
  }
111
117
 
112
118
  const COLOUR_OPTIONS = ["R", "G", "B", "A"];
@@ -7,15 +7,14 @@
7
7
  import Operation from "../Operation.mjs";
8
8
  import OperationError from "../errors/OperationError.mjs";
9
9
  import { isImage } from "../lib/FileType.mjs";
10
- import Jimp from "jimp/es/index.js";
10
+ import { Jimp } from "jimp";
11
11
 
12
- import {RGBA_DELIM_OPTIONS} from "../lib/Delim.mjs";
12
+ import { RGBA_DELIM_OPTIONS } from "../lib/Delim.mjs";
13
13
 
14
14
  /**
15
15
  * Extract RGBA operation
16
16
  */
17
17
  class ExtractRGBA extends Operation {
18
-
19
18
  /**
20
19
  * ExtractRGBA constructor
21
20
  */
@@ -24,7 +23,8 @@ class ExtractRGBA extends Operation {
24
23
 
25
24
  this.name = "Extract RGBA";
26
25
  this.module = "Image";
27
- this.description = "Extracts each pixel's RGBA value in an image. These are sometimes used in Steganography to hide text or data.";
26
+ this.description =
27
+ "Extracts each pixel's RGBA value in an image. These are sometimes used in Steganography to hide text or data.";
28
28
  this.infoURL = "https://wikipedia.org/wiki/RGBA_color_space";
29
29
  this.inputType = "ArrayBuffer";
30
30
  this.outputType = "string";
@@ -32,13 +32,13 @@ class ExtractRGBA extends Operation {
32
32
  {
33
33
  name: "Delimiter",
34
34
  type: "editableOption",
35
- value: RGBA_DELIM_OPTIONS
35
+ value: RGBA_DELIM_OPTIONS,
36
36
  },
37
37
  {
38
38
  name: "Include Alpha",
39
39
  type: "boolean",
40
- value: true
41
- }
40
+ value: true,
41
+ },
42
42
  ];
43
43
  }
44
44
 
@@ -48,18 +48,20 @@ class ExtractRGBA extends Operation {
48
48
  * @returns {string}
49
49
  */
50
50
  async run(input, args) {
51
- if (!isImage(input)) throw new OperationError("Please enter a valid image file.");
51
+ if (!isImage(input))
52
+ throw new OperationError("Please enter a valid image file.");
52
53
 
53
54
  const delimiter = args[0],
54
55
  includeAlpha = args[1],
55
56
  parsedImage = await Jimp.read(input);
56
57
 
57
58
  let bitmap = parsedImage.bitmap.data;
58
- bitmap = includeAlpha ? bitmap : bitmap.filter((val, idx) => idx % 4 !== 3);
59
+ bitmap = includeAlpha ?
60
+ bitmap :
61
+ bitmap.filter((val, idx) => idx % 4 !== 3);
59
62
 
60
63
  return bitmap.join(delimiter);
61
64
  }
62
-
63
65
  }
64
66
 
65
67
  export default ExtractRGBA;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @author ThePlayer372-FR []
3
+ * @license Apache-2.0
4
+ */
5
+
6
+ import Operation from "../Operation.mjs";
7
+ import OperationError from "../errors/OperationError.mjs";
8
+ import { fromBase64 } from "../lib/Base64.mjs";
9
+
10
+ /**
11
+ * Flask Session Decode operation
12
+ */
13
+ class FlaskSessionDecode extends Operation {
14
+ /**
15
+ * FlaskSessionDecode constructor
16
+ */
17
+ constructor() {
18
+ super();
19
+
20
+ this.name = "Flask Session Decode";
21
+ this.module = "Crypto";
22
+ this.description = "Decodes the payload of a Flask session cookie (itsdangerous) into JSON.";
23
+ this.inputType = "string";
24
+ this.outputType = "JSON";
25
+ this.args = [
26
+ {
27
+ name: "View TimeStamp",
28
+ type: "boolean",
29
+ value: false
30
+ }
31
+ ];
32
+ }
33
+
34
+ /**
35
+ * @param {string} input
36
+ * @param {Object[]} args
37
+ * @returns {Object[]}
38
+ */
39
+ run(input, args) {
40
+ input = input.trim();
41
+ const parts = input.split(".");
42
+ if (parts.length !== 3) {
43
+ throw new OperationError("Invalid Flask token format. Expected payload.timestamp.signature");
44
+ }
45
+
46
+ const payloadB64 = parts[0];
47
+ const time = parts[1];
48
+
49
+ const timeB64 = time.replace(/-/g, "+").replace(/_/g, "/");
50
+ const binary = fromBase64(timeB64);
51
+ const bytes = new Uint8Array(4);
52
+ for (let i = 0; i < 4; i++) {
53
+ bytes[i] = binary.charCodeAt(i);
54
+ }
55
+ const view = new DataView(bytes.buffer);
56
+ const timestamp = view.getInt32(0, false);
57
+
58
+ const base64 = payloadB64.replace(/-/g, "+").replace(/_/g, "/");
59
+ const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
60
+ let payloadJson;
61
+ try {
62
+ payloadJson = fromBase64(padded);
63
+ } catch (e) {
64
+ throw new OperationError("Invalid Base64 payload");
65
+ }
66
+
67
+ try {
68
+ let data = JSON.parse(payloadJson);
69
+
70
+ if (args[0]) {
71
+ data = {payload: data, timestamp: timestamp};
72
+ }
73
+ return data;
74
+ } catch (e) {
75
+ throw new OperationError("Unable to decode JSON payload: " + e.message);
76
+ }
77
+ }
78
+ }
79
+
80
+ export default FlaskSessionDecode;