node-yara-rs

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

commit 7c1107f4497ae86160dac8435951db3ecef4f367
parent f828c6ad5c3c3b0f108d069496529ecdeb72ec1c
Author: MTRNord <mtrnord1@gmail.com>
Date:   Sun,  1 Oct 2023 13:33:44 +0200

Fix return types for functions and write proper tests. Also reduce required dependencies

Diffstat:
MCargo.toml | 9+--------
MMakefile | 3---
M__test__/index.spec.mjs | 41++++++++++++++++++++++++++++++++++++++---
Mbuild.rs | 2++
Mindex.d.ts | 24++++++++++++------------
Mindex.js | 6+-----
Mpackage.json | 2+-
Msrc/lib.rs | 83++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Myarn.lock | 111++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
9 files changed, 218 insertions(+), 63 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -10,17 +10,10 @@ crate-type = ["cdylib"] # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix napi = { version = "2.12.2", default-features = false, features = [ "napi8", - "async", - "tokio_rt", "error_anyhow", - "serde-json", ] } napi-derive = "2.12.2" -serde_json = "1.0.107" -yara = { version = "0.21.0", features = [ - "module-magic", - "serde", -] } +yara = { version = "0.21.0" } [build-dependencies] napi-build = "2.0.1" diff --git a/Makefile b/Makefile @@ -25,10 +25,7 @@ yara: LDFLAGS="$(LDFLAGS)" \ ./configure \ $(CFGOPTS) \ - --enable-static \ - --disable-shared \ --with-pic \ - --without-crypto \ --prefix=$(BASE)/build/yara cd $(BASE)/deps/yara-$(YARA) && make cd $(BASE)/deps/yara-$(YARA) && make install diff --git a/__test__/index.spec.mjs b/__test__/index.spec.mjs @@ -1,7 +1,41 @@ import test from 'ava' -import { sum } from '../index.js' +import { YaraScanner } from '../index.js' -test('sum from native', (t) => { - t.is(sum(1, 2), 3) +const TEST_RULE = "rule TestRule {\n condition:\n true\n}" + +test('can construct YaraScanner', (t) => { + t.plan(1) + t.notThrows(() => { + const scanner = new YaraScanner([], []); + }); +}) + +test('can load string rules', (t) => { + t.plan(1) + t.notThrows(() => { + const scanner = new YaraScanner([{ + string: TEST_RULE + }], []); + }); }) + + +test('can match string rules', (t) => { + t.plan(2) + t.notThrows(() => { + const scanner = new YaraScanner([{ + string: TEST_RULE + }], []); + const result = scanner.scanString(""); + t.deepEqual(result, [ + { + identifier: "TestRule", + namespace: "default", + metadatas: [], + tags: [], + strings: [] + } + ]) + }); +}) +\ No newline at end of file diff --git a/build.rs b/build.rs @@ -1,5 +1,7 @@ extern crate napi_build; fn main() { + println!("cargo:rerun-if-changed=build"); + println!("cargo:rerun-if-changed=deps"); napi_build::setup(); } diff --git a/index.d.ts b/index.d.ts @@ -19,12 +19,7 @@ export interface YaraVariable { /** Limitation of napi-rs which doesnt support any */ stringValue?: string } -export class YaraScanner { - constructor(rules: Array<YaraRule>, variables: Array<YaraVariable>) - scanBuffer(buffer: Buffer, timeout: number): Array<YaraRuleResult> - scanString(input: string, timeout: number): Array<YaraRuleResult> -} -export class YaraRuleResult { +export interface YaraRuleResult { /** Name of the rule. */ identifier: string /** Namespace of the rule. */ @@ -36,19 +31,19 @@ export class YaraRuleResult { /** Matcher strings of the rule. */ strings: Array<YaraString> } -export class YaraRuleMetadata { +export interface YaraRuleMetadata { identifier: string - integerValue: number - stringValue: string - boolValue: boolean + integerValue?: number + stringValue?: string + boolValue?: boolean } -export class YaraString { +export interface YaraString { /** Name of the string, with the '$'. */ identifier: string /** Matches of the string for the scan. */ matches: Array<YaraMatch> } -export class YaraMatch { +export interface YaraMatch { base: number /** Offset of the match within the scanning area. */ offset: number @@ -57,3 +52,8 @@ export class YaraMatch { /** Matched data. */ data: Array<number> } +export class YaraScanner { + constructor(rules: Array<YaraRule>, variables: Array<YaraVariable>) + scanBuffer(buffer: Buffer): Array<YaraRuleResult> + scanString(input: string): Array<YaraRuleResult> +} diff --git a/index.js b/index.js @@ -252,10 +252,6 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { YaraScanner, YaraRuleResult, YaraRuleMetadata, YaraString, YaraMatch } = nativeBinding +const { YaraScanner } = nativeBinding module.exports.YaraScanner = YaraScanner -module.exports.YaraRuleResult = YaraRuleResult -module.exports.YaraRuleMetadata = YaraRuleMetadata -module.exports.YaraString = YaraString -module.exports.YaraMatch = YaraMatch diff --git a/package.json b/package.json @@ -21,7 +21,7 @@ "timeout": "3m" }, "engines": { - "node": ">= 10" + "node": ">= 18" }, "scripts": { "artifacts": "napi artifacts", diff --git a/src/lib.rs b/src/lib.rs @@ -1,12 +1,13 @@ #![deny(clippy::all)] use napi::{anyhow::Context, bindgen_prelude::Buffer, Result}; -use yara::{Compiler, Rule as ExtYaraRule, Rules}; +use yara::{Compiler, MetadataValue, Rule as ExtYaraRule, Rules}; #[macro_use] extern crate napi_derive; #[napi(object)] +#[derive(Debug)] pub struct YaraRule { pub filename: Option<String>, pub string: Option<String>, @@ -14,6 +15,7 @@ pub struct YaraRule { } #[napi(object)] +#[derive(Debug)] pub struct YaraVariable { pub id: String, /// Limitation of napi-rs which doesnt support any @@ -31,40 +33,40 @@ pub struct YaraScanner { rules: Rules, } -#[napi] +#[napi(object)] +#[derive(Debug)] pub struct YaraRuleResult { /// Name of the rule. pub identifier: String, /// Namespace of the rule. pub namespace: String, /// Metadatas of the rule. - #[napi(ts_type = "Array<YaraRuleMetadata>")] - pub metadatas: Vec<serde_json::Value>, + pub metadatas: Vec<YaraRuleMetadata>, /// Tags of the rule. pub tags: Vec<String>, /// Matcher strings of the rule. - #[napi(ts_type = "Array<YaraString>")] - pub strings: Vec<serde_json::Value>, + pub strings: Vec<YaraString>, } -#[napi] +#[napi(object)] +#[derive(Debug, Default)] pub struct YaraRuleMetadata { pub identifier: String, - pub integer_value: i64, - pub string_value: String, - pub bool_value: bool, + pub integer_value: Option<i64>, + pub string_value: Option<String>, + pub bool_value: Option<bool>, } -#[napi] +#[napi(object)] +#[derive(Debug)] pub struct YaraString { /// Name of the string, with the '$'. pub identifier: String, /// Matches of the string for the scan. - #[napi(ts_type = "Array<YaraMatch>")] - pub matches: Vec<serde_json::Value>, + pub matches: Vec<YaraMatch>, } -#[napi] +#[napi(object)] #[derive(Debug, Clone)] pub struct YaraMatch { // base offset of the memory block in which the match occurred. @@ -135,7 +137,7 @@ impl YaraScanner { } fn convert_yara_results(&self, rules: Vec<ExtYaraRule>) -> Vec<YaraRuleResult> { - rules + let results = rules .iter() .map(|rule| YaraRuleResult { identifier: rule.identifier.to_string(), @@ -143,40 +145,61 @@ impl YaraScanner { metadatas: rule .metadatas .iter() - .map(|metadata| { - serde_json::to_value(metadata).expect("Failed to serialize metadata in result") + .map(|metadata| match metadata.value { + MetadataValue::Integer(int) => YaraRuleMetadata { + identifier: metadata.identifier.to_string(), + integer_value: Some(int), + ..Default::default() + }, + MetadataValue::String(string) => YaraRuleMetadata { + identifier: metadata.identifier.to_string(), + string_value: Some(string.to_string()), + ..Default::default() + }, + MetadataValue::Boolean(boolean) => YaraRuleMetadata { + identifier: metadata.identifier.to_string(), + bool_value: Some(boolean), + ..Default::default() + }, }) .collect(), tags: rule.tags.iter().map(ToString::to_string).collect(), strings: rule .strings .iter() - .map(|string| { - serde_json::to_value(string).expect("Failed to serialize metadata in result") + .map(|string| YaraString { + identifier: string.identifier.to_string(), + matches: string + .matches + .iter() + .map(|matches| YaraMatch { + base: matches.base as i64, + offset: matches.offset as i64, + length: matches.length as i64, + data: matches.data.clone(), + }) + .collect(), }) .collect(), }) - .collect() + .collect(); + results } #[napi] - pub fn scan_buffer(&self, buffer: Buffer, timeout: i32) -> Result<Vec<YaraRuleResult>> { + pub fn scan_buffer(&self, buffer: Buffer) -> Result<Vec<YaraRuleResult>> { + let mut scanner = self.rules.scanner().context("Failed to get scanner")?; let buf: Vec<u8> = buffer.into(); - let results = self - .rules - .scan_mem(&buf, timeout) - .context("Failed to scan buffer")?; + let results = scanner.scan_mem(&buf).context("Failed to scan buffer")?; Ok(self.convert_yara_results(results)) } #[napi] - pub fn scan_string(&self, input: String, timeout: i32) -> Result<Vec<YaraRuleResult>> { + pub fn scan_string(&self, input: String) -> Result<Vec<YaraRuleResult>> { + let mut scanner = self.rules.scanner().context("Failed to get scanner")?; let buf: &[u8] = input.as_bytes(); - let results = self - .rules - .scan_mem(buf, timeout) - .context("Failed to scan buffer")?; + let results = scanner.scan_mem(buf).context("Failed to scan buffer")?; Ok(self.convert_yara_results(results)) } diff --git a/yarn.lock b/yarn.lock @@ -5,6 +5,16 @@ __metadata: version: 6 cacheKey: 8 +"@ava/typescript@npm:^4.1.0": + version: 4.1.0 + resolution: "@ava/typescript@npm:4.1.0" + dependencies: + escape-string-regexp: ^5.0.0 + execa: ^7.1.1 + checksum: b0e0aa2a5d3bc8514dea73a2f8884bc6011c98d28e9f476dbfbdbab679f4ac17ac0d4ecb01ca9a6d6553db81a0e7786584625ec11cbd0b4e2743844470742160 + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -32,8 +42,10 @@ __metadata: version: 0.0.0-use.local resolution: "@node_yara_rs/node-yara-rs@workspace:." dependencies: + "@ava/typescript": ^4.1.0 "@napi-rs/cli": ^2.16.3 ava: ^5.1.1 + typescript: ^5.2.2 languageName: unknown linkType: soft @@ -554,7 +566,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.0": +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" dependencies: @@ -700,6 +712,23 @@ __metadata: languageName: node linkType: hard +"execa@npm:^7.1.1": + version: 7.2.0 + resolution: "execa@npm:7.2.0" + dependencies: + cross-spawn: ^7.0.3 + get-stream: ^6.0.1 + human-signals: ^4.3.0 + is-stream: ^3.0.0 + merge-stream: ^2.0.0 + npm-run-path: ^5.1.0 + onetime: ^6.0.0 + signal-exit: ^3.0.7 + strip-final-newline: ^3.0.0 + checksum: 14fd17ba0ca8c87b277584d93b1d9fc24f2a65e5152b31d5eb159a3b814854283eaae5f51efa9525e304447e2f757c691877f7adff8fde5746aae67eb1edd1cc + languageName: node + linkType: hard + "exponential-backoff@npm:^3.1.1": version: 3.1.1 resolution: "exponential-backoff@npm:3.1.1" @@ -842,6 +871,13 @@ __metadata: languageName: node linkType: hard +"get-stream@npm:^6.0.1": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: e04ecece32c92eebf5b8c940f51468cd53554dcbb0ea725b2748be583c9523d00128137966afce410b9b051eb2ef16d657cd2b120ca8edafcf5a65e81af63cad + languageName: node + linkType: hard + "glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -935,6 +971,13 @@ __metadata: languageName: node linkType: hard +"human-signals@npm:^4.3.0": + version: 4.3.1 + resolution: "human-signals@npm:4.3.1" + checksum: 6f12958df3f21b6fdaf02d90896c271df00636a31e2bbea05bddf817a35c66b38a6fdac5863e2df85bd52f34958997f1f50350ff97249e1dff8452865d5235d1 + languageName: node + linkType: hard + "humanize-ms@npm:^1.2.1": version: 1.2.1 resolution: "humanize-ms@npm:1.2.1" @@ -1093,6 +1136,13 @@ __metadata: languageName: node linkType: hard +"is-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "is-stream@npm:3.0.0" + checksum: 172093fe99119ffd07611ab6d1bcccfe8bc4aa80d864b15f43e63e54b7abc71e779acd69afdb854c4e2a67fdc16ae710e370eda40088d1cfc956a50ed82d8f16 + languageName: node + linkType: hard + "is-unicode-supported@npm:^1.2.0": version: 1.3.0 resolution: "is-unicode-supported@npm:1.3.0" @@ -1245,6 +1295,13 @@ __metadata: languageName: node linkType: hard +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 6fa4dcc8d86629705cea944a4b88ef4cb0e07656ebf223fa287443256414283dd25d91c1cd84c77987f2aec5927af1a9db6085757cb43d90eb170ebf4b47f4f4 + languageName: node + linkType: hard + "merge2@npm:^1.3.0, merge2@npm:^1.4.1": version: 1.4.1 resolution: "merge2@npm:1.4.1" @@ -1447,6 +1504,15 @@ __metadata: languageName: node linkType: hard +"npm-run-path@npm:^5.1.0": + version: 5.1.0 + resolution: "npm-run-path@npm:5.1.0" + dependencies: + path-key: ^4.0.0 + checksum: dc184eb5ec239d6a2b990b43236845332ef12f4e0beaa9701de724aa797fe40b6bbd0157fb7639d24d3ab13f5d5cf22d223a19c6300846b8126f335f788bee66 + languageName: node + linkType: hard + "npmlog@npm:^6.0.0": version: 6.0.2 resolution: "npmlog@npm:6.0.2" @@ -1468,6 +1534,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^6.0.0": + version: 6.0.0 + resolution: "onetime@npm:6.0.0" + dependencies: + mimic-fn: ^4.0.0 + checksum: 0846ce78e440841335d4e9182ef69d5762e9f38aa7499b19f42ea1c4cd40f0b4446094c455c713f9adac3f4ae86f613bb5e30c99e52652764d06a89f709b3788 + languageName: node + linkType: hard + "p-defer@npm:^1.0.0": version: 1.0.0 resolution: "p-defer@npm:1.0.0" @@ -1555,6 +1630,13 @@ __metadata: languageName: node linkType: hard +"path-key@npm:^4.0.0": + version: 4.0.0 + resolution: "path-key@npm:4.0.0" + checksum: 8e6c314ae6d16b83e93032c61020129f6f4484590a777eed709c4a01b50e498822b00f76ceaf94bc64dbd90b327df56ceadce27da3d83393790f1219e07721d7 + languageName: node + linkType: hard + "path-scurry@npm:^1.10.1": version: 1.10.1 resolution: "path-scurry@npm:1.10.1" @@ -1891,6 +1973,13 @@ __metadata: languageName: node linkType: hard +"strip-final-newline@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-final-newline@npm:3.0.0" + checksum: 23ee263adfa2070cd0f23d1ac14e2ed2f000c9b44229aec9c799f1367ec001478469560abefd00c5c99ee6f0b31c137d53ec6029c53e9f32a93804e18c201050 + languageName: node + linkType: hard + "supertap@npm:^3.0.1": version: 3.0.1 resolution: "supertap@npm:3.0.1" @@ -1947,6 +2036,26 @@ __metadata: languageName: node linkType: hard +"typescript@npm:^5.2.2": + version: 5.2.2 + resolution: "typescript@npm:5.2.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 7912821dac4d962d315c36800fe387cdc0a6298dba7ec171b350b4a6e988b51d7b8f051317786db1094bd7431d526b648aba7da8236607febb26cf5b871d2d3c + languageName: node + linkType: hard + +"typescript@patch:typescript@^5.2.2#~builtin<compat/typescript>": + version: 5.2.2 + resolution: "typescript@patch:typescript@npm%3A5.2.2#~builtin<compat/typescript>::version=5.2.2&hash=f3b441" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 0f4da2f15e6f1245e49db15801dbee52f2bbfb267e1c39225afdab5afee1a72839cd86000e65ee9d7e4dfaff12239d28beaf5ee431357fcced15fb08583d72ca + languageName: node + linkType: hard + "unique-filename@npm:^3.0.0": version: 3.0.0 resolution: "unique-filename@npm:3.0.0"