@@ -2,7 +2,7 @@ import OperationConfig from "../config/OperationConfig.json";
2
2
import Utils, { isWorkerEnvironment } from "../Utils.mjs";
3
3
import Recipe from "../Recipe.mjs";
4
4
import Dish from "../Dish.mjs";
5
- import {detectFileType} from "./FileType.mjs";
5
+ import {detectFileType, isType } from "./FileType.mjs";
6
6
import chiSquared from "chi-squared";
7
7
8
8
/**
@@ -19,35 +19,68 @@ class Magic {
19
19
* Magic constructor.
20
20
*
21
21
* @param {ArrayBuffer} buf
22
- * @param {Object[]} [opPatterns]
22
+ * @param {Object} prevOp
23
23
*/
24
- constructor(buf, opPatterns) {
24
+ constructor(buf, opPatterns, prevOp ) {
25
25
this.inputBuffer = new Uint8Array(buf);
26
26
this.inputStr = Utils.arrayBufferToStr(buf);
27
- this.opPatterns = opPatterns || Magic._generateOpPatterns();
27
+ this.opPatterns = opPatterns || Magic._generateOpCriteria();
28
+ this.prevOp = prevOp;
28
29
}
29
30
30
31
/**
31
- * Finds operations that claim to be able to decode the input based on regular
32
- * expression matches.
32
+ * Finds operations that claim to be able to decode the input based on
33
+ * regular expression matches.
33
34
*
34
- * @returns {Object[]}
35
+ * @param {[Object]} opPatterns
36
+ * @returns {Array}
35
37
*/
36
- findMatchingOps( ) {
38
+ inputRegexMatch(opPatterns ) {
37
39
const matches = [];
38
40
39
- for (let i = 0; i < this. opPatterns.length; i++) {
40
- const pattern = this. opPatterns[i],
41
- regex = new RegExp(pattern.match, pattern.flags);
41
+ for (let i = 0; i < opPatterns.length; i++) {
42
+ const pattern = opPatterns[i];
43
+
42
44
43
- if (regex .test(this.inputStr)) {
45
+ if (pattern.match .test(this.inputStr)) {
44
46
matches.push(pattern);
45
47
}
46
48
}
47
49
48
50
return matches;
49
51
}
50
52
53
+ /**
54
+ * Finds operations that claim to be able to decode the input based on entropy
55
+ * matches.
56
+ *
57
+ * @param {[Object]} opPatterns
58
+ * @returns {Array}
59
+ */
60
+ entropyInputMatch(opPatterns) {
61
+ const matches = [];
62
+
63
+ const entropyOfInput = this.calcEntropy();
64
+
65
+ for (let i = 0; i < opPatterns.length; i++) {
66
+ const currOp = opPatterns[i];
67
+ if ((entropyOfInput > currOp.entropy[0]) && (entropyOfInput < currOp.entropy[1]))
68
+ matches.push(currOp);
69
+ }
70
+ return matches;
71
+ }
72
+
73
+ /**
74
+ * Finds operations that claim to be able to decode the input based on criteria.
75
+ *
76
+ * @returns {Object[]}
77
+ */
78
+ findMatchingInputOps() {
79
+ let matches = this.inputRegexMatch(this.opPatterns.regex);
80
+ matches = matches.concat(this.entropyInputMatch(this.opPatterns.entropy));
81
+ return [...new Set(matches)];
82
+ }
83
+
51
84
/**
52
85
* Attempts to detect the language of the input by comparing its byte frequency
53
86
* to that of several known languages.
@@ -264,6 +297,35 @@ class Magic {
264
297
return results;
265
298
}
266
299
300
+ /**
301
+ *
302
+ */
303
+ checkRegexes(regexes) {
304
+ for (const elem of regexes) {
305
+ const regex = new RegExp(elem.match, elem.flags);
306
+ if (regex.test(this.inputStr))
307
+ return true;
308
+ }
309
+ return false;
310
+ }
311
+ /**
312
+ *
313
+ */
314
+ checkOutputFromPrevious() {
315
+ let score = 0;
316
+ if ("regex" in this.prevOp.output) {
317
+ if (this.checkRegexes(this.prevOp.output.regex)) score++;
318
+ }
319
+ if ("entropy" in this.prevOp.output) {
320
+ const inputEntropy = this.calcEntropy();
321
+ if ((inputEntropy > this.prevOp.output.entropy[0]) && (inputEntropy < this.prevOp.output.entropy[1])) score++;
322
+ }
323
+ if ("mime" in this.prevOp.output) {
324
+ if (isType(this.prevOp.output.mime, this.inputBuffer)) score++;
325
+ }
326
+ return score > 0;
327
+ }
328
+
267
329
/**
268
330
* Speculatively executes matching operations, recording metadata of each result.
269
331
*
@@ -281,8 +343,15 @@ class Magic {
281
343
if (depth < 0) return [];
282
344
283
345
// Find any operations that can be run on this data
284
- const matchingOps = this.findMatchingOps();
285
346
347
+ if (this.prevOp) {
348
+ if ("output" in this.prevOp) {
349
+ if (!(this.checkOutputFromPrevious())) {
350
+ return [];
351
+ }
352
+ }
353
+ }
354
+ const matchingOps = this.findMatchingInputOps();
286
355
let results = [];
287
356
288
357
// Record the properties of the current data
@@ -305,8 +374,7 @@ class Magic {
305
374
const opConfig = {
306
375
op: op.op,
307
376
args: op.args
308
- },
309
- output = await this._runRecipe([opConfig]);
377
+ }, output = await this._runRecipe([opConfig]);
310
378
311
379
// If the recipe is repeating and returning the same data, do not continue
312
380
if (prevOp && op.op === prevOp.op && _buffersEqual(output, this.inputBuffer)) {
@@ -318,7 +386,8 @@ class Magic {
318
386
return;
319
387
}
320
388
321
- const magic = new Magic(output, this.opPatterns),
389
+
390
+ const magic = new Magic(output, this.opPatterns, OperationConfig[op.op]),
322
391
speculativeResults = await magic.speculativeExecution(
323
392
depth-1, extLang, intensive, [...recipeConfig, opConfig], op.useful, crib);
324
393
@@ -330,7 +399,7 @@ class Magic {
330
399
const bfEncodings = await this.bruteForce();
331
400
332
401
await Promise.all(bfEncodings.map(async enc => {
333
- const magic = new Magic(enc.data, this.opPatterns),
402
+ const magic = new Magic(enc.data, this.opPatterns, undefined ),
334
403
bfResults = await magic.speculativeExecution(
335
404
depth-1, extLang, false, [...recipeConfig, enc.conf], false, crib);
336
405
@@ -447,24 +516,34 @@ class Magic {
447
516
* @private
448
517
* @returns {Object[]}
449
518
*/
450
- static _generateOpPatterns() {
451
- const opPatterns = [];
519
+ static _generateOpCriteria() {
520
+ const opCriteria = {
521
+ regex: [],
522
+ entropy: []
523
+ };
452
524
453
525
for (const op in OperationConfig) {
454
- if (!("patterns" in OperationConfig[op])) continue;
455
-
456
- OperationConfig[op].patterns.forEach(pattern => {
457
- opPatterns.push({
458
- op: op,
459
- match: pattern.match,
460
- flags: pattern.flags,
461
- args: pattern.args,
462
- useful: pattern.useful || false
463
- });
464
- });
526
+ if ("input" in OperationConfig[op]) {
527
+ if ("regex" in OperationConfig[op].input)
528
+ OperationConfig[op].input.regex.forEach(pattern => {
529
+ opCriteria.regex.push({
530
+ op: op,
531
+ match: new RegExp(pattern.match, pattern.flags),
532
+ args: pattern.args,
533
+ useful: pattern.useful || false
534
+ });
535
+ });
536
+ if ("entropy" in OperationConfig[op].input) {
537
+ opCriteria.entropy.push({
538
+ op: op,
539
+ entropy: OperationConfig[op].input.entropy.input,
540
+ args: OperationConfig[op].input.entropy.args
541
+ });
542
+ }
543
+ }
465
544
}
466
545
467
- return opPatterns ;
546
+ return opCriteria ;
468
547
}
469
548
470
549
/**
0 commit comments