Bladeren bron

Add support for LZNT1 decompression.

Maxime THIEBAUT 1 jaar geleden
bovenliggende
commit
77042abc23

+ 1 - 0
.gitignore

@@ -3,6 +3,7 @@ npm-debug.log
 travis.log
 build
 .vscode
+.idea
 .*.swp
 src/core/config/modules/*
 src/core/config/OperationConfig.json

+ 2 - 1
src/core/config/Categories.json

@@ -351,7 +351,8 @@
             "LZMA Decompress",
             "LZMA Compress",
             "LZ4 Decompress",
-            "LZ4 Compress"
+            "LZ4 Compress",
+            "LZNT1 Decompress"
         ]
     },
     {

+ 88 - 0
src/core/lib/LZNT1.mjs

@@ -0,0 +1,88 @@
+/**
+ *
+ * LZNT1 Decompress.
+ *
+ * @author 0xThiebaut [thiebaut.dev]
+ * @copyright Crown Copyright 2023
+ * @license Apache-2.0
+ *
+ * https://github.com/Velocidex/go-ntfs/blob/master/parser%2Flznt1.go
+ */
+
+import Utils from "../Utils.mjs";
+import OperationError from "../errors/OperationError.mjs";
+
+const COMPRESSED_MASK = 1 << 15,
+    SIZE_MASK = (1 << 12) - 1;
+
+/**
+ * @param {number} offset
+ * @returns {number}
+ */
+function getDisplacement(offset) {
+    let result = 0;
+    while (offset >= 0x10) {
+        offset >>= 1;
+        result += 1;
+    }
+    return result;
+}
+
+/**
+ * @param {byteArray} compressed
+ * @returns {byteArray}
+ */
+export function decompress(compressed) {
+    const decompressed = Array();
+    let coffset = 0;
+
+    while (coffset + 2 <= compressed.length) {
+        const doffset = decompressed.length;
+
+        const blockHeader = Utils.byteArrayToInt(compressed.slice(coffset, coffset + 2), "little");
+        coffset += 2;
+
+        const size = blockHeader & SIZE_MASK;
+        const blockEnd = coffset + size + 1;
+
+        if (size === 0) {
+            break;
+        } else if (compressed.length < coffset + size) {
+            throw new OperationError("Malformed LZNT1 stream: Block too small! Has the stream been truncated?");
+        }
+
+        if ((blockHeader & COMPRESSED_MASK) !== 0) {
+            while (coffset < blockEnd) {
+                let header = compressed[coffset++];
+
+                for (let i = 0; i < 8 && coffset < blockEnd; i++) {
+                    if ((header & 1) === 0) {
+                        decompressed.push(compressed[coffset++]);
+                    } else {
+                        const pointer = Utils.byteArrayToInt(compressed.slice(coffset, coffset + 2), "little");
+                        coffset += 2;
+
+                        const displacement = getDisplacement(decompressed.length - doffset - 1);
+                        const symbolOffset = (pointer >> (12 - displacement)) + 1;
+                        const symbolLength = (pointer & (0xFFF >> displacement)) + 2;
+                        const shiftOffset = decompressed.length - symbolOffset;
+
+                        for (let shiftDelta = 0; shiftDelta < symbolLength + 1; shiftDelta++) {
+                            const shift = shiftOffset + shiftDelta;
+                            if (shift < 0 || decompressed.length <= shift) {
+                                throw new OperationError("Malformed LZNT1 stream: Invalid shift!");
+                            }
+                            decompressed.push(decompressed[shift]);
+                        }
+                    }
+                    header >>= 1;
+                }
+            }
+        } else {
+            decompressed.push(...compressed.slice(coffset, coffset + size + 1));
+            coffset += size + 1;
+        }
+    }
+
+    return decompressed;
+}

+ 41 - 0
src/core/operations/LZNT1Decompress.mjs

@@ -0,0 +1,41 @@
+/**
+ * @author 0xThiebaut [thiebaut.dev]
+ * @copyright Crown Copyright 2023
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import {decompress} from "../lib/LZNT1.mjs";
+
+/**
+ * LZNT1 Decompress operation
+ */
+class LZNT1Decompress extends Operation {
+
+    /**
+     * LZNT1 Decompress constructor
+     */
+    constructor() {
+        super();
+
+        this.name = "LZNT1 Decompress";
+        this.module = "Compression";
+        this.description = "Decompresses data using the LZNT1 algorithm.<br><br>Similar to the Windows API <code>RtlDecompressBuffer</code>.";
+        this.infoURL = "https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-xca/5655f4a3-6ba4-489b-959f-e1f407c52f15";
+        this.inputType = "byteArray";
+        this.outputType = "byteArray";
+        this.args = [];
+    }
+
+    /**
+     * @param {byteArray} input
+     * @param {Object[]} args
+     * @returns {byteArray}
+     */
+    run(input, args) {
+        return decompress(input);
+    }
+
+}
+
+export default LZNT1Decompress;

+ 2 - 1
src/web/html/index.html

@@ -62,7 +62,8 @@
                 "Training branch predictor...",
                 "Timing cache hits...",
                 "Speculatively executing recipes...",
-                "Adding LLM hallucinations..."
+                "Adding LLM hallucinations...",
+                "Decompressing malware..."
             ];
 
             // Shuffle array using Durstenfeld algorithm

+ 4 - 0
tests/node/tests/operations.mjs

@@ -635,6 +635,10 @@ WWFkYSBZYWRh\r
         assert.strictEqual(chef.keccak("Flea Market").toString(), "c2a06880b19e453ee5440e8bd4c2024bedc15a6630096aa3f609acfd2b8f15f27cd293e1cc73933e81432269129ce954a6138889ce87831179d55dcff1cc7587");
     }),
 
+    it("LZNT1 Decompress", () => {
+        assert.strictEqual(chef.LZNT1Decompress("\x1a\xb0\x00compress\x00edtestda\x04ta\x07\x88alot").toString(), "compressedtestdatacompressedalot");
+    }),
+
     it("MD6", () => {
         assert.strictEqual(chef.MD6("Head Over Heels", {key: "arty"}).toString(), "d8f7fe4931fbaa37316f76283d5f615f50ddd54afdc794b61da522556aee99ad");
     }),

+ 1 - 0
tests/operations/index.mjs

@@ -62,6 +62,7 @@ import "./tests/JSONtoCSV.mjs";
 import "./tests/JWTDecode.mjs";
 import "./tests/JWTSign.mjs";
 import "./tests/JWTVerify.mjs";
+import "./tests/LZNT1Decompress.mjs";
 import "./tests/MS.mjs";
 import "./tests/Magic.mjs";
 import "./tests/MorseCode.mjs";

+ 22 - 0
tests/operations/tests/LZNT1Decompress.mjs

@@ -0,0 +1,22 @@
+/**
+ * LZNT1 Decompress tests.
+ *
+ * @author 0xThiebaut [thiebaut.dev]
+ * @copyright Crown Copyright 2023
+ * @license Apache-2.0
+ */
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+    {
+        name: "LZNT1 Decompress",
+        input: "\x1a\xb0\x00compress\x00edtestda\x04ta\x07\x88alot",
+        expectedOutput: "compressedtestdatacompressedalot",
+        recipeConfig: [
+            {
+                op: "LZNT1 Decompress",
+                args: []
+            }
+        ],
+    }
+]);