node-yara-rs

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

commit 0d5c5f78b174c4965ec050494f9fc97718c56a84
parent ff2d786dab2ec2eba939b10c9f7ee1d750935bd7
Author: MTRNord <mtrnord1@gmail.com>
Date:   Sat,  7 Oct 2023 23:33:18 +0200

Add json parsing support to the yara build

Diffstat:
M.cargo/config.toml | 25+++++++++++++++++++++++++
M.github/workflows/CI.yml | 6+++++-
M.gitignore | 1+
A.vscode/settings.json | 13+++++++++++++
MCargo.toml | 1+
MMakefile | 45++++++++++++++++++++++++++++++++++++++++++++-
M__test__/index.spec.mjs | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
A__test__/json_test.yara | 44++++++++++++++++++++++++++++++++++++++++++++
A__test__/test.json | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Mbuild.rs | 11+++++++++++
Achangelog.d/+json.feature | 2++
Adeps/json.c | 485+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
12 files changed, 737 insertions(+), 2 deletions(-)

diff --git a/.cargo/config.toml b/.cargo/config.toml @@ -1,3 +1,27 @@ [env] YARA_LIBRARY_PATH = { value = "build/yara/lib", relative = true } YARA_INCLUDE_DIR = { value = "build/yara/include", relative = true } + +[target.x86_64-unknown-linux-gnu] +linker = "clang" +rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"] + +# NOTE: you must install [Mach-O LLD Port](https://lld.llvm.org/MachO/index.html) on mac. you can easily do this by installing llvm which includes lld with the "brew" package manager: +# `brew install llvm` +[target.x86_64-apple-darwin] +rustflags = [ + "-C", + "link-arg=-fuse-ld=/usr/local/opt/llvm/bin/ld64.lld", + "-Zshare-generics=y", +] + +[target.aarch64-apple-darwin] +rustflags = [ + "-C", + "link-arg=-fuse-ld=/opt/homebrew/opt/llvm/bin/ld64.lld", + "-Zshare-generics=y", +] + +[target.x86_64-pc-windows-msvc] +linker = "rust-lld.exe" +rustflags = ["-Zshare-generics=n"] +\ No newline at end of file diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml @@ -32,6 +32,8 @@ jobs: brew install automake brew install libtool brew install pkg-config + brew install jansson + brew install llvm make build strip -x *.node - host: windows-latest @@ -41,7 +43,7 @@ jobs: target: x86_64-unknown-linux-gnu build: |- set -e && - sudo apt-get install -y automake libtool make gcc pkg-config && + sudo apt-get install -y automake libtool make gcc pkg-config libjansson-dev && make debug_results && make yara && yarn build --target x86_64-unknown-linux-gnu && @@ -53,6 +55,8 @@ jobs: brew install automake brew install libtool brew install pkg-config + brew install jansson + brew install llvm CFLAGS="-target arm64-apple-macos11" make yara yarn build --target aarch64-apple-darwin strip -x *.node diff --git a/.gitignore b/.gitignore @@ -197,4 +197,5 @@ Cargo.lock *.node deps/yara-* +deps/jansson-* build \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "files.associations": { + "*.rs": "rust", + "*.zonefile": "zone", + "system_error": "c", + "modules.h": "c", + "globals.h": "c", + "stdio.h": "c", + "jansson.h": "c", + "jansson_config.h": "c" + } +} +\ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml @@ -2,6 +2,7 @@ edition = "2021" name = "mtrnord_node-yara-rs" version = "0.0.0" +links = "jansson" [lib] crate-type = ["cdylib"] diff --git a/Makefile b/Makefile @@ -5,6 +5,7 @@ ifeq ($(OSNAME),Darwin) CFLAGS += -I/usr/local/include/node CFLAGS += -I/usr/local/include LDFLAGS += -L/usr/local/lib +JANSSON_LDFLAGS += $(LDFLAGS) endif ifeq ($(findstring arm64,$(CFLAGS)),arm64) @@ -12,26 +13,65 @@ CFGOPTS += --host=aarch64-apple-darwin endif YARA?=4.3.2 +JANSSON?=2.13.1 libyara: yara clean: -rm -rf $(BASE)/build/yara -rm -rf $(BASE)/deps/yara-$(YARA) + -rm -rf $(BASE)/build/jansson + -rm -rf $(BASE)/deps/jansson-$(JANSSON) cargo clean -yara: clean +jansson: clean + echo $(CFLAGS) + echo $(LDFLAGS) + echo $(JANSSON_LDFLAGS) + echo $(CC) + test -f $(BASE)/deps/jansson-$(JANSSON).tar.gz || curl -L -k https://github.com/akheron/jansson/releases/download/v$(JANSSON)/jansson-$(JANSSON).tar.gz > $(BASE)/deps/jansson-$(JANSSON).tar.gz + cd $(BASE)/deps && tar -xzvf jansson-$(JANSSON).tar.gz + cd $(BASE)/deps/jansson-$(JANSSON) && \ + CFLAGS="$(CFLAGS)" \ + LDFLAGS="$(JANSSON_LDFLAGS)" \ + ./configure \ + $(CFGOPTS) \ + --enable-static \ + --disable-shared \ + --with-pic \ + --prefix=$(BASE)/build/jansson + cd $(BASE)/deps/jansson-$(JANSSON) && make + cd $(BASE)/deps/jansson-$(JANSSON) && make install + +yara: clean jansson echo $(CFLAGS) echo $(LDFLAGS) echo $(CC) test -f $(BASE)/deps/yara-$(YARA).tar.gz || curl -L -k https://github.com/VirusTotal/yara/archive/v$(YARA).tar.gz > $(BASE)/deps/yara-$(YARA).tar.gz cd $(BASE)/deps && tar -xzvf yara-$(YARA).tar.gz + + # Handle json.c + mkdir -p $(BASE)/deps/yara-$(YARA)/libyara/modules/json + echo "Copying json.c into libyara..." + cp $(BASE)/deps/json.c $(BASE)/deps/yara-$(YARA)/libyara/modules/json/json.c + @if grep -q "MODULE(json)" "$(BASE)/deps/yara-$(YARA)/libyara/modules/module_list"; then\ + echo "Already added json module to module list...";\ + else\ + echo "MODULE(json)" >> "$(BASE)/deps/yara-$(YARA)/libyara/modules/module_list";\ + fi + @if grep -q "MODULES += libyara/modules/json/json.c" "$(BASE)/deps/yara-$(YARA)/Makefile.am"; then\ + echo "Already added json module link to Makefile.am...";\ + else\ + echo "MODULES += libyara/modules/json/json.c" >> "$(BASE)/deps/yara-$(YARA)/Makefile.am";\ + fi + cd $(BASE)/deps/yara-$(YARA) && ./bootstrap.sh cd $(BASE)/deps/yara-$(YARA) && \ CFLAGS="$(CFLAGS)" \ LDFLAGS="$(LDFLAGS)" \ ./configure \ $(CFGOPTS) \ + --enable-cuckoo \ --enable-static \ --disable-shared \ --with-pic \ @@ -45,6 +85,9 @@ build: yara debug_results: build cargo rustc --release -- --print link-args ldd *.node + nm build/yara/bin/yara | grep json + nm build/yara/lib/libyara.a | grep json + nm *.node | grep json test: build yarn run test \ No newline at end of file diff --git a/__test__/index.spec.mjs b/__test__/index.spec.mjs @@ -1,6 +1,7 @@ import test from 'ava' import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; +import * as fs from 'fs'; import { YaraCompiler } from '../index.js' const TEST_RULE = "rule TestRule {\n condition:\n true\n}" @@ -104,4 +105,58 @@ test('can match file based rules', (t) => { } ]) }); +}) + + +test('can match file based json rules', (t) => { + const data = fs.readFileSync(join(__dirname, "./test.json"), 'utf8'); + t.plan(2) + t.notThrows(() => { + const compiler = new YaraCompiler([{ + filename: join(__dirname, "./json_test.yara") + }, { + filename: join(__dirname, "./test.yara") + }], []); + const scanner = compiler.newScanner(); + const result = scanner.scanString(data); + t.deepEqual(result, [ + { + identifier: "string_array_includes", + namespace: "default", + metadatas: [], + tags: [], + strings: [] + }, { + identifier: "string_array_includes_nested", + namespace: "default", + metadatas: [], + tags: [], + strings: [] + }, { + identifier: "integer_array_includes", + namespace: "default", + metadatas: [], + tags: [], + strings: [] + }, { + identifier: "integer_array_includes_nested", + namespace: "default", + metadatas: [], + tags: [], + strings: [] + }, { + identifier: "float_array_includes", + namespace: "default", + metadatas: [], + tags: [], + strings: [] + }, { + identifier: "float_array_includes_nested", + namespace: "default", + metadatas: [], + tags: [], + strings: [] + } + ]) + }); }) \ No newline at end of file diff --git a/__test__/json_test.yara b/__test__/json_test.yara @@ -0,0 +1,43 @@ +import "json" + +rule string_array_includes { + condition: + json.string_array_includes("strArray", "one") and + json.string_array_includes("strArray", "two") and + json.string_array_includes("strArray", "three") +} + +rule string_array_includes_nested { + condition: + json.string_array_includes("string.strArray", "one") and + json.string_array_includes("string.strArray", "two") and + json.string_array_includes("string.strArray", "three") +} + +rule integer_array_includes { + condition: + json.integer_array_includes("intArray", 1) and + json.integer_array_includes("intArray", 2) and + json.integer_array_includes("intArray", 3) +} + +rule integer_array_includes_nested { + condition: + json.integer_array_includes("integer.intArray", 1) and + json.integer_array_includes("integer.intArray", 2) and + json.integer_array_includes("integer.intArray", 3) +} + +rule float_array_includes { + condition: + json.float_array_includes("floatArray", 1.0) and + json.float_array_includes("floatArray", 2.0) and + json.float_array_includes("floatArray", 3.0) +} + +rule float_array_includes_nested { + condition: + json.float_array_includes("float.floatArray", 1.0) and + json.float_array_includes("float.floatArray", 2.0) and + json.float_array_includes("float.floatArray", 3.0) +} +\ No newline at end of file diff --git a/__test__/test.json b/__test__/test.json @@ -0,0 +1,50 @@ +{ + "keykey": "valuevalue", + "foo": { + "bar": "valuevalue", + "baz": { + "boo": "value" + } + }, + "numbers": { + "int": 1, + "float": 1.1 + }, + "regex": "e2c87f81a9bcb33d4f943a4072c137c2", + "strArray": [ + "one", + "two", + "three" + ], + "string": { + "strArray": [ + "one", + "two", + "three" + ] + }, + "intArray": [ + 1, + 2, + 3 + ], + "integer": { + "intArray": [ + 1, + 2, + 3 + ] + }, + "floatArray": [ + 1, + 2, + 3 + ], + "float": { + "floatArray": [ + 1, + 2, + 3 + ] + } +} +\ No newline at end of file diff --git a/build.rs b/build.rs @@ -1,7 +1,18 @@ +use std::path::PathBuf; + extern crate napi_build; fn main() { println!("cargo:rerun-if-changed=build"); println!("cargo:rerun-if-changed=deps"); + + // Workaround weird jansson linking + + let base = env!("CARGO_MANIFEST_DIR"); + let mut include_path = PathBuf::from(base); + include_path.push("build/jansson/lib"); + + println!("cargo:rustc-link-search=native={}", include_path.display()); + println!("cargo:rustc-link-lib=static=jansson"); napi_build::setup(); } diff --git a/changelog.d/+json.feature b/changelog.d/+json.feature @@ -0,0 +1 @@ +Add a json module to the used yara version +\ No newline at end of file diff --git a/deps/json.c b/deps/json.c @@ -0,0 +1,485 @@ +/* +Copyright (c) 2014. The YARA Authors. All Rights Reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <jansson.h> +#include <stdio.h> +#include <yara/modules.h> +#include <yara/globals.h> +#include <string.h> + +#define MODULE_NAME json + +// Ensures Key exists in JSON +define_function(key_exists) +{ + char *key = strdup(string_argument(1)); + + json_t *json = yr_module()->data; + if (json == NULL) + { + return_integer(0); + } + + // Split key into possible subcomponents - separated by '.' + json_t *iter = json; + char *token = strtok(key, "."); + while (token != NULL) + { + iter = json_object_get(iter, token); + if (iter == NULL) + { + return_integer(0); + } + token = strtok(NULL, "."); + } + + free(key); + free(token); + + return_integer(1); +} + +// Assert String value +define_function(value_exists_string) +{ + char *key = strdup(string_argument(1)); + char *value = strdup(string_argument(2)); + + json_t *json = yr_module()->data; + if (json == NULL) + { + return_integer(0); + } + + // Split key into possible subcomponents - separated by '.' + json_t *iter = json; + char *token = strtok(key, "."); + char *prevToken = strdup(token); + + while (token != NULL) + { + iter = json_object_get(iter, token); + if (iter == NULL) + { + return_integer(0); + } + + strcpy(prevToken, token); + token = strtok(NULL, "."); + } + + const char *found = json_string_value(iter); + if (found == NULL) + { + printf("no string value could be obtained from value at key\n"); + return_integer(0); + } + + if (strcmp(found, value) != 0) + { + return_integer(0); + } + free(key); + free(value); + free(prevToken); + free(token); + + return_integer(1); +} + +// Assert Integer value +define_function(value_exists_integer) +{ + char *key = strdup(string_argument(1)); + int value = integer_argument(2); + + json_t *json = yr_module()->data; + if (json == NULL) + { + return_integer(0); + } + + // Split key into possible subcomponents - separated by '.' + json_t *iter = json; + char *token = strtok(key, "."); + char *prevToken = strdup(token); + + while (token != NULL) + { + iter = json_object_get(iter, token); + if (iter == NULL) + { + return_integer(0); + } + + strcpy(prevToken, token); + token = strtok(NULL, "."); + } + + double foundNumber = json_number_value(iter); + if (foundNumber == 0.0) + { + printf("no number value could be obtained from value at key\n"); + return_integer(0); + } + int found = (int)foundNumber; + + if (found != value) + { + return_integer(0); + } + free(key); + free(prevToken); + free(token); + + return_integer(1); +} + +// Assert Regex value +define_function(value_exists_regex) +{ + YR_SCAN_CONTEXT *context = yr_scan_context(); + char *key = strdup(string_argument(1)); + RE *value = regexp_argument(2); + + json_t *json = yr_module()->data; + if (json == NULL) + { + return_integer(0); + } + + // Split key into possible subcomponents - separated by '.' + json_t *iter = json; + char *token = strtok(key, "."); + char *prevToken = strdup(token); + + while (token != NULL) + { + iter = json_object_get(iter, token); + if (iter == NULL) + { + return_integer(0); + } + + strcpy(prevToken, token); + token = strtok(NULL, "."); + } + + const char *found = json_string_value(iter); + if (found == NULL) + { + printf("no string value could be obtained from value at key\n"); + return_integer(0); + } + if (yr_re_match(context, value, found) <= 0) + { + return_integer(0); + } + + free(key); + free(prevToken); + free(token); + + return_integer(1); +} + +// Assert Float value +define_function(value_exists_float) +{ + char *key = strdup(string_argument(1)); + double value = float_argument(2); + + json_t *json = yr_module()->data; + if (json == NULL) + { + return_integer(0); + } + + // Split key into possible subcomponents - separated by '.' + json_t *iter = json; + char *token = strtok(key, "."); + char *prevToken = strdup(token); + + while (token != NULL) + { + iter = json_object_get(iter, token); + if (iter == NULL) + { + return_integer(0); + } + + strcpy(prevToken, token); + token = strtok(NULL, "."); + } + + double found = json_number_value(iter); + if (found == 0.0) + { + printf("no number value could be obtained from value at key\n"); + return_integer(0); + } + + if (found != value) + { + return_integer(0); + } + free(key); + free(prevToken); + free(token); + + return_integer(1); +} + +// String Array includes +define_function(string_array_includes) +{ + char *key = strdup(string_argument(1)); + char *value = strdup(string_argument(2)); + + json_t *json = yr_module()->data; + if (json == NULL) + { + return_integer(0); + } + + // Split key into possible subcomponents - separated by '.' + json_t *iter = json; + char *token = strtok(key, "."); + char *prevToken = strdup(token); + + while (token != NULL) + { + iter = json_object_get(iter, token); + if (iter == NULL) + { + return_integer(0); + } + + strcpy(prevToken, token); + token = strtok(NULL, "."); + } + + json_t *array = iter; + bool isArray = json_is_array(array); + + size_t index; + json_t *val; + + json_array_foreach(array, index, val) + { + json_t *element = json_array_get(array, index); + const char *found = json_string_value(element); + if (strcmp(found, value) == 0) + { + return_integer(1); + } + } + + free(array); + free(key); + free(key); + free(value); + free(prevToken); + free(token); + + return_integer(0); +} +// Integer Array includes +define_function(integer_array_includes) +{ + char *key = strdup(string_argument(1)); + int value = integer_argument(2); + + json_t *json = yr_module()->data; + if (json == NULL) + { + return_integer(0); + } + + // Split key into possible subcomponents - separated by '.' + json_t *iter = json; + char *token = strtok(key, "."); + char *prevToken = strdup(token); + + while (token != NULL) + { + iter = json_object_get(iter, token); + if (iter == NULL) + { + return_integer(0); + } + + strcpy(prevToken, token); + token = strtok(NULL, "."); + } + + json_t *array = iter; + bool isArray = json_is_array(array); + + size_t index; + json_t *val; + + json_array_foreach(array, index, val) + { + json_t *element = json_array_get(array, index); + double foundNumber = json_number_value(element); + if (foundNumber == 0.0) + { + printf("no number value could be obtained from value at key\n"); + return_integer(0); + } + int found = (int)foundNumber; + + if (found == value) + { + return_integer(1); + } + } + + free(array); + free(key); + free(key); + free(value); + free(prevToken); + free(token); + + return_integer(0); +} +// Float Array includes +define_function(float_array_includes) +{ + char *key = strdup(string_argument(1)); + double value = float_argument(2); + + json_t *json = yr_module()->data; + if (json == NULL) + { + return_integer(0); + } + + // Split key into possible subcomponents - separated by '.' + json_t *iter = json; + char *token = strtok(key, "."); + char *prevToken = strdup(token); + + while (token != NULL) + { + iter = json_object_get(iter, token); + if (iter == NULL) + { + return_integer(0); + } + + strcpy(prevToken, token); + token = strtok(NULL, "."); + } + + json_t *array = iter; + bool isArray = json_is_array(array); + + size_t index; + json_t *val; + + json_array_foreach(array, index, val) + { + json_t *element = json_array_get(array, index); + double found = json_number_value(element); + if (found == 0.0) + { + printf("no number value could be obtained from value at key\n"); + return_integer(0); + } + + if (found == value) + { + return_integer(1); + } + } + + free(array); + free(key); + free(key); + free(prevToken); + free(token); + + return_integer(0); +} + +begin_declarations; + +declare_function("key_exists", "s", "i", key_exists); +declare_function("value_exists", "ss", "i", value_exists_string); +declare_function("value_exists", "si", "i", value_exists_integer); +declare_function("value_exists", "sr", "i", value_exists_regex); +declare_function("value_exists", "sf", "i", value_exists_float); +declare_function("string_array_includes", "ss", "i", string_array_includes); +declare_function("integer_array_includes", "si", "i", integer_array_includes); +declare_function("float_array_includes", "sf", "i", float_array_includes); +end_declarations; + +int module_initialize(YR_MODULE *module) +{ + return ERROR_SUCCESS; +} + +int module_finalize(YR_MODULE *module) +{ + return ERROR_SUCCESS; +} + +int module_load( + YR_SCAN_CONTEXT *context, + YR_OBJECT *module_object, + void *module_data, + size_t module_data_size) +{ + // Get first Memory Block + YR_MEMORY_BLOCK *block = first_memory_block(context); + const uint8_t *block_data = block->fetch_data(block); + + // Parse and Save JSON to Module + json_error_t json_error; + int flags = JSON_DECODE_INT_AS_REAL | JSON_ALLOW_NUL; + + json_t *json = json_loads((const char *)block_data, flags, &json_error); + if (!json) + { + // fprintf(stderr, "most likely not a json file. error on line %d: %s\n", json_error.line, json_error.text); + // NOT a JSON file or a VALID JSON FILE + // YR_DEBUG_FPRINTF(2, stderr, "most likely not a json file. error on line %d: %s\n", json_error.line, json_error.text); + module_object->data = NULL; + return ERROR_SUCCESS; + } + + // JSON is valid + module_object->data = json; + + return ERROR_SUCCESS; +} + +int module_unload(YR_OBJECT *module_object) +{ + free(module_object->data); + return ERROR_SUCCESS; +}