node-yara-rs

git clone git://archive.git.mtrnord.blog/MTRNord/node-yara-rs.git
Log | Files | Refs | README | LICENSE

commit d11da2946b0c3f2c68fd2f2bf05348196ae3c079
parent 61827781fd3f14ab614827031a76ed0612792668
Author: MTRNord <mtrnord1@gmail.com>
Date:   Sun,  1 Oct 2023 16:47:32 +0200

Restructure the code to reflect the truth a bit better

Diffstat:
M__test__/index.spec.mjs | 11++++++-----
Mindex.d.ts | 62+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mindex.js | 3++-
Mpackage.json | 1+
Msrc/lib.rs | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Myarn.lock | 8++++++++
6 files changed, 178 insertions(+), 18 deletions(-)

diff --git a/__test__/index.spec.mjs b/__test__/index.spec.mjs @@ -1,20 +1,20 @@ import test from 'ava' -import { YaraScanner } from '../index.js' +import { YaraCompiler } from '../index.js' const TEST_RULE = "rule TestRule {\n condition:\n true\n}" -test('can construct YaraScanner', (t) => { +test('can construct YaraCompiler', (t) => { t.plan(1) t.notThrows(() => { - const scanner = new YaraScanner([], []); + const compiler = new YaraCompiler([], []); }); }) test('can load string rules', (t) => { t.plan(1) t.notThrows(() => { - const scanner = new YaraScanner([{ + const compiler = new YaraCompiler([{ string: TEST_RULE }], []); }); @@ -24,9 +24,10 @@ test('can load string rules', (t) => { test('can match string rules', (t) => { t.plan(2) t.notThrows(() => { - const scanner = new YaraScanner([{ + const compiler = new YaraCompiler([{ string: TEST_RULE }], []); + const scanner = compiler.newScanner(); const result = scanner.scanString(""); t.deepEqual(result, [ { diff --git a/index.d.ts b/index.d.ts @@ -52,8 +52,68 @@ export interface YaraMatch { /** Matched data. */ data: Array<number> } -export class YaraScanner { +/** + * An interface to use yara with node in a stable manner using Rust + * @public + */ +export class YaraCompiler { + /** + * Constructs a new Yara instance and compiles the provided rules and variables. + * + * @param rules - The rules which shall be compiled. + * @param variables - The variables you want to pass to the rules. + * @throws This can throw if there is an unexpected error. + * + * @returns A new instance of a YaraScanner which can be used to scan data + */ constructor(rules: Array<YaraRule>, variables: Array<YaraVariable>) + /** + * Creates a new yara scanner for the rules defined earlier. + * This can be called multiple times + * + * @returns A {@link YaraScanner} instance + */ + newScanner(): YaraScanner +} +/** + * An interface to use yara with node in a stable manner using Rust + * @public + */ +export class YaraScanner { + /** + * Scan a buffer of data with yara + * + * @param buffer - The data which shall be scanned by yara. + * @throws This can throw if there is an unexpected error. + * + * @returns The results of yara scan_mem. + */ scanBuffer(buffer: Buffer): Array<YaraRuleResult> + /** + * Scan a string of data with yara + * + * @param input - The data which shall be scanned by yara. + * @throws This can throw if there is an unexpected error. + * + * @returns The results of yara scan_mem. + */ scanString(input: string): Array<YaraRuleResult> + /** + * Scan a file with yara + * + * @param filepath - The path to the file yara shall scan + * @throws This can throw if there is an unexpected error. + * + * @returns The results of yara scan_mem. + */ + scanFile(filepath: string): Array<YaraRuleResult> + /** + * Scan a process with yara + * + * @param pid - The process id of the process that shall be scanned. + * @throws This can throw if there is an unexpected error. + * + * @returns The results of yara scan_mem. + */ + scanProcess(pid: number): Array<YaraRuleResult> } diff --git a/index.js b/index.js @@ -252,6 +252,7 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { YaraScanner } = nativeBinding +const { YaraCompiler, YaraScanner } = nativeBinding +module.exports.YaraCompiler = YaraCompiler module.exports.YaraScanner = YaraScanner diff --git a/package.json b/package.json @@ -15,6 +15,7 @@ "license": "MIT", "devDependencies": { "@napi-rs/cli": "^2.16.3", + "@types/node": "^20.8.0", "ava": "^5.1.1" }, "ava": { diff --git a/src/lib.rs b/src/lib.rs @@ -1,7 +1,11 @@ #![deny(clippy::all)] -use napi::{anyhow::Context, bindgen_prelude::Buffer, Result}; -use yara::{Compiler, MetadataValue, Rule as ExtYaraRule, Rules}; +use napi::{ + anyhow::Context, + bindgen_prelude::{Buffer, Reference, SharedReference}, + Env, Result, +}; +use yara::{Compiler, MetadataValue, Rule as ExtYaraRule, Rules, Scanner}; #[macro_use] extern crate napi_derive; @@ -28,11 +32,20 @@ pub struct YaraVariable { pub string_value: Option<String>, } +/// An interface to use yara with node in a stable manner using Rust +/// @public #[napi] -pub struct YaraScanner { +pub struct YaraCompiler { rules: Rules, } +/// An interface to use yara with node in a stable manner using Rust +/// @public +#[napi] +pub struct YaraScanner { + scanner: SharedReference<YaraCompiler, Scanner<'static>>, +} + #[napi(object)] #[derive(Debug)] pub struct YaraRuleResult { @@ -80,7 +93,14 @@ pub struct YaraMatch { } #[napi] -impl YaraScanner { +impl YaraCompiler { + /// Constructs a new Yara instance and compiles the provided rules and variables. + /// + /// @param rules - The rules which shall be compiled. + /// @param variables - The variables you want to pass to the rules. + /// @throws This can throw if there is an unexpected error. + /// + /// @returns A new instance of a YaraScanner which can be used to scan data #[napi(constructor)] pub fn new(rules: Vec<YaraRule>, variables: Vec<YaraVariable>) -> napi::Result<Self> { let mut compiler = Compiler::new().context("Failed to create yara compiler")?; @@ -133,9 +153,30 @@ impl YaraScanner { .compile_rules() .context("Failed to compile rules")?; - Ok(YaraScanner { rules }) + Ok(YaraCompiler { rules }) + } + + /// Creates a new yara scanner for the rules defined earlier. + /// This can be called multiple times + /// + /// @returns A {@link YaraScanner} instance + #[napi] + pub fn new_scanner(&self, reference: Reference<YaraCompiler>, env: Env) -> Result<YaraScanner> { + YaraScanner::new(reference, env) + } +} + +#[napi] +impl YaraScanner { + pub fn new(reference: Reference<YaraCompiler>, env: Env) -> Result<Self> { + let scanner = reference.share_with(env, |compiler| { + Ok(compiler.rules.scanner().context("Failed to get scanner")?) + })?; + Ok(YaraScanner { scanner }) } + /// Converts the yara-rs types to types which we can return to napi-rs. + /// Ideally we dont need this but sadly for now this is required. fn convert_yara_results(&self, rules: Vec<ExtYaraRule>) -> Vec<YaraRuleResult> { let results = rules .iter() @@ -186,20 +227,68 @@ impl YaraScanner { results } + /// Scan a buffer of data with yara + /// + /// @param buffer - The data which shall be scanned by yara. + /// @throws This can throw if there is an unexpected error. + /// + /// @returns The results of yara scan_mem. #[napi] - pub fn scan_buffer(&self, buffer: Buffer) -> Result<Vec<YaraRuleResult>> { - let mut scanner = self.rules.scanner().context("Failed to get scanner")?; + pub fn scan_buffer(&mut self, buffer: Buffer) -> Result<Vec<YaraRuleResult>> { let buf: Vec<u8> = buffer.into(); - let results = scanner.scan_mem(&buf).context("Failed to scan buffer")?; + let results = self + .scanner + .scan_mem(&buf) + .context("Failed to scan buffer")?; Ok(self.convert_yara_results(results)) } + /// Scan a string of data with yara + /// + /// @param input - The data which shall be scanned by yara. + /// @throws This can throw if there is an unexpected error. + /// + /// @returns The results of yara scan_mem. #[napi] - pub fn scan_string(&self, input: String) -> Result<Vec<YaraRuleResult>> { - let mut scanner = self.rules.scanner().context("Failed to get scanner")?; + pub fn scan_string(&mut self, input: String) -> Result<Vec<YaraRuleResult>> { let buf: &[u8] = input.as_bytes(); - let results = scanner.scan_mem(buf).context("Failed to scan buffer")?; + let results = self + .scanner + .scan_mem(buf) + .context("Failed to scan string")?; + + Ok(self.convert_yara_results(results)) + } + + /// Scan a file with yara + /// + /// @param filepath - The path to the file yara shall scan + /// @throws This can throw if there is an unexpected error. + /// + /// @returns The results of yara scan_mem. + #[napi] + pub fn scan_file(&mut self, filepath: String) -> Result<Vec<YaraRuleResult>> { + let results = self + .scanner + .scan_file(filepath) + .context("Failed to scan file")?; + + Ok(self.convert_yara_results(results)) + } + + /// Scan a process with yara + /// + /// @param pid - The process id of the process that shall be scanned. + /// @throws This can throw if there is an unexpected error. + /// + /// @returns The results of yara scan_mem. + #[napi] + pub fn scan_process(&mut self, pid: u32) -> Result<Vec<YaraRuleResult>> { + let results = self + .scanner + .scan_process(pid) + .context("Failed to scan file")?; Ok(self.convert_yara_results(results)) } diff --git a/yarn.lock b/yarn.lock @@ -33,6 +33,7 @@ __metadata: resolution: "@node_yara_rs/node-yara-rs@workspace:." dependencies: "@napi-rs/cli": ^2.16.3 + "@types/node": ^20.8.0 ava: ^5.1.1 languageName: unknown linkType: soft @@ -87,6 +88,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^20.8.0": + version: 20.8.0 + resolution: "@types/node@npm:20.8.0" + checksum: ebad6342d54238a24bf980d7750117a5d67749c9b72cbb7a974a1e932c39034aa3a810d669e007e8a5071782a253aa069a187b614407a382403c9826e837c849 + languageName: node + linkType: hard + "abbrev@npm:^1.0.0": version: 1.1.1 resolution: "abbrev@npm:1.1.1"