0.1.2 • Published 9 months ago

@litko/yara-x v0.1.2

Weekly downloads
-
License
MIT
Repository
-
Last release
9 months ago

@litko/yara-x

v0.1.1

Features

  • High Performance: Built with napi-rs and VirusTotal/yara-x
  • Async Support: First-class support for asynchronous scanning
  • WASM Compilation: Compile rules to WebAssembly for portable execution
  • Zero Dependencies: No external runtime dependencies

Usage

Installation

npm install @litko/yara-x

Basic Example

import { compile } from "@litko/yara-x";

// Compile yara rules
const rules = compile(`
  rule test_rule {
    strings:
      $a = "hello world"
    condition:
      $a
  }
`);

// Scan a buffer
const buffer = Buffer.from("This is a test with hello world in it");
const matches = rules.scan(buffer);

// Process matches
if (matches.length > 0) {
  console.log(`Found ${matches.length} matching rules:`);
  matches.forEach((match) => {
    console.log(`- Rule: ${match.ruleIdentifier}`);
    match.matches.forEach((stringMatch) => {
      console.log(
        `  * Match at offset ${stringMatch.offset}: ${stringMatch.data}`,
      );
    });
  });
} else {
  console.log("No matches found");
}

Scanning Files

import { fromFile, compile } from "@litko/yara-x";
import { readFileSync } from "fs";

// Load rules from a file
const rules = fromFile("./rules/malware_rules.yar");

try {
  // Scan a file directly
  const matches = rules.scanFile("./samples/suspicious_file.exe");

  console.log(`Found ${matches.length} matching rules`);
} catch (error) {
  console.error(`Scanning error: ${error.message}`);
}

Asynchronous Scanning

import { compile } from "@litko/yara-x";

async function scanLargeFile() {
  const rules = compile(`rule large_file_rule {
      strings:
        $a = "sensitive data"
      condition:
        $a
    }
  `);

  try {
    // Scan a file asynchronously
    const matches = await rules.scanFileAsync("./samples/large_file.bin");
    console.log(`Found ${matches.length} matching rules`);
  } catch (error) {
    console.error(`Async scanning error: ${error.message}`);
  }
}

scanLargeFile();

Variables

import { compile } from "@litko/yara-x";

// Create a scanner with variables
const rules = compile(
  `
  rule variable_rule {
    condition:
      string_var contains "secret" and int_var > 10
  }
`,
  {
    defineVariables: {
      string_var: "this is a secret message",
      int_var: "20",
    },
  },
);

// Scan with default variables
let matches = rules.scan(Buffer.from("test data"));
console.log(`Matches with default variables: ${matches.length}`);

// Override variables at scan time
matches = rules.scan(Buffer.from("test data"), {
  string_var: "no secrets here",
  int_var: 5, // Note: variables at scan time can be numbers as well
});
console.log(`Matches with overridden variables: ${matches.length}`);

WASM Compilation

import { compile, compileToWasm } from "@litko/yara-x";

// Compile rules to WASM
const rule = `
  rule wasm_test {
    strings:
      $a = "compile to wasm"
    condition:
      $a
  }
`;

// Static compilation
compileToWasm(rule, "./output/rules.wasm");

// Or from a compiled rules instance
const compiledRules = compile(rule);
compiledRules.emitWasmFile("./output/instance_rules.wasm");

// Async compilation
await compiledRules.emitWasmFileAsync("./output/async_rules.wasm");

Incremental Rule Building

import { create } from "@litko/yara-x";

// Create an empty scanner
const scanner = create();

// Add rules incrementally
scanner.addRuleSource(`
  wrule first_rule {
    strings:
      $a = "first pattern"
    condition:
      $a
  }
`);

// Add rules from a file
scanner.addRuleFile("./rules/more_rules.yar");

// Add another rule
scanner.addRuleSource(`
  rule another_rule {
    strings:
      $a = "another pattern"
    condition:
      $a
  }
`);

// Now scan with all the rules
const matches = scanner.scan(Buffer.from("test data with first pattern"));

Rule Validation

import { validate } from "@litko/yara-x";

// Validate rules without executing them
const result = validate(`
  rule valid_rule {
    strings:
      $a = "valid"
    condition:
      $a
    }
`);

if (result.errors.length === 0) {
  console.log("Rules are valid!");
} else {
  console.error("Rule validation failed:");
  result.errors.forEach((error) => {
    console.error(`- ${error.code}: ${error.message}`);
  });
}

Advanced Options

import { compile } from "@litko/yara-x";

// Create a scanner with advanced options
const rules = compile(
  `
  rule advanced_rule {
    strings:
      $a = /hello[[:space:]]world/ // Using POSIX character class
    condition:
      $a and test_var > 10
  }
`,
  {
    // Define variables
    defineVariables: {
      test_var: "20",
    },

    // Enable relaxed regular expression syntax
    relaxedReSyntax: true,

    // Enable condition optimization
    conditionOptimization: true,

    // Ignore specific modules
    ignoreModules: ["pe"],

  :  // Error on potentially slow patterns
    errorOnSlowPattern: true,

    // Error on potentially slow loops
    errorOnSlowLoop: true,
  },
);

Error Handling

Compilation Errors

import { compile } from "@litko/yara-x";

try {
  // This will throw an error due to invalid syntax
  const rules = compile(`
    rule invalid_rule {
      strings:
        $a = "unclosed string
      condition:
        $a
    }
  `);
} catch (error) {
  console.error(`Compilation error: ${error.message}`);
  // Output: Compilation error: error[E001]: syntax error
  //  --> line:3:28
  //   |
  // 3 |         $a = "unclosed string
  //   |                            ^ expecting `"`, found end of file
  // 278:  }
}

Scanning errors

import { compile } from "@litko/yara-x";

const rules = compile(`
  rule test_rule {
    condition:
      true
  }
`);

try {
  // This will throw if the file doesn't exist
  rules.scanFile("/path/to/nonexistent/file.bin");
} catch (error) {
  console.error(`Scanning error: ${error.message}`);
  // Output: Scanning error: Error reading file: No such file or directory (os error 2)
}

Async Errors

import { compile, compileToWasm } from "@litko/yara-x";

async function handleAsyncErrors() {
  const rules = compile(`
    rule test_rule {
      condition:
        true
    }
  `);

  try {
    await rules.scanFileAsync("/path/to/nonexistent/file.bin");
  } catch (error) {
    console.error(`Async scanning error: ${error.message}`);
  }

  try {
    await compileToWasm(
      "rule test { condition: true }",
      "/invalid/path/rules.wasm",
    );
  } catch (error) {
    console.error(`WASM compilation error: ${error.message}`);
  }
}

handleAsyncErrors();

Compiler Warnings

import { compile } from "@litko/yara-x";

// Create a scanner with a rule that generates warnings
const rules = compile(`
  rule warning_rule {
    strings:
      $a = "unused string"
    condition:
      true  // Warning: invariant expression
    }
`);

// Get and display warnings
const warnings = rules.getWarnings();
if (warnings.length > 0) {
  console.log("Compiler warnings:");
  warnings.forEach((warning) => {
    console.log(`- ${warning.code}: ${warning.message}`);
  });
}

Performance Benchmarks

Test Setup:

  • Hardware: MacBook Pro (M3 Max, 36GB RAM)
  • Test Data: Generated data of varying sizes (small: 64 bytes, medium: 100KB, large: 10MB). See __test__/benchmark.mjs for data generation and benchmarking code.
  • The Large test file (10MB) is auto-generated, to prevent bloating the size of the repository.

Key Metrics (Averages):

OperationAverage TimeIterationsp50p95p99
Scanner Creation (Simple Rule)1.675 ms1001.547 ms2.318 ms2.657 ms
Scanner Creation (Complex Rule)1.878 ms1001.848 ms2.005 ms2.865 ms
Scanner Creation (Regex Rule)2.447 ms1002.444 ms2.473 ms2.569 ms
Scanner Creation (Multiple Rules)1.497 ms1001.488 ms1.547 ms1.819 ms
Scanning Small Data (64 bytes, Simple Rule)0.145 ms10000.143 ms0.156 ms0.169 ms
Scanning Medium Data (100KB, Simple Rule)0.151 ms1000.146 ms0.179 ms0.205 ms
Scanning Large Data (10MB, Simple Rule)0.347 ms100.340 ms0.394 ms0.394 ms
Scanning Medium Data (100KB, Complex Rule)0.219 ms1000.215 ms0.254 ms0.269 ms
Scanning Medium Data (100KB, Regex Rule)0.156 ms1000.152 ms0.182 ms0.210 ms
Scanning Medium Data (100KB, Multiple Rules)0.218 ms1000.212 ms0.261 ms0.353 ms
Async Scanning Medium Data (100KB, Simple Rule)0.012 ms1000.011 ms0.016 ms0.027ms
Scanning with Variables0.143 ms10000.140 ms0.155 ms0.166 ms
Scanning with Variables (Override at Scan Time)0.144 ms10000.142 ms0.158 ms0.175 ms

API Reference

Functions

  • compile(ruleSource: string, options?: CompilerOptions) - Compiles yara rules from a string.
  • compileToWasm(ruleSource: string, outputPath: string, options?: CompilerOptions) - Compiles yara rules from a string to WASM file.
  • compileFileToWasm(rulesPath: string, outputPath: string, options?: CompilerOptions) - Compiles yara rules from a file to WASM file.
  • validate(ruleSource: string, options?: CompilerOptions) - Validates yara rules without executing them.
  • create(options?: CompilerOptions) - Creates an empty rules scanner to add rules incrementally.
  • fromFile(rulePath: string, options?: CompilerOptions) - Compiles yara rules from a file.

yarax Methods

  • getWarnings() - Get compiler warnings.
  • scan(data: Buffer, variables?: Record<string, string | number>) - Scan a buffer.
  • scanFile(filePath: string, variables?: Record<string, string | number>) - Scan a file.
  • scanAsync(data: Buffer, variables?: Record<string, object | undefined | null>) - Scan a buffer asynchronously.
  • scanFileAsync(filePath: string, variables?: Record<string, object | undefined | null>) - Scan a file asynchronously.
  • emitWasmFile(filePath: string) - Emit compiled rules to WASM file synchronously.
  • emitWasmFileAsync(filePath: string) - Emit compiled rules to WASM file asynchronously.
  • addRuleSource(rules: string) - Add rules from a string to an existing scanner.
  • addRuleFile(filePath: string) - Add rules from a file to an existing scanner.

Rule Validation

  • validate(rules: string, options?: CompilerOptions) - Validate yara rules without executing them.

Licenses

This project incorporates code under two distinct licenses:

  • MIT License:
    • The node.js bindings and other code specific to this module are licensed under the MIT license.
    • See LICENSE-MIT for the full text.
  • BSD-3-Clause License:
    • The included yara-x library is licensed under the BSD-3-Clause license.
    • See LICENSE-BSD-3-Clause for the full text.
0.1.2

9 months ago

0.1.1

9 months ago

0.1.0

9 months ago