From 599b5180e9079c3f819513b7e2f72b54edb62a75 Mon Sep 17 00:00:00 2001 From: Dmitry Ng <19asdek91@gmail.com> Date: Wed, 24 May 2023 19:39:50 +0300 Subject: [PATCH] feat: major update of yara scanner module which brings new caching of scans result with policy timeout --- config.json | 10 +- yara_scanner/1.0.0/bmodule/new-check.vue | 4 +- .../1.0.0/cmodule/cache/cache_logic.lua | 105 ++ .../cmodule/cache/scan_results_cache.lua | 106 ++ .../clibs/linux/386/libyara_scanner.so | Bin 2200324 -> 2200324 bytes .../clibs/linux/amd64/libyara_scanner.so | Bin 2129320 -> 2129320 bytes .../1.0.0/cmodule/engines/acts_engine.lua | 983 ++++++++++-------- .../1.0.0/cmodule/engines/db_engine.lua | 383 ++++--- yara_scanner/1.0.0/cmodule/main.lua | 153 +-- yara_scanner/1.0.0/cmodule/mmap.lua | 165 +-- .../1.0.0/cmodule/process_api/process_api.lua | 4 + .../cmodule/process_api/process_api_linux.lua | 139 +++ .../cmodule/process_api/process_api_osx.lua | 69 ++ .../process_api/process_api_windows.lua | 160 +++ .../1.0.0/cmodule/utils/yara_utils.lua | 19 + yara_scanner/1.0.0/cmodule/yara_cmodule.lua | 46 +- .../1.0.0/config/action_config_schema.json | 78 ++ yara_scanner/1.0.0/config/config_schema.json | 21 +- .../1.0.0/config/current_action_config.json | 14 + yara_scanner/1.0.0/config/current_config.json | 5 +- .../1.0.0/config/current_event_config.json | 114 ++ .../1.0.0/config/default_action_config.json | 14 + yara_scanner/1.0.0/config/default_config.json | 5 +- .../1.0.0/config/default_event_config.json | 114 ++ .../1.0.0/config/event_config_schema.json | 242 ++++- yara_scanner/1.0.0/config/info.json | 10 +- yara_scanner/1.0.0/config/locale.json | 100 +- 27 files changed, 2303 insertions(+), 760 deletions(-) create mode 100644 yara_scanner/1.0.0/cmodule/cache/cache_logic.lua create mode 100644 yara_scanner/1.0.0/cmodule/cache/scan_results_cache.lua create mode 100644 yara_scanner/1.0.0/cmodule/process_api/process_api.lua create mode 100644 yara_scanner/1.0.0/cmodule/process_api/process_api_linux.lua create mode 100644 yara_scanner/1.0.0/cmodule/process_api/process_api_osx.lua create mode 100644 yara_scanner/1.0.0/cmodule/process_api/process_api_windows.lua create mode 100644 yara_scanner/1.0.0/cmodule/utils/yara_utils.lua diff --git a/config.json b/config.json index f81bb2b..d5cbdd7 100644 --- a/config.json +++ b/config.json @@ -275,9 +275,11 @@ }, "actions": [ "yr_object_scan_proc", + "yr_object_scan_proc_non_cached", "yr_object_task_scan_proc", "yr_scan_fs", "yr_subject_scan_proc", + "yr_subject_scan_proc_non_cached", "yr_subject_task_scan_proc", "yr_task_fastscan_fs", "yr_task_fastscan_proc", @@ -293,12 +295,18 @@ "yr_module_started", "yr_module_stopped", "yr_object_process_matched_high", + "yr_object_process_matched_high_cached", "yr_object_process_matched_low", + "yr_object_process_matched_low_cached", "yr_object_process_matched_medium", + "yr_object_process_matched_medium_cached", "yr_process_matched_custom", "yr_subject_process_matched_high", + "yr_subject_process_matched_high_cached", "yr_subject_process_matched_low", - "yr_subject_process_matched_medium" + "yr_subject_process_matched_low_cached", + "yr_subject_process_matched_medium", + "yr_subject_process_matched_medium_cached" ], "fields": [ "malware_class", diff --git a/yara_scanner/1.0.0/bmodule/new-check.vue b/yara_scanner/1.0.0/bmodule/new-check.vue index 658754c..7f005a7 100644 --- a/yara_scanner/1.0.0/bmodule/new-check.vue +++ b/yara_scanner/1.0.0/bmodule/new-check.vue @@ -324,7 +324,7 @@ module.exports = { return this.checkForm.scope.type !== this.CheckScope.Scan || (this.checkForm.scope.type === this.CheckScope.Scan && this.checkForm.scope.value && !(/(\/\/)|(\\\\)/g.test(this.checkForm.scope.value))); }, isValidProcess() { - return this.checkForm.type === this.CheckType.Process && this.checkForm.scope.type === this.CheckScope.Scan ? this.checkForm.scope.id : true; + return this.checkForm.type === this.CheckType.Process && this.checkForm.scope.type === this.CheckScope.Scan ? (this.checkForm.scope.value ? true : this.checkForm.scope.id) : true; }, isValidEditor() { return this.checkForm.rules.type === this.CheckRules.Custom ? this.editor.getValue() : true; @@ -515,7 +515,7 @@ module.exports = { async save() { this.hasFirstSave = true; - if (this.canStartCheck && this.isValidPath && this.isValidProcess && this.isValidEditor) { + if (this.canStartCheck && (this.isValidPath || this.isValidProcess) && this.isValidEditor) { this.isSaving = true; this.checkForm.rules.value = this.editor.getValue(); this.checkForm.scope.id = +this.checkForm.scope.id; diff --git a/yara_scanner/1.0.0/cmodule/cache/cache_logic.lua b/yara_scanner/1.0.0/cmodule/cache/cache_logic.lua new file mode 100644 index 0000000..d45dc5f --- /dev/null +++ b/yara_scanner/1.0.0/cmodule/cache/cache_logic.lua @@ -0,0 +1,105 @@ +require("yaci") +local glue = require("glue") + +local CacheLogic = newclass("CacheLogic") + +local DEFAULT_YARA_PROCESS_CACHING_TIME = 60.0 + +function CacheLogic:init() + local YaraUtils = require("utils.yara_utils") + self.yara_utils = YaraUtils() +end + +function CacheLogic:set_yara_process_caching_time(minutes) + assert((type(minutes) == "number"), "CacheLogic:set_yara_process_caching_time ~ invalid minutes parameter") + self.yara_process_caching_time = minutes +end + +function CacheLogic:get_yara_process_caching_time() + assert((type(self.yara_process_caching_time) == "number"), "CacheLogic:get_yara_process_caching_time ~ invalid yara_process_caching_time") + return self.yara_process_caching_time +end + +function CacheLogic:get_yara_process_caching_time_in_seconds() + return self.yara_utils:to_seconds(self:get_yara_process_caching_time()) +end + +function CacheLogic:get_default_yara_process_caching_time() + return DEFAULT_YARA_PROCESS_CACHING_TIME +end + +function CacheLogic:is_make_scan( --[[proc_id, proc_image, ]] scan_results) + if not scan_results then + return true + end + if not scan_results.yara_scan_results then + return true + end + local last_scan_time = scan_results.last_scan_time + if type(last_scan_time) ~= "number" then + __log.error("Non-valid last_scan_time type: " .. type(last_scan_time)) + return true + end + + if self:get_yara_process_caching_time_in_seconds() <= 0 then + return true + end + + local elapsed_time = os.difftime(self.yara_utils:get_UTC_time(), last_scan_time) + __log.info("CacheLogic:is_make_scan ~ elapsed time between scans: " .. elapsed_time) + if elapsed_time > self:get_yara_process_caching_time_in_seconds() then + return true + end + + return false +end + +function CacheLogic:is_make_caching(yara_scan_results) + if not yara_scan_results then + return false + end + if self:is_yara_error(yara_scan_results) then + return false + end + if self:get_yara_process_caching_time_in_seconds() <= 0 then + return false + end + return true +end + +function CacheLogic:is_yara_error(yara_scan_results) + if type(yara_scan_results) ~= "table" then + return false + end + if glue.count(yara_scan_results) ~= 1 then + return false + end + for _, item in ipairs(yara_scan_results) do + if not item then + return false + end + if not item.error then + return false + end + end + return true +end + +function CacheLogic:is_use_cached_results(action_name) + if type(action_name) ~= "string" then + return false + end + if action_name == "yr_subject_scan_proc_non_cached" then + return false + end + if action_name == "yr_object_scan_proc_non_cached" then + return false + end + return true +end + +function CacheLogic:cleanup() + self.yara_utils = nil +end + +return CacheLogic diff --git a/yara_scanner/1.0.0/cmodule/cache/scan_results_cache.lua b/yara_scanner/1.0.0/cmodule/cache/scan_results_cache.lua new file mode 100644 index 0000000..f904918 --- /dev/null +++ b/yara_scanner/1.0.0/cmodule/cache/scan_results_cache.lua @@ -0,0 +1,106 @@ +require("yaci") +require("engines.db_engine") +local cjson = require("cjson") + +local ScanResultsCache = newclass("ScanResultsCache") + +function ScanResultsCache:init(db_engine) + self.db_engine = db_engine +end + +function ScanResultsCache:add(process_id, process_image, scan_results) + if not self.db_engine then + __log.error("ScanResultsCache:add ~ db_engine is not initialized") + return false + end + + if (type(process_id) ~= "number") then + __log.error("ScanResultsCache:add ~ non-valid process_id parameter") + return false + end + + if (type(process_image) ~= "string") then + __log.error("ScanResultsCache:add ~ non-valid process_image parameter") + return false + end + + if not scan_results then + __log.error("ScanResultsCache:add ~ non-valid scan_results parameter") + return false + end + + if not scan_results.yara_scan_results then + __log.error("ScanResultsCache:add ~ non-valid scan_results.yara_scan_results") + return false + end + + local yara_scan_results = scan_results.yara_scan_results + + if type(yara_scan_results) ~= "table" then + __log.error("ScanResultsCache:add ~ non-table scan_results.yara_scan_results") + return false + end + + yara_scan_results = cjson.encode(yara_scan_results) + + return self.db_engine:add_to_process_cache(process_id, process_image, scan_results.last_scan_time, yara_scan_results) +end + +function ScanResultsCache:get(process_id, process_image) + if not self.db_engine then + __log.error("ScanResultsCache:get ~ db_engine is not initialized") + return nil + end + + if (type(process_id) ~= "number") then + __log.error("ScanResultsCache:add ~ non-valid process_id parameter") + return nil + end + + if (type(process_image) ~= "string") then + __log.error("ScanResultsCache:add ~ non-valid process_image parameter") + return nil + end + + local query_scan_results = self.db_engine:get_from_process_cache(process_id, process_image) + + if not query_scan_results then + __log.debug("ScanResultsCache:get ~ no scan results in process cache") + return nil + end + + if type(query_scan_results) ~= "table" then + __log.error("ScanResultsCache:get ~ non-valid scan results ~ process cache") + return nil + end + + query_scan_results.scan_results = cjson.decode(query_scan_results.scan_results) + + return query_scan_results +end + +function ScanResultsCache:remove(process_id, process_image) + -- TODO return success \ failure + if not self.db_engine then + __log.error("ScanResultsCache:remove ~ db_engine is not initialized") + return + end + + if (type(process_id) ~= "number") then + __log.error("ScanResultsCache:add ~ non-valid process_id parameter") + return + end + + if (type(process_image) ~= "string") then + __log.error("ScanResultsCache:add ~ non-valid process_image parameter") + return + end + + self.db_engine:delete_from_process_cache(process_id, process_image) +end + +function ScanResultsCache:cleanup() + self.db_engine = nil +end + +return ScanResultsCache diff --git a/yara_scanner/1.0.0/cmodule/clibs/linux/386/libyara_scanner.so b/yara_scanner/1.0.0/cmodule/clibs/linux/386/libyara_scanner.so index ceee28e6c2c37963bb458360e947b7dcb67a5032..24695f8eade1bd2188ed98b18e0d1617a5812acb 100755 GIT binary patch delta 150 zcmWN=yA6V16o64tKzS%Cg1i(gsO_EPZzUnbhKdkMiW}L)2}~S^Rs2qJfR5}rf$21no4DI%?&Nubm(%+9X;-O;E_I03>fmvh%pnU%y{9IIg4MZ HZu8?0JNPT- delta 150 zcmWN=K@vh>7=YnX5=Dg~B3fA6JITMrs+oVLg$uO!MmKQ+Gmhgbz0b@e(`X34V#r3e zV#;o|66JpSR@cLzYyIwim9n^}O@{{_>C)qgXI|(tV8|T*Uzd&kf)Nj?$&~fv5RZM#GO9 zI@XDrYU#9RyC1+H^RlOOFdKx#F4|`V6>b$cQ_}Ot@#t1CPwQ IUQWaF57)aij{pDw delta 144 zcmWN=OA3Nu6o66G?DnuUD<4_v4qQ_;3Vv`P%?mEL6&DaVZUL^qr8x0BaJuPhb^NHM zwmP~{SC>Eei&sDIrfM2fnb)HoM7ZLb8*b@w$31-pJTT;uC!QHGX2O&gX1wyooJBuK GIlgD^*EGfe diff --git a/yara_scanner/1.0.0/cmodule/engines/acts_engine.lua b/yara_scanner/1.0.0/cmodule/engines/acts_engine.lua index b4bed39..0f81769 100644 --- a/yara_scanner/1.0.0/cmodule/engines/acts_engine.lua +++ b/yara_scanner/1.0.0/cmodule/engines/acts_engine.lua @@ -18,7 +18,7 @@ require("yara_cmodule") local scan_tag = { FILE = 4294967294, - MEM = 4294967295 + MEM = 4294967295, } local scan_type = { @@ -38,6 +38,11 @@ local task_status = { INTERRUPTED = 4, } +local scan_target = { + PROCESS = "process", + FILE = "file", +} + -- --[[ @@ -58,11 +63,11 @@ function CActsEngine:init(cfg) do local active_rules = {} - for rule_id,rule_meta in pairs(cfg.rules_meta_files) do + for rule_id, rule_meta in pairs(cfg.rules_meta_files) do active_rules[rule_id] = rule_meta end - for rule_id,rule_meta in pairs(cfg.rules_meta_mem) do + for rule_id, rule_meta in pairs(cfg.rules_meta_mem) do active_rules[rule_id] = rule_meta end @@ -74,26 +79,34 @@ function CActsEngine:init(cfg) -- initialization of object after base class constructing + local ScanResultsCache = require("cache.scan_results_cache") + self.scan_results_cache = ScanResultsCache(self.db_engine) + + local CacheLogic = require("cache.cache_logic") + self.cache_logic = CacheLogic() + + local YaraUtils = require("utils.yara_utils") + self.yara_utils = YaraUtils() + self.config_suffix = cfg.config_suffix self:update_config_cb() self:yara_update_interrupted_tasks() assert(self:yara_init(cfg)) - -- cleanup + self:init_cleanup(cfg) +end +function CActsEngine:init_cleanup(cfg) cfg.rules_meta_files = nil cfg.rules_meta_mem = nil cfg.rules_files = nil cfg.rules_mem = nil end --- in: nil --- out: nil function CActsEngine:free() - __log.debug("finalize CActsEngine object") - -- here will be triggered after closing vxproto object (destructor of the state) + __log.debug("finalize CActsEngine object") end -- in: nil @@ -105,24 +118,22 @@ function CActsEngine:timer_cb() local acts_engine = CActsEngine:cast(self) local db_engine = acts_engine.db_engine - while acts_engine.yara_ctx.q_res:length() ~= 0 do - - local _,res = acts_engine.yara_ctx.q_res:shift(0) + while acts_engine.yara_ctx.scan_results_queue:length() ~= 0 do + local _, res = acts_engine.yara_ctx.scan_results_queue:shift(0) local task_id = acts_engine.yara_ctx.task_ids[res.task_id] if res.data == nil then -- task complete - acts_engine.yara_ctx.task_ids[res.task_id] = nil local workers = acts_engine.yara_ctx.task_ctx[task_id].workers if workers == 1 then - - db_engine:add_task_result(task_id, res.err and task_status.ERROR or task_status.COMPLETED, res.time_end, res.err) + db_engine:add_task_result(task_id, res.err and task_status.ERROR or task_status.COMPLETED, res.time_end, + res.err) local tag = acts_engine.yara_ctx.user_rules_tags[task_id] if type(tag) ~= "nil" then - acts_engine.yara_ctx.m:unload_rules({[1] = tag}) + acts_engine.yara_ctx.m:unload_rules({ [1] = tag }) acts_engine.yara_ctx.user_rules_tags[task_id] = nil end @@ -130,7 +141,8 @@ function CActsEngine:timer_cb() payload.type = "yr_task_result" payload.data = {} payload.data.task_id = task_id - payload.data.detects = acts_engine.db_engine:db_request("db_req_task_detects", nil, nil, nil, nil, {task_id = task_id}) + payload.data.detects = acts_engine.db_engine:db_request("db_req_task_detects", nil, nil, nil, nil, + { task_id = task_id }) payload.data.error = res.err payload.data.user_data = acts_engine.yara_ctx.task_ctx[task_id].user_data @@ -152,42 +164,43 @@ function CActsEngine:timer_cb() local rules = {} if type(task.custom_rules) ~= "nil" then - for _,rule_id in ipairs(res.data.rules) do - table.insert(rules, {rule_id = rule_id}) + for _, rule_id in ipairs(res.data.rules) do + table.insert(rules, { rule_id = rule_id }) end else - for _,rule_id in ipairs(res.data.rules) do - + for _, rule_id in ipairs(res.data.rules) do local rule = {} rule.rule_id = rule_id rule.meta = db_engine:get_rule_meta(rule_id) - for _,exclude_rule in ipairs(acts_engine.exclude_rules) do + for _, exclude_rule in ipairs(acts_engine.exclude_rules) do if exclude_rule.rule_name:lower() == rule.meta.rule_name:lower() then if is_process_res then - __log.infof('the rule skipped due to policy, proc_id = %d, proc_image = %s, rule_name = %s', - res.data.pid, res.data.imagepath, rule.meta.rule_name) + __log.infof("the rule skipped due to policy, proc_id = %d, proc_image = %s, rule_name = %s", + res.data.pid, res.data.imagepath, rule.meta.rule_name) elseif is_file_res then - __log.infof('the rule skipped due to policy, filepath = %s, sha256_filehash = %s, rule_name = %s', - res.data.filepath, res.data.sha256, rule.meta.rule_name) + __log.infof( + "the rule skipped due to policy, filepath = %s, sha256_filehash = %s, rule_name = %s", + res.data.filepath, res.data.sha256, rule.meta.rule_name) end goto rules_filter_continue end end - for _,malware_class_item in ipairs(acts_engine.malware_class_items) do + for _, malware_class_item in ipairs(acts_engine.malware_class_items) do if malware_class_item.malware_class:lower() == rule.meta.malware_class:lower() then - if malware_class_item.enabled == true then break end if is_process_res then - __log.infof('the rule skipped due to policy, proc_id = %d, proc_image = %s, rule_name = %s, malware_class = %s', - res.data.pid, res.data.imagepath, rule.meta.rule_name, rule.meta.malware_class) + __log.infof( + "the rule skipped due to policy, proc_id = %d, proc_image = %s, rule_name = %s, malware_class = %s", + res.data.pid, res.data.imagepath, rule.meta.rule_name, rule.meta.malware_class) elseif is_file_res then - __log.infof('the rule skipped due to policy, filepath = %s, sha256_filehash = %s, rule_name = %s, malware_class = %s', - res.data.filepath, res.data.sha256, rule.meta.rule_name, rule.meta.malware_class) + __log.infof( + "the rule skipped due to policy, filepath = %s, sha256_filehash = %s, rule_name = %s, malware_class = %s", + res.data.filepath, res.data.sha256, rule.meta.rule_name, rule.meta.malware_class) end goto rules_filter_continue end @@ -199,31 +212,27 @@ function CActsEngine:timer_cb() end if is_process_res then - db_engine:add_task_result_process(task_id, res.data.pid, res.data.imagepath, res.data.err, rules) if acts_engine.yara_ctx.task_ctx[task_id].silent_mode == true then goto continue end - for _,rule in ipairs(rules) do - + for _, rule in ipairs(rules) do if rule.meta == nil then - - __log.infof('process matched (custom rule), proc_id = %d, proc_image = %s, rule_name = %s', res.data.pid, res.data.imagepath, rule.rule_id) - local event_data = { - ['object.process.id'] = res.data.pid, - ['object.process.fullpath'] = res.data.imagepath, + ["object.process.id"] = res.data.pid, + ["object.process.fullpath"] = res.data.imagepath, rule_name = rule.rule_id, - rules = task.custom_rules + rules = task.custom_rules, } self:push_event("yr_process_matched_custom", event_data) else - - __log.infof('process matched, proc_id = %d, proc_image = %s, rule_name = %s, malware_class = %s, rule_severity = %s, rule_type = %s, rule_precision = %d', - res.data.pid, res.data.imagepath, rule.meta.rule_name, rule.meta.malware_class, rule.meta.rule_severity, rule.meta.rule_type, rule.meta.rule_precision) + __log.infof( + "process matched, proc_id = %d, proc_image = %s, rule_name = %s, malware_class = %s, rule_severity = %s, rule_type = %s, rule_precision = %d", + res.data.pid, res.data.imagepath, rule.meta.rule_name, rule.meta.malware_class, + rule.meta.rule_severity, rule.meta.rule_type, rule.meta.rule_precision) if rule.meta.is_silent == true then goto rules_continue @@ -232,76 +241,74 @@ function CActsEngine:timer_cb() local event_suffix = acts_engine.yara_ctx.task_ctx[task_id].event_suffix local event_data = { - [event_suffix .. '.process.id'] = res.data.pid, - [event_suffix .. '.process.fullpath'] = res.data.imagepath, + [event_suffix .. ".process.id"] = res.data.pid, + [event_suffix .. ".process.fullpath"] = res.data.imagepath, rule_name = rule.meta.rule_name, malware_class = rule.meta.malware_class, rule_type = rule.meta.rule_type, - rule_precision = rule.meta.rule_precision + rule_precision = rule.meta.rule_precision, } - if rule.meta.rule_severity == 'low' then - self:push_event("yr_" .. event_suffix .. "_process_matched_low", event_data) - elseif rule.meta.rule_severity == 'medium' then - self:push_event("yr_" .. event_suffix .. "_process_matched_medium", event_data) - elseif rule.meta.rule_severity == 'high' then - self:push_event("yr_" .. event_suffix .. "_process_matched_high", event_data) - else - __log.errorf('invalid rule severity: %s', rule.meta.rule_severity) - end + local action_data = acts_engine.yara_ctx.task_ctx[task_id].action_data + + -- TODO use .cached + local event_name = acts_engine:get_event_name(rule.meta.rule_severity, event_suffix, + scan_target.PROCESS, false) + self:push_event(event_name, event_data, action_data) end ::rules_continue:: end end if is_file_res then - db_engine:add_task_result_file(task_id, res.data.filepath, res.data.sha256, res.data.err, rules) if acts_engine.yara_ctx.task_ctx[task_id].silent_mode == true then goto continue end - for _,rule in ipairs(rules) do - + for _, rule in ipairs(rules) do if rule.meta == nil then - - __log.infof('file matched (custom rule), filepath = %s, sha256_filehash = %s, rule_name = %s', res.data.filepath, res.data.sha256, rule.rule_id) + __log.infof("file matched (custom rule), filepath = %s, sha256_filehash = %s, rule_name = %s", + res.data.filepath, res.data.sha256, rule.rule_id) local event_data = { - ['object.fullpath'] = res.data.filepath, - ['object.sha256_hash'] = res.data.sha256, + ["object.fullpath"] = res.data.filepath, + ["object.sha256_hash"] = res.data.sha256, rule_name = rule.rule_id, - rules = task.custom_rules + rules = task.custom_rules, } self:push_event("yr_file_matched_custom", event_data) else - - __log.infof('file matched, filepath = %s, sha256_filehash = %s, rule_name = %s, malware_class = %s, rule_severity = %s, rule_type = %s, rule_precision = %d', - res.data.filepath, res.data.sha256, rule.meta.rule_name, rule.meta.malware_class, rule.meta.rule_severity, rule.meta.rule_type, rule.meta.rule_precision) + __log.infof( + "file matched, filepath = %s, sha256_filehash = %s, rule_name = %s, malware_class = %s, rule_severity = %s, rule_type = %s, rule_precision = %d", + res.data.filepath, res.data.sha256, rule.meta.rule_name, rule.meta.malware_class, + rule.meta.rule_severity, rule.meta.rule_type, rule.meta.rule_precision) if rule.meta.is_silent == true then goto rules_continue end local event_data = { - ['object.fullpath'] = res.data.filepath, - ['object.sha256_hash'] = res.data.sha256, + ["object.fullpath"] = res.data.filepath, + ["object.sha256_hash"] = res.data.sha256, rule_name = rule.meta.rule_name, malware_class = rule.meta.malware_class, rule_type = rule.meta.rule_type, - rule_precision = rule.meta.rule_precision + rule_precision = rule.meta.rule_precision, } - if rule.meta.rule_severity == 'low' then - self:push_event("yr_file_matched_low", event_data) - elseif rule.meta.rule_severity == 'medium' then - self:push_event("yr_file_matched_medium", event_data) - elseif rule.meta.rule_severity == 'high' then - self:push_event("yr_file_matched_high", event_data) + local action_data = acts_engine.yara_ctx.task_ctx[task_id].action_data + + if rule.meta.rule_severity == "low" then + self:push_event("yr_file_matched_low", event_data, action_data) + elseif rule.meta.rule_severity == "medium" then + self:push_event("yr_file_matched_medium", event_data, action_data) + elseif rule.meta.rule_severity == "high" then + self:push_event("yr_file_matched_high", event_data, action_data) else - __log.errorf('invalid rule severity: %s', rule.meta.rule_severity) + __log.errorf("invalid rule severity: %s", rule.meta.rule_severity) end end ::rules_continue:: @@ -352,22 +359,32 @@ function CActsEngine:update_config_cb() local acts_engine = CActsEngine:cast(self) local default_config = cjson.decode(__config.get_default_config()) - acts_engine.fastscan_proc_items = default_config['fastscan_proc_items' .. acts_engine.config_suffix] - acts_engine.fastscan_fs_items = default_config['fastscan_fs_items' .. acts_engine.config_suffix] + acts_engine.fastscan_proc_items = default_config["fastscan_proc_items" .. acts_engine.config_suffix] + acts_engine.fastscan_fs_items = default_config["fastscan_fs_items" .. acts_engine.config_suffix] local current_config = cjson.decode(__config.get_current_config()) - acts_engine.exclude_rules = current_config['exclude_rules'] - acts_engine.malware_class_items = current_config['malware_class_items'] + acts_engine.exclude_rules = current_config["exclude_rules"] + acts_engine.malware_class_items = current_config["malware_class_items"] -- convert to generic array - local fs_excludes = current_config['exclude_fs_items' .. acts_engine.config_suffix] + local fs_excludes = current_config["exclude_fs_items" .. acts_engine.config_suffix] if fs_excludes ~= nil then acts_engine.exclude_fs_items = {} - for _,item in pairs(fs_excludes) do + for _, item in pairs(fs_excludes) do table.insert(acts_engine.exclude_fs_items, item.filepath) end end + local yara_process_caching_time = current_config["yara_process_caching_time"] + if type(yara_process_caching_time) == "number" then + acts_engine.cache_logic:set_yara_process_caching_time(yara_process_caching_time) + else + __log.error("Invalid type of yara_process_caching_time ~ " .. type(yara_process_caching_time)) + acts_engine.cache_logic:set_yara_process_caching_time(acts_engine.cache_logic:get_default_yara_process_caching_time()) + end + + acts_engine.db_engine:clear_process_cache() + -- actual current configuration contains into next fields -- self.config.actions -- self.config.events @@ -390,30 +407,29 @@ function CActsEngine:recv_data_cb(src, data) local params = payload.data or {} -- remove nodes with cjson.null value - for k,v in pairs(params) do + for k, v in pairs(params) do if v == cjson.null then params[k] = nil end end if not (params.proc_id == nil or tonumber(params.proc_id) ~= nil) or - not (params.proc_image == nil or type(params.proc_image) == "string") or - not (params.filepath == nil or type(params.filepath) == "string") or - not (params.recursive == nil or type(params.recursive) == "boolean") or - not (params.rules == nil or type(params.rules) == "string") or - not (params.task_id == nil or type(params.task_id) == "string") or - not (params.silent_mode == nil or type(params.silent_mode) == "boolean") then + not (params.proc_image == nil or type(params.proc_image) == "string") or + not (params.filepath == nil or type(params.filepath) == "string") or + not (params.recursive == nil or type(params.recursive) == "boolean") or + not (params.rules == nil or type(params.rules) == "string") or + not (params.task_id == nil or type(params.task_id) == "string") or + not (params.silent_mode == nil or type(params.silent_mode) == "boolean") then __log.errorf("invalid data parameters for data request: %s", data) return false end local function process_scan_resp(results, err) - local is_imc = self:get_sender_info(src) if not is_imc then __api.send_data_to(src, cjson.encode( - glue.merge({type = "scan_response", results = results, error = err}, payload) + glue.merge({ type = "scan_response", results = results, error = err }, payload) )) end @@ -425,22 +441,24 @@ function CActsEngine:recv_data_cb(src, data) local is_imc = self:get_sender_info(src) if not is_imc then if payload.type == "db_req_active_rules" then - payload.data = acts_engine.db_engine:db_request(payload.type, params.page, params.pageSize, params.sort, params.filters) + payload.data = acts_engine.db_engine:db_request(payload.type, params.page, params.pageSize, params.sort, + params.filters) payload.type = "db_resp_active_rules" __api.send_data_to(src, cjson.encode(payload)) elseif payload.type == "db_req_tasks" then - payload.data = acts_engine.db_engine:db_request(payload.type, params.page, params.pageSize, params.sort, params.filters) + payload.data = acts_engine.db_engine:db_request(payload.type, params.page, params.pageSize, params.sort, + params.filters) payload.type = "db_resp_tasks" __api.send_data_to(src, cjson.encode(payload)) elseif payload.type == "db_req_task_detects" then - payload.data = acts_engine.db_engine:db_request(payload.type, params.page, params.pageSize, params.sort, params.filters, {task_id = params.task_id}) + payload.data = acts_engine.db_engine:db_request(payload.type, params.page, params.pageSize, params.sort, + params.filters, { task_id = params.task_id }) payload.type = "db_resp_task_detects" __api.send_data_to(src, cjson.encode(payload)) end end if payload.type == "yr_task_stop" then - if params.task_id == nil then return false end @@ -450,26 +468,28 @@ function CActsEngine:recv_data_cb(src, data) __api.send_data_to(src, cjson.encode(payload)) return true - elseif payload.type == "yr_task_scan_proc" then - return process_scan_resp(acts_engine:yara_task_scan_proc(src, params.silent_mode == true, params.user_data, - tonumber(params.proc_id), params.proc_image, "object", params.rules)) + return process_scan_resp(acts_engine:yara_task_scan_proc(src, nil, params.silent_mode == true, params.user_data, + tonumber(params.proc_id), params.proc_image, "object", params.rules)) elseif payload.type == "yr_task_scan_fs" then - if params.filepath == nil then return false end - return process_scan_resp(acts_engine:yara_task_scan_fs(src, params.silent_mode == true, params.user_data, - params.filepath, params.recursive == true, params.rules)) + return process_scan_resp(acts_engine:yara_task_scan_fs(src, nil, params.silent_mode == true, params.user_data, + params.filepath, params.recursive == true, params.rules)) elseif payload.type == "yr_task_fastscan_proc" then - return process_scan_resp(acts_engine:yara_task_fastscan_proc(src, params.silent_mode == true, params.user_data, params.rules)) + return process_scan_resp(acts_engine:yara_task_fastscan_proc(src, nil, params.silent_mode == true, + params.user_data, params.rules)) elseif payload.type == "yr_task_fastscan_fs" then - return process_scan_resp(acts_engine:yara_task_fastscan_fs(src, params.silent_mode == true, params.user_data, params.rules)) + return process_scan_resp(acts_engine:yara_task_fastscan_fs(src, nil, params.silent_mode == true, params + .user_data, params.rules)) elseif payload.type == "yr_task_fullscan_proc" then - return process_scan_resp(acts_engine:yara_task_fullscan_proc(src, params.silent_mode == true, params.user_data, params.rules)) + return process_scan_resp(acts_engine:yara_task_fullscan_proc(src, nil, params.silent_mode == true, + params.user_data, params.rules)) elseif payload.type == "yr_task_fullscan_fs" then - return process_scan_resp(acts_engine:yara_task_fullscan_fs(src, params.silent_mode == true, params.user_data, params.rules)) + return process_scan_resp(acts_engine:yara_task_fullscan_fs(src, nil, params.silent_mode == true, params + .user_data, params.rules)) end return false @@ -497,12 +517,11 @@ function CActsEngine:recv_action_cb(src, data, name) __log.debugf("perform custom logic for action '%s'", name) local function process_resp(results, err) - local is_imc = self:get_sender_info(src) if not is_imc then __api.send_data_to(src, cjson.encode( - glue.merge({type = "action_response", name = name, results = results, error = err}, data) + glue.merge({ type = "action_response", name = name, results = results, error = err }, data) )) end return true @@ -510,15 +529,15 @@ function CActsEngine:recv_action_cb(src, data, name) local acts_engine = CActsEngine:cast(self) local params = data.data or {} - local validate_params = function(...) + local validate_params = function (...) local t = glue.pack(...) for _, v in ipairs(t) do if (v == "object.process.id" and tonumber(params[v]) == nil) or - (v == "subject.process.id" and tonumber(params[v]) == nil) or - (v == "object.process.fullpath" and type(params[v]) ~= "string") or - (v == "subject.process.fullpath" and type(params[v]) ~= "string") or - (v == "object.fullpath" and type(params[v]) ~= "string") or - (v == "recursive" and not (params[v] == nil or type(params[v]) == "boolean")) then + (v == "subject.process.id" and tonumber(params[v]) == nil) or + (v == "object.process.fullpath" and type(params[v]) ~= "string") or + (v == "subject.process.fullpath" and type(params[v]) ~= "string") or + (v == "object.fullpath" and type(params[v]) ~= "string") or + (v == "recursive" and not (params[v] == nil or type(params[v]) == "boolean")) then __log.errorf("invalid data parameter '%s' for action '%s', request: %s", v, name, cjson.encode(params)) return false end @@ -526,53 +545,55 @@ function CActsEngine:recv_action_cb(src, data, name) return true end - if name == "yr_object_scan_proc" then + if name == "yr_object_scan_proc" or name == "yr_object_scan_proc_non_cached" then if not validate_params("object.process.id", "object.process.fullpath") then return false end - return process_resp(acts_engine:yara_scan_proc(tonumber(params["object.process.id"]), params["object.process.fullpath"], "object")) - elseif name == "yr_subject_scan_proc" then + return process_resp(acts_engine:yara_scan_proc(name, data.actions, tonumber(params["object.process.id"]), + params["object.process.fullpath"], "object")) + elseif name == "yr_subject_scan_proc" or name == "yr_subject_scan_proc_non_cached" then if not validate_params("subject.process.id", "subject.process.fullpath") then return false end - return process_resp(acts_engine:yara_scan_proc(tonumber(params["subject.process.id"]), params["subject.process.fullpath"], "subject")) + return process_resp(acts_engine:yara_scan_proc(name, data.actions, tonumber(params["subject.process.id"]), + params["subject.process.fullpath"], "subject")) elseif name == "yr_scan_fs" then if not validate_params("object.fullpath", "recursive") then return false end - return process_resp(acts_engine:yara_scan_fs(params["object.fullpath"], params["recursive"] == true)) + return process_resp(acts_engine:yara_scan_fs(data.actions, params["object.fullpath"], params["recursive"] == true)) elseif name == "yr_object_task_scan_proc" then if not validate_params("object.process.id", "object.process.fullpath") then return false end - return process_resp(acts_engine:yara_task_scan_proc(src, false, nil, - tonumber(params["object.process.id"]), params["object.process.fullpath"], "object")) + return process_resp(acts_engine:yara_task_scan_proc(src, data.actions, false, nil, + tonumber(params["object.process.id"]), params["object.process.fullpath"], "object")) elseif name == "yr_subject_task_scan_proc" then if not validate_params("subject.process.id", "subject.process.fullpath") then return false end - return process_resp(acts_engine:yara_task_scan_proc(src, false, nil, - tonumber(params["subject.process.id"]), params["subject.process.fullpath"], "subject")) + return process_resp(acts_engine:yara_task_scan_proc(src, data.actions, false, nil, + tonumber(params["subject.process.id"]), params["subject.process.fullpath"], "subject")) elseif name == "yr_task_scan_fs" then if not validate_params("object.fullpath", "recursive") then return false end - return process_resp(acts_engine:yara_task_scan_fs(src, false, nil, - params["object.fullpath"], params["recursive"] == true)) + return process_resp(acts_engine:yara_task_scan_fs(src, data.actions, false, nil, + params["object.fullpath"], params["recursive"] == true)) elseif name == "yr_task_fastscan_proc" then - return process_resp(acts_engine:yara_task_fastscan_proc(src, false)) + return process_resp(acts_engine:yara_task_fastscan_proc(src, data.actions, false)) elseif name == "yr_task_fastscan_fs" then - return process_resp(acts_engine:yara_task_fastscan_fs(src, false)) + return process_resp(acts_engine:yara_task_fastscan_fs(src, data.actions, false)) elseif name == "yr_task_fullscan_proc" then - return process_resp(acts_engine:yara_task_fullscan_proc(src, false)) + return process_resp(acts_engine:yara_task_fullscan_proc(src, data.actions, false)) elseif name == "yr_task_fullscan_fs" then - return process_resp(acts_engine:yara_task_fullscan_fs(src, false)) + return process_resp(acts_engine:yara_task_fullscan_fs(src, data.actions, false)) end return false @@ -580,26 +601,30 @@ end -- yara context -local function callback_worker(thread_ctx, filepath_library, ev_callback_ready, q_callback_err, ev_module_stop, q_res) - +local function callback_worker(thread_ctx, filepath_library, ev_callback_ready, q_callback_err, ev_module_stop, + scan_results_queue) local m local function callback_result(task_id, object_type, err, result, _) - q_res:push({task_id = task_id, - data = m:decode_result_raw(result, object_type), - err = err ~= nil and m:decode_error(err) or nil}) + scan_results_queue:push({ + task_id = task_id, + data = m:decode_result_raw(result, object_type), + err = err ~= nil and m:decode_error(err) or nil, + }) end local function callback_complete(task_id, err, _) - q_res:push({task_id = task_id, - time_end = os.date('!%Y-%m-%dT%H:%M:%SZ', os.time()), -- GMT - err = err ~= nil and m:decode_error(err) or nil}) + scan_results_queue:push({ + task_id = task_id, + time_end = os.date("!%Y-%m-%dT%H:%M:%SZ", os.time()), -- GMT + err = err ~= nil and m:decode_error(err) or nil, + }) end local function load(modulename) local errmsg = "" local modulepath = string.gsub(modulename, "%.", "/") - local filenames = {modulepath .. "/init.lua", modulepath .. ".lua"} + local filenames = { modulepath .. "/init.lua", modulepath .. ".lua" } for _, filename in ipairs(filenames) do local filedata = thread_ctx.__files[filename] if filedata then @@ -635,7 +660,6 @@ end -- in: any -- out: boolean function CActsEngine:yara_init(cfg) - __log.debugf("yara_init CActsEngine") self.filepath_system_root = cfg.filepath_system_root @@ -648,52 +672,63 @@ function CActsEngine:yara_init(cfg) self.yara_ctx.user_rules_next_tag = 0 self.yara_ctx.user_rules_tags = {} -- map task_id to rule_tag - self.yara_ctx.q_res = thread.queue(100) + self.yara_ctx.scan_results_queue = thread.queue(100) local ev_callback_ready = thread.event() local q_callback_err = thread.queue(1) self.yara_ctx.th_callbacks = thread.new(callback_worker, { - __tmpdir = __tmpdir, - __files = __files, - __debug = true, - __module_id = tostring(__config.ctx.name) - }, cfg.filepath_library, ev_callback_ready, q_callback_err, self.ev_module_stop, self.yara_ctx.q_res) + __tmpdir = __tmpdir, + __files = __files, + __debug = true, + __module_id = tostring(__config.ctx.name), + }, cfg.filepath_library, ev_callback_ready, q_callback_err, self.ev_module_stop, self.yara_ctx + .scan_results_queue) ev_callback_ready:wait() if q_callback_err:length() ~= 0 then - local _,err = q_callback_err:shift(0) - __log.errorf('unable to initialize yara callback instance: %s', err) + local _, err = q_callback_err:shift(0) + __log.errorf("unable to initialize yara callback instance: %s", err) return false end - __log.debug('yara callback worker: ready') + __log.debug("yara callback worker: ready") self.yara_ctx.m = CYaraModule(cfg.filepath_library) local err = self.yara_ctx.m:initialize() if err ~= nil then - __log.errorf('unable to initialize yara main instance: %s', err) + __log.errorf("unable to initialize yara main instance: %s", err) return false end - err = self.yara_ctx.m:reload_rules({[scan_tag.FILE] = {string = cfg.rules_files}, - [scan_tag.MEM] = {string = cfg.rules_mem}}) + err = self.yara_ctx.m:reload_rules({ + [scan_tag.FILE] = { string = cfg.rules_files }, + [scan_tag.MEM] = { string = cfg.rules_mem }, + }) if err ~= nil then - __log.errorf('unable to load yara rules: %s', err) + __log.errorf("unable to load yara rules: %s", err) return false end return true end --- load new rules with temporary tag -function CActsEngine:prepare_user_rules(task_id, rules) +function CActsEngine:check_yara_engine_initialized() + if not self.yara_ctx or not self.yara_ctx.m then + local err = "yara engine is not initialized" + __log.error(err) + return false, err + end + return true, nil +end +-- load new rules with temporary tag +function CActsEngine:reload_rules(task_id, rules) local tag = self.yara_ctx.user_rules_next_tag - local err = self.yara_ctx.m:reload_rules({[tag] = {string = rules}}) + local err = self.yara_ctx.m:reload_rules({ [tag] = { string = rules } }) if err ~= nil then return nil, err end @@ -704,193 +739,190 @@ function CActsEngine:prepare_user_rules(task_id, rules) return tag end -function CActsEngine:yara_scan_proc(proc_id, proc_image, event_suffix) +function CActsEngine:prepare_user_rules(task_id, rules, tag) + if type(rules) == "nil" then + return tag, nil + end + local res, err = self:reload_rules(task_id, rules) + if res == nil then + return nil, err + end + return res, nil +end + +function CActsEngine:get_event_name(rule_severity, subject_or_object, process_or_file, is_cached_result) + if rule_severity ~= "low" and rule_severity ~= "medium" and rule_severity ~= "high" then + __log.errorf("invalid rule severity: %s", rule_severity) + return nil + end + local event_name = "yr_" + if subject_or_object and string.len(subject_or_object) > 0 then + if subject_or_object ~= "subject" and subject_or_object ~= "object" then + __log.errorf("invalid subject_or_object event attribute: %s", subject_or_object) + return nil + end + event_name = event_name .. subject_or_object .. "_" + end + local cached = "" + if is_cached_result then + cached = "_cached" + end + return event_name .. process_or_file .. "_matched_" .. rule_severity .. cached +end + +function CActsEngine:log_scan_time(scan_start_time, is_cached) + local cache_status = "cached" + if type(is_cached) ~= "boolean" then + return + end + if not is_cached then + cache_status = "non-cached" + end + local elapsed_time = os.clock() - scan_start_time + local elapsed_time_string = "YARA scanning elapsed time: " .. string.format("%.10f", elapsed_time) .. " seconds, " .. cache_status + __log.info(elapsed_time_string) +end + +function CActsEngine:yara_scan_proc(action_name, action_data, proc_id, proc_image, event_suffix) + if type(action_name) ~= "string" then + local error = "CActsEngine:yara_scan_proc ~ non-valid action_name parameter" + __log.error(error) + return nil, error + end + + if not action_data then + local error = "CActsEngine:yara_scan_proc ~ non-valid action_data parameter" + __log.error(error) + return nil, error + end do - local msg = "yara_scan_proc CActsEngine" + local msg = "CActsEngine:yara_scan_proc" if proc_id ~= nil then msg = msg .. string.format(", proc_id: %d", proc_id) end if proc_image ~= nil then msg = msg .. string.format(", proc_image: %s", proc_image) end - __log.debug(msg) end - if self.yara_ctx == nil or self.yara_ctx.m == nil then - local err = 'yara engine is not initialized' - __log.error(err) - return nil, err + local initialized, error = self:check_yara_engine_initialized() + if not initialized then + return nil, error end - local success,res = self.yara_ctx.m:scan_proc(proc_id, proc_image, scan_tag.MEM) - if not success then - __log.error(res) - return nil, res + local scan_start_time = os.clock() + + local function init_scan_results(cached_scan_results) + local scan_results = {} + if not cached_scan_results then + return scan_results + end + scan_results.yara_scan_results = cached_scan_results.scan_results + scan_results.last_scan_time = cached_scan_results.last_scan_time + scan_results.cached = true + return scan_results + end + + local cached_scan_results = nil + if self.cache_logic:is_use_cached_results(action_name) then + cached_scan_results = self.scan_results_cache:get(proc_id, proc_image) + end + + local scan_results = init_scan_results(cached_scan_results) + + if self.cache_logic:is_make_scan( --[[proc_id, proc_image, ]] scan_results) then + self.scan_results_cache:remove(proc_id, proc_image) + local success, yara_scan_results = self.yara_ctx.m:scan_proc(proc_id, proc_image, scan_tag.MEM) + if not success then + __log.error(yara_scan_results) + return nil, yara_scan_results + end + scan_results.yara_scan_results = yara_scan_results + scan_results.last_scan_time = self.yara_utils:get_UTC_time() + scan_results.cached = false + if self.cache_logic:is_make_caching(yara_scan_results) then + self.scan_results_cache:add(proc_id, proc_image, scan_results) + end end local results = {} - for _,item in ipairs(res) do + if not scan_results then + return results + end + if not scan_results.yara_scan_results then + return results + end + + for _, item in ipairs(scan_results.yara_scan_results) do local result = {} result.error = item.error result.proc_image = item.proc_image result.proc_id = item.proc_id result.rules = {} - for _,rule_id in ipairs(item.rules) do - + for _, rule_id in ipairs(item.rules) do local rule_meta = self.db_engine:get_rule_meta(rule_id) - for _,exclude_rule in ipairs(self.exclude_rules) do + for _, exclude_rule in ipairs(self.exclude_rules) do if exclude_rule.rule_name:lower() == rule_meta.rule_name:lower() then - __log.infof('the rule skipped due to policy, proc_id = %d, proc_image = %s, rule_name = %s', - item.proc_id, item.proc_image, rule_meta.rule_name) + __log.infof("the rule skipped due to policy, proc_id = %d, proc_image = %s, rule_name = %s", + item.proc_id, item.proc_image, rule_meta.rule_name) goto rules_continue end end - for _,malware_class_item in ipairs(self.malware_class_items) do + for _, malware_class_item in ipairs(self.malware_class_items) do if malware_class_item.malware_class:lower() == rule_meta.malware_class:lower() then - if malware_class_item.enabled == true then break end - __log.infof('the rule skipped due to policy, proc_id = %d, proc_image = %s, rule_name = %s, malware_class = %s', - item.proc_id, item.proc_image, rule_meta.rule_name, rule_meta.malware_class) + __log.infof( + "the rule skipped due to policy, proc_id = %d, proc_image = %s, rule_name = %s, malware_class = %s", + item.proc_id, item.proc_image, rule_meta.rule_name, rule_meta.malware_class) goto rules_continue end end table.insert(result.rules, rule_meta) - __log.infof('process matched, proc_id = %d, proc_image = %s, rule_name = %s, malware_class = %s, rule_severity = %s, rule_type = %s, rule_precision = %d', - item.proc_id, item.proc_image, rule_meta.rule_name, rule_meta.malware_class, rule_meta.rule_severity, rule_meta.rule_type, rule_meta.rule_precision) + __log.infof( + "process matched, proc_id = %d, proc_image = %s, rule_name = %s, malware_class = %s, rule_severity = %s, rule_type = %s, rule_precision = %d", + item.proc_id, item.proc_image, rule_meta.rule_name, rule_meta.malware_class, rule_meta.rule_severity, + rule_meta.rule_type, rule_meta.rule_precision) if rule_meta.is_silent == true then goto rules_continue end local event_data = { - [event_suffix ..'.process.id'] = item.proc_id, - [event_suffix ..'.process.fullpath'] = item.proc_image, + [event_suffix .. ".process.id"] = item.proc_id, + [event_suffix .. ".process.fullpath"] = item.proc_image, rule_name = rule_meta.rule_name, malware_class = rule_meta.malware_class, rule_type = rule_meta.rule_type, - rule_precision = rule_meta.rule_precision + rule_precision = rule_meta.rule_precision, } - - if rule_meta.rule_severity == 'low' then - self:push_event("yr_" .. event_suffix .. "_process_matched_low", event_data) - elseif rule_meta.rule_severity == 'medium' then - self:push_event("yr_" .. event_suffix .. "_process_matched_medium", event_data) - elseif rule_meta.rule_severity == 'high' then - self:push_event("yr_" .. event_suffix .. "_process_matched_high", event_data) - else - __log.errorf('invalid rule severity: %s', rule_meta.rule_severity) - end + local event_name = self:get_event_name(rule_meta.rule_severity, event_suffix, scan_target.PROCESS, + scan_results.cached) + self:push_event(event_name, event_data, action_data) ::rules_continue:: end table.insert(results, result) end - -- return #matches ~= 0 and matches or nil - return results -end - -function CActsEngine:yara_scan_fs(filepath, recursive) - - __log.debugf("yara_scan_fs CActsEngine, filepath: %s", filepath) - - if self.yara_ctx == nil or self.yara_ctx.m == nil then - local err = 'yara engine is not initialized' - __log.error(err) - return nil, err - end - - local success, res = self.yara_ctx.m:scan_fs(filepath, recursive, scan_tag.FILE) - if not success then - __log.error(res) - return nil, res - end - - local results = {} - - for _,item in ipairs(res) do - - local result = {} - result.error = item.error - result.filepath = item.filepath - result.sha256_filehash = item.sha256_filehash - result.rules = {} - - for _,rule_id in ipairs(item.rules) do - - local rule_meta = self.db_engine:get_rule_meta(rule_id) - - for _,exclude_rule in ipairs(self.exclude_rules) do - if exclude_rule.rule_name:lower() == rule_meta.rule_name:lower() then - __log.infof('the rule skipped due to policy, filepath = %s, sha256_filehash = %s, rule_name = %s', - item.filepath, item.sha256_filehash, rule_meta.rule_name) - goto rules_continue - end - end - - for _,malware_class_item in ipairs(self.malware_class_items) do - if malware_class_item.malware_class:lower() == rule_meta.malware_class:lower() then - - if malware_class_item.enabled == true then - break - end - - __log.infof('the rule skipped due to policy, filepath = %s, sha256_filehash = %s, rule_name = %s, malware_class = %s', - item.filepath, item.sha256_filehash, rule_meta.rule_name, rule_meta.malware_class) - goto rules_continue - end - end - - table.insert(result.rules, rule_meta) - - __log.infof('file matched, filepath = %s, sha256_filehash = %s, rule_name = %s, malware_class = %s, rule_severity = %s, rule_type = %s, rule_precision = %d', - item.filepath, item.sha256_filehash, rule_meta.rule_name, rule_meta.malware_class, rule_meta.rule_severity, rule_meta.rule_type, rule_meta.rule_precision) - - if rule_meta.is_silent == true then - goto rules_continue - end - - local event_data = { - ['object.fullpath'] = item.filepath, - ['object.sha256_hash'] = item.sha256_filehash, - rule_name = rule_meta.rule_name, - malware_class = rule_meta.malware_class, - rule_type = rule_meta.rule_type, - rule_precision = rule_meta.rule_precision - } - - if rule_meta.rule_severity == 'low' then - self:push_event("yr_file_matched_low", event_data) - elseif rule_meta.rule_severity == 'medium' then - self:push_event("yr_file_matched_medium", event_data) - elseif rule_meta.rule_severity == 'high' then - self:push_event("yr_file_matched_high", event_data) - else - __log.errorf('invalid rule severity: %s', rule_meta.rule_severity) - end - ::rules_continue:: - end - - table.insert(results, result) - end + self:log_scan_time(scan_start_time, scan_results.cached) return results end -function CActsEngine:yara_task_scan_proc(src, silent_mode, user_data, proc_id, proc_image, event_suffix, rules) - +function CActsEngine:yara_task_scan_proc(src, action_data, silent_mode, user_data, proc_id, proc_image, event_suffix, + rules) do local msg = "yara_task_scan_proc CActsEngine" if proc_id ~= nil then @@ -903,23 +935,15 @@ function CActsEngine:yara_task_scan_proc(src, silent_mode, user_data, proc_id, p __log.debug(msg) end - if self.yara_ctx == nil or self.yara_ctx.m == nil then - local err = 'yara engine is not initialized' - __log.error(err) - return nil, err + local initialized, error = self:check_yara_engine_initialized() + if not initialized then + return nil, error end local task_id = uuid.new() - local tag = scan_tag.MEM - - if type(rules) ~= "nil" then - - local res, err = self:prepare_user_rules(task_id, rules) - if res == nil then - return nil, err - end - - tag = res + local tag, prepare_error = self:prepare_user_rules(task_id, rules, scan_tag.MEM) + if tag == nil then + return nil, prepare_error end local success, res = self.yara_ctx.m:task_scan_proc(proc_id, proc_image, tag, nil) @@ -932,6 +956,7 @@ function CActsEngine:yara_task_scan_proc(src, silent_mode, user_data, proc_id, p self.yara_ctx.task_ctx[task_id] = {} self.yara_ctx.task_ctx[task_id].src = src + self.yara_ctx.task_ctx[task_id].action_data = action_data self.yara_ctx.task_ctx[task_id].user_data = user_data self.yara_ctx.task_ctx[task_id].workers = 1 @@ -942,80 +967,28 @@ function CActsEngine:yara_task_scan_proc(src, silent_mode, user_data, proc_id, p end self.yara_ctx.task_ids[res] = task_id - return {task_id = task_id} + return { task_id = task_id } end -function CActsEngine:yara_task_scan_fs(src, silent_mode, user_data, filepath, recursive, rules) - - __log.debugf("yara_task_scan_fs CActsEngine, filepath: %s", filepath) - - if self.yara_ctx == nil or self.yara_ctx.m == nil then - local err = 'yara engine is not initialized' - __log.error(err) - return nil, err - end - - local task_id = uuid.new() - local tag = scan_tag.FILE - - if type(rules) ~= "nil" then - - local res, err = self:prepare_user_rules(task_id, rules) - if res == nil then - return nil, err - end - - tag = res - end - - local success, res = self.yara_ctx.m:task_scan_fs(filepath, recursive, tag, self.exclude_fs_items) - if not success then - __log.error(res) - return nil, res - end - - self.db_engine:add_task_fs(task_id, filepath, recursive, scan_type.CUSTOM_FS, rules) - - self.yara_ctx.task_ctx[task_id] = {} - self.yara_ctx.task_ctx[task_id].src = src - self.yara_ctx.task_ctx[task_id].user_data = user_data - self.yara_ctx.task_ctx[task_id].workers = 1 - - if silent_mode then - self.yara_ctx.task_ctx[task_id].silent_mode = true - end - - self.yara_ctx.task_ids[res] = task_id - return {task_id = task_id} -end - -function CActsEngine:yara_task_fastscan_proc(src, silent_mode, user_data, rules) - +function CActsEngine:yara_task_fastscan_proc(src, action_data, silent_mode, user_data, rules) __log.debug("yara_task_fastscan_proc CActsEngine") - if self.yara_ctx == nil or self.yara_ctx.m == nil then - local err = 'yara engine is not initialized' - __log.error(err) - return nil, err + local initialized, error = self:check_yara_engine_initialized() + if not initialized then + return nil, error end local task_id = uuid.new() - local tag = scan_tag.MEM - - if type(rules) ~= "nil" then - - local res, err = self:prepare_user_rules(task_id, rules) - if res == nil then - return nil, err - end - - tag = res + local tag, prepare_error = self:prepare_user_rules(task_id, rules, scan_tag.MEM) + if tag == nil then + return nil, prepare_error end self.db_engine:add_task_proc(task_id, nil, nil, scan_type.FAST_PROC, rules) self.yara_ctx.task_ctx[task_id] = {} self.yara_ctx.task_ctx[task_id].src = src + self.yara_ctx.task_ctx[task_id].action_data = action_data self.yara_ctx.task_ctx[task_id].user_data = user_data self.yara_ctx.task_ctx[task_id].workers = 0 @@ -1025,8 +998,7 @@ function CActsEngine:yara_task_fastscan_proc(src, silent_mode, user_data, rules) self.yara_ctx.task_ctx[task_id].event_suffix = "object" end - for _,item in ipairs(self.fastscan_proc_items) do - + for _, item in ipairs(self.fastscan_proc_items) do local success, res = self.yara_ctx.m:task_scan_proc(nil, item.proc_image, tag, nil) if not success then __log.error(res) @@ -1038,86 +1010,28 @@ function CActsEngine:yara_task_fastscan_proc(src, silent_mode, user_data, rules) ::continue:: end - return {task_id = task_id} + return { task_id = task_id } end -function CActsEngine:yara_task_fastscan_fs(src, silent_mode, user_data, rules) - - __log.debug("yara_task_fastscan_fs CActsEngine") - - if self.yara_ctx == nil or self.yara_ctx.m == nil then - local err = 'yara engine is not initialized' - __log.error(err) - return nil, err - end - - local task_id = uuid.new() - local tag = scan_tag.FILE - - if type(rules) ~= "nil" then - - local res, err = self:prepare_user_rules(task_id, rules) - if res == nil then - return nil, err - end - - tag = res - end - - self.db_engine:add_task_fs(task_id, nil, nil, scan_type.FAST_FS, rules) - - self.yara_ctx.task_ctx[task_id] = {} - self.yara_ctx.task_ctx[task_id].src = src - self.yara_ctx.task_ctx[task_id].user_data = user_data - self.yara_ctx.task_ctx[task_id].workers = 0 - - if silent_mode then - self.yara_ctx.task_ctx[task_id].silent_mode = true - end - - for _,item in ipairs(self.fastscan_fs_items) do - - local success, res = self.yara_ctx.m:task_scan_fs(item.filepath, item.recursive, tag, self.exclude_fs_items) - if not success then - __log.error(res) - goto continue - end - - self.yara_ctx.task_ids[res] = task_id - self.yara_ctx.task_ctx[task_id].workers = self.yara_ctx.task_ctx[task_id].workers + 1 - ::continue:: - end - - return {task_id = task_id} -end - -function CActsEngine:yara_task_fullscan_proc(src, silent_mode, user_data, rules) - +function CActsEngine:yara_task_fullscan_proc(src, action_data, silent_mode, user_data, rules) __log.debug("yara_task_fullscan_proc CActsEngine") - if self.yara_ctx == nil or self.yara_ctx.m == nil then - local err = 'yara engine is not initialized' - __log.error(err) - return nil, err + local initialized, error = self:check_yara_engine_initialized() + if not initialized then + return nil, error end local task_id = uuid.new() - local tag = scan_tag.MEM - - if type(rules) ~= "nil" then - - local res, err = self:prepare_user_rules(task_id, rules) - if res == nil then - return nil, err - end - - tag = res + local tag, prepare_error = self:prepare_user_rules(task_id, rules, scan_tag.MEM) + if tag == nil then + return nil, prepare_error end self.db_engine:add_task_proc(task_id, nil, nil, scan_type.FULL_PROC, rules) self.yara_ctx.task_ctx[task_id] = {} self.yara_ctx.task_ctx[task_id].src = src + self.yara_ctx.task_ctx[task_id].action_data = action_data self.yara_ctx.task_ctx[task_id].user_data = user_data self.yara_ctx.task_ctx[task_id].workers = 1 @@ -1134,36 +1048,183 @@ function CActsEngine:yara_task_fullscan_proc(src, silent_mode, user_data, rules) end self.yara_ctx.task_ids[res] = task_id - return {task_id = task_id} + return { task_id = task_id } end -function CActsEngine:yara_task_fullscan_fs(src, silent_mode, user_data, rules) +function CActsEngine:yara_scan_fs(action_data, filepath, recursive) + __log.debugf("yara_scan_fs CActsEngine, filepath: %s", filepath) - __log.debug("yara_task_fullscan_fs CActsEngine") + local initialized, error = self:check_yara_engine_initialized() + if not initialized then + return nil, error + end - if self.yara_ctx == nil or self.yara_ctx.m == nil then - local err = 'yara engine is not initialized' - __log.error(err) - return nil, err + local success, res = self.yara_ctx.m:scan_fs(filepath, recursive, scan_tag.FILE) + if not success then + __log.error(res) + return nil, res + end + + local results = {} + + for _, item in ipairs(res) do + local result = {} + result.error = item.error + result.filepath = item.filepath + result.sha256_filehash = item.sha256_filehash + result.rules = {} + + for _, rule_id in ipairs(item.rules) do + local rule_meta = self.db_engine:get_rule_meta(rule_id) + + for _, exclude_rule in ipairs(self.exclude_rules) do + if exclude_rule.rule_name:lower() == rule_meta.rule_name:lower() then + __log.infof("the rule skipped due to policy, filepath = %s, sha256_filehash = %s, rule_name = %s", + item.filepath, item.sha256_filehash, rule_meta.rule_name) + goto rules_continue + end + end + + for _, malware_class_item in ipairs(self.malware_class_items) do + if malware_class_item.malware_class:lower() == rule_meta.malware_class:lower() then + if malware_class_item.enabled == true then + break + end + + __log.infof( + "the rule skipped due to policy, filepath = %s, sha256_filehash = %s, rule_name = %s, malware_class = %s", + item.filepath, item.sha256_filehash, rule_meta.rule_name, rule_meta.malware_class) + goto rules_continue + end + end + + table.insert(result.rules, rule_meta) + + __log.infof( + "file matched, filepath = %s, sha256_filehash = %s, rule_name = %s, malware_class = %s, rule_severity = %s, rule_type = %s, rule_precision = %d", + item.filepath, item.sha256_filehash, rule_meta.rule_name, rule_meta.malware_class, + rule_meta.rule_severity, rule_meta.rule_type, rule_meta.rule_precision) + + if rule_meta.is_silent == true then + goto rules_continue + end + + local event_data = { + ["object.fullpath"] = item.filepath, + ["object.sha256_hash"] = item.sha256_filehash, + rule_name = rule_meta.rule_name, + malware_class = rule_meta.malware_class, + rule_type = rule_meta.rule_type, + rule_precision = rule_meta.rule_precision, + } + -- TODO use scan_results.cached + local event_name = self:get_event_name(rule_meta.rule_severity, "", scan_target.FILE, false) + self:push_event(event_name, event_data, action_data) + ::rules_continue:: + end + + table.insert(results, result) + end + + return results +end + +function CActsEngine:yara_task_scan_fs(src, action_data, silent_mode, user_data, filepath, recursive, rules) + __log.debugf("yara_task_scan_fs CActsEngine, filepath: %s", filepath) + + local initialized, error = self:check_yara_engine_initialized() + if not initialized then + return nil, error end local task_id = uuid.new() - local tag = scan_tag.FILE + local tag, prepare_error = self:prepare_user_rules(task_id, rules, scan_tag.FILE) + if tag == nil then + return nil, prepare_error + end - if type(rules) ~= "nil" then + local success, res = self.yara_ctx.m:task_scan_fs(filepath, recursive, tag, self.exclude_fs_items) + if not success then + __log.error(res) + return nil, res + end - local res, err = self:prepare_user_rules(task_id, rules) - if res == nil then - return nil, err + self.db_engine:add_task_fs(task_id, filepath, recursive, scan_type.CUSTOM_FS, rules) + + self.yara_ctx.task_ctx[task_id] = {} + self.yara_ctx.task_ctx[task_id].src = src + self.yara_ctx.task_ctx[task_id].action_data = action_data + self.yara_ctx.task_ctx[task_id].user_data = user_data + self.yara_ctx.task_ctx[task_id].workers = 1 + + if silent_mode then + self.yara_ctx.task_ctx[task_id].silent_mode = true + end + + self.yara_ctx.task_ids[res] = task_id + return { task_id = task_id } +end + +function CActsEngine:yara_task_fastscan_fs(src, action_data, silent_mode, user_data, rules) + __log.debug("yara_task_fastscan_fs CActsEngine") + + local initialized, error = self:check_yara_engine_initialized() + if not initialized then + return nil, error + end + + local task_id = uuid.new() + local tag, prepare_error = self:prepare_user_rules(task_id, rules, scan_tag.FILE) + if tag == nil then + return nil, prepare_error + end + + self.db_engine:add_task_fs(task_id, nil, nil, scan_type.FAST_FS, rules) + + self.yara_ctx.task_ctx[task_id] = {} + self.yara_ctx.task_ctx[task_id].src = src + self.yara_ctx.task_ctx[task_id].action_data = action_data + self.yara_ctx.task_ctx[task_id].user_data = user_data + self.yara_ctx.task_ctx[task_id].workers = 0 + + if silent_mode then + self.yara_ctx.task_ctx[task_id].silent_mode = true + end + + for _, item in ipairs(self.fastscan_fs_items) do + local success, res = self.yara_ctx.m:task_scan_fs(item.filepath, item.recursive, tag, self.exclude_fs_items) + if not success then + __log.error(res) + goto continue end - tag = res + self.yara_ctx.task_ids[res] = task_id + self.yara_ctx.task_ctx[task_id].workers = self.yara_ctx.task_ctx[task_id].workers + 1 + ::continue:: + end + + return { task_id = task_id } +end + +function CActsEngine:yara_task_fullscan_fs(src, action_data, silent_mode, user_data, rules) + __log.debug("yara_task_fullscan_fs CActsEngine") + + local initialized, error = self:check_yara_engine_initialized() + if not initialized then + return nil, error + end + + local task_id = uuid.new() + local tag, prepare_error = self:prepare_user_rules(task_id, rules, scan_tag.FILE) + if tag == nil then + return nil, prepare_error end self.db_engine:add_task_fs(task_id, nil, nil, scan_type.FULL_FS, rules) self.yara_ctx.task_ctx[task_id] = {} self.yara_ctx.task_ctx[task_id].src = src + self.yara_ctx.task_ctx[task_id].action_data = action_data self.yara_ctx.task_ctx[task_id].user_data = user_data self.yara_ctx.task_ctx[task_id].workers = 1 @@ -1178,23 +1239,20 @@ function CActsEngine:yara_task_fullscan_fs(src, silent_mode, user_data, rules) end self.yara_ctx.task_ids[res] = task_id - return {task_id = task_id} + return { task_id = task_id } end - function CActsEngine:yara_task_stop(task_id) - __log.debug("yara_task_stop CActsEngine") - if self.yara_ctx == nil or self.yara_ctx.m == nil then - local err = 'yara engine is not initialized' - __log.error(err) - return nil, err + local initialized, error = self:check_yara_engine_initialized() + if not initialized then + return nil, error end local ids = {} - for id,task_id_ in pairs(self.yara_ctx.task_ids) do + for id, task_id_ in pairs(self.yara_ctx.task_ids) do if task_id == task_id_ then table.insert(ids, id) end @@ -1202,7 +1260,7 @@ function CActsEngine:yara_task_stop(task_id) local stopped = false - for _,id in ipairs(ids) do + for _, id in ipairs(ids) do local success = self.yara_ctx.m:task_scan_stop(id) if success then stopped = true @@ -1210,23 +1268,46 @@ function CActsEngine:yara_task_stop(task_id) end if stopped then - self.db_engine:add_task_result(task_id, task_status.CANCELED, os.date('!%Y-%m-%dT%H:%M:%SZ', os.time()), nil) + self.db_engine:add_task_result(task_id, task_status.CANCELED, os.date("!%Y-%m-%dT%H:%M:%SZ", os.time()), nil) end - return {task_id = task_id, stopped = stopped} + return { task_id = task_id, stopped = stopped } end function CActsEngine:yara_update_interrupted_tasks() - __log.debug("yara_update_interrupted_tasks CActsEngine") - local res = self.db_engine:db_request("db_req_tasks", nil, nil, nil, {[1] = {field = "status", value = task_status.IN_PROGRESS}}) + local res = self.db_engine:db_request("db_req_tasks", nil, nil, nil, + { [1] = { field = "status", value = task_status.IN_PROGRESS } }) if type(res) ~= "table" or type(res.tasks) ~= "table" then __log.errorf("failed to get interrupted tasks with result: '%s'", tostring(cjson.encode(res))) return end - for _,task in ipairs(res.tasks) do - __log.infof('task has not been completed, set interrupted status, task_id = %s', task.task_id) + for _, task in ipairs(res.tasks) do + __log.infof("task has not been completed, set interrupted status, task_id = %s", task.task_id) self.db_engine:add_task_result(task.task_id, task_status.INTERRUPTED) end end + +function CActsEngine:cleanup_scan_results_cache() + self.scan_results_cache:cleanup() + self.scan_results_cache = nil + -- collectgarbage("collect") +end + +function CActsEngine:cleanup_cache_logic() + self.cache_logic:cleanup() + self.cache_logic = nil + -- collectgarbage("collect") +end + +function CActsEngine:cleanup_yara_utils() + self.yara_utils = nil + -- collectgarbage("collect") +end + +function CActsEngine:cleanup() + self:cleanup_scan_results_cache() + self:cleanup_cache_logic() + self:cleanup_yara_utils() +end diff --git a/yara_scanner/1.0.0/cmodule/engines/db_engine.lua b/yara_scanner/1.0.0/cmodule/engines/db_engine.lua index bd2bf26..18f55c6 100644 --- a/yara_scanner/1.0.0/cmodule/engines/db_engine.lua +++ b/yara_scanner/1.0.0/cmodule/engines/db_engine.lua @@ -5,7 +5,6 @@ local glue = require("glue") CDatabaseEngine = newclass("CDatabaseEngine") function CDatabaseEngine:init(db) - self.db = assert(db) self.queries = {} @@ -100,8 +99,6 @@ function CDatabaseEngine:init(db) ); ]] - -- TODO: add caches with sha256 - self.queries.add_rule = [[ REPLACE INTO rules VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); ]] @@ -166,6 +163,65 @@ function CDatabaseEngine:init(db) WHERE rule_id == ? ]] + -- cache queries + + -- file cache + self.queries.create_table_file_cache = [[ + CREATE TABLE IF NOT EXISTS file_cache ( + filepath TEXT, + sha256 TEXT, + scan_results TEXT + ); + ]] + + self.queries.create_index_for_file_cache = [[ + CREATE INDEX IF NOT EXISTS file_cache_index ON file_cache (filepath, sha256); + ]] + + self.queries.add_to_file_cache = [[ + INSERT INTO file_cache VALUES(?, ?, ?, ?); + ]] + + -- process cache + self.queries.create_table_process_cache = [[ + CREATE TABLE IF NOT EXISTS process_cache ( + process_id INTEGER, + process_image TEXT, + last_scan_time DOUBLE, + scan_results TEXT + ); + ]] + + self.queries.create_index_for_process_cache = [[ + CREATE INDEX IF NOT EXISTS process_cache_index ON process_cache (process_id, process_image); + ]] + + self.queries.add_to_process_cache = [[ + INSERT INTO process_cache VALUES(?, ?, ?, ?); + ]] + + self.queries.get_from_process_cache = [[ + SELECT scan_results, last_scan_time + FROM process_cache + WHERE process_id == ? AND process_image == ? + ]] + + -- test query + self.queries.get_all_from_process_cache = [[ + SELECT * + FROM process_cache + ]] + + self.queries.delete_from_process_cache = [[ + DELETE + FROM process_cache + WHERE process_id == ? AND process_image == ? + ]] + + self.queries.delete_all_from_process_cache = [[ + DELETE + FROM process_cache + ]] end function CDatabaseEngine:free() @@ -173,7 +229,7 @@ function CDatabaseEngine:free() end function CDatabaseEngine:create_tables() - __log.debug("create_tables CDatabaseEngine") + __log.debug("CDatabaseEngine:create_tables") self:exec_query(self.queries.create_table_rules) self:exec_query(self.queries.create_table_tasks) @@ -183,80 +239,80 @@ function CDatabaseEngine:create_tables() self:exec_query(self.queries.create_table_task_result_process) self:exec_query(self.queries.create_table_task_result_file) self:exec_query(self.queries.create_table_task_detects) - + self:exec_query(self.queries.create_table_file_cache) + self:exec_query(self.queries.create_index_for_file_cache) + self:exec_query(self.queries.create_table_process_cache) + self:exec_query(self.queries.create_index_for_process_cache) end -- from json function CDatabaseEngine:add_rule(rule_id, rule_meta) - __log.debug("add_rule CDatabaseEngine") assert(type(rule_id) == "string", "rule_id must be a value of string type") assert(type(rule_meta) == "table", "rule_meta must be a value of table type") - assert(type(rule_meta['rule_name']) == "string", "rule_name must be a value of string type") - assert(type(rule_meta['malware_class']) == "string", "malware_class must be a value of string type") - assert(type(rule_meta['malware_family']) == "string", "malware_family must be a value of string type") - assert(type(rule_meta['rule_severity']) == "string", "rule_severity must be a value of string type") - assert(type(rule_meta['rule_type']) == "string", "rule_type must be a value of string type") - assert(type(rule_meta['is_silent']) == "boolean", "is_silent must be a value of string type") - assert(type(rule_meta['description']) == "string", "description must be a value of string type") - assert(type(rule_meta['date']) == "string", "date must be a value of string type") - assert(type(rule_meta['hash']) == "table", "hash must be a value of table type") - assert(type(rule_meta['reference']) == "table", "reference must be a value of table type") - assert(type(rule_meta['rule_precision']) == "number", "rule_precision must be a value of number type") + assert(type(rule_meta["rule_name"]) == "string", "rule_name must be a value of string type") + assert(type(rule_meta["malware_class"]) == "string", "malware_class must be a value of string type") + assert(type(rule_meta["malware_family"]) == "string", "malware_family must be a value of string type") + assert(type(rule_meta["rule_severity"]) == "string", "rule_severity must be a value of string type") + assert(type(rule_meta["rule_type"]) == "string", "rule_type must be a value of string type") + assert(type(rule_meta["is_silent"]) == "boolean", "is_silent must be a value of string type") + assert(type(rule_meta["description"]) == "string", "description must be a value of string type") + assert(type(rule_meta["date"]) == "string", "date must be a value of string type") + assert(type(rule_meta["hash"]) == "table", "hash must be a value of table type") + assert(type(rule_meta["reference"]) == "table", "reference must be a value of table type") + assert(type(rule_meta["rule_precision"]) == "number", "rule_precision must be a value of number type") return self:exec_query(self.queries.add_rule, - 1, - rule_id, - rule_meta['rule_name'], - rule_meta['malware_class'], - rule_meta['malware_family'], - rule_meta['rule_severity'], - rule_meta['rule_type'], - rule_meta['is_silent'], - rule_meta['description'], - rule_meta['date'], - table.concat(rule_meta['hash'], "|"), - table.concat(rule_meta['reference'], "|"), - rule_meta['rule_precision']) + 1, + rule_id, + rule_meta["rule_name"], + rule_meta["malware_class"], + rule_meta["malware_family"], + rule_meta["rule_severity"], + rule_meta["rule_type"], + rule_meta["is_silent"], + rule_meta["description"], + rule_meta["date"], + table.concat(rule_meta["hash"], "|"), + table.concat(rule_meta["reference"], "|"), + rule_meta["rule_precision"]) end function CDatabaseEngine:get_task(task_id) - __log.debug("get_task CDatabaseEngine") - assert(type(task_id) == "string","task_id must be a value of string type") + assert(type(task_id) == "string", "task_id must be a value of string type") local query = self:select_query_unpack_single(self.queries.get_task, task_id) if not query then - __log.debug("get_task CDatabaseEngine, empty query results") + __log.debug("CDatabaseEngine:get_task, empty query results") return nil end local res = { - task_id = query['task_id'], - task_type = query['task_type'], - objects_type = query['objects_type'], - custom_rules = query['custom_rules'] + task_id = query["task_id"], + task_type = query["task_type"], + objects_type = query["objects_type"], + custom_rules = query["custom_rules"], } return res end function CDatabaseEngine:update_rules(active_rules) - __log.debug("update_rules CDatabaseEngine") + __log.debug("CDatabaseEngine:update_rules") assert(type(active_rules) == "table", "active_rules must be a value of table type") local current_rules = self:select_query("SELECT rule_id,active FROM rules") if not current_rules then - __log.debug("get_task CDatabaseEngine, empty query results") + __log.debug("CDatabaseEngine:get_task, empty query results") return nil end local active_rules_in_db = {} - for _,rule in ipairs(current_rules) do - + for _, rule in ipairs(current_rules) do if active_rules[rule.rule_id] ~= nil then active_rules_in_db[rule.rule_id] = true; if rule.active == 0 then - __log.infof("mark rule as active, rule_id = %s", rule.rule_id) + __log.debugf("mark rule as active, rule_id = %s", rule.rule_id) self:exec_query("UPDATE rules SET active = 1 WHERE rule_id = ?", rule.rule_id) end else @@ -269,28 +325,27 @@ function CDatabaseEngine:update_rules(active_rules) AND B.rule_id = ? ]], rule.rule_id) - if not detects or detects['COUNT(*)'] == 0 then - __log.infof("delete rule, rule_id = %s", rule.rule_id) + if not detects or detects["COUNT(*)"] == 0 then + __log.debugf("delete rule, rule_id = %s", rule.rule_id) self:exec_query("DELETE FROM rules WHERE rule_id = ?", rule.rule_id) else if rule.active == 1 then - __log.infof("mark rule as inactive, rule_id = %s", rule.rule_id) + __log.debugf("mark rule as inactive, rule_id = %s", rule.rule_id) self:exec_query("UPDATE rules SET active = 0 WHERE rule_id = ?", rule.rule_id) end end end end - for rule_id,rule_meta in pairs(active_rules) do + for rule_id, rule_meta in pairs(active_rules) do if active_rules_in_db[rule_id] == nil then - __log.infof("add new rule, rule_id = %s", rule_id) self:add_rule(rule_id, rule_meta) end end end function CDatabaseEngine:add_task_proc(task_id, proc_id, proc_image, task_type, rules) - __log.debug("add_task_proc CDatabaseEngine") + __log.debug("CDatabaseEngine:add_task_proc") assert(type(task_id) == "string", "task_id must be a value of string type") assert(type(proc_id) == "nil" or type(proc_id) == "number", "proc_id must be a value of nil or number type") assert(type(proc_image) == "nil" or type(proc_image) == "string", "proc_image must be a value of nil or string type") @@ -308,16 +363,16 @@ function CDatabaseEngine:add_task_proc(task_id, proc_id, proc_image, task_type, end return self:exec_query(self.queries.add_task_status, - task_id, - 0, - os.date('!%Y-%m-%dT%H:%M:%SZ', os.time()), -- GMT - nil, - 0, - nil) + task_id, + 0, + os.date("!%Y-%m-%dT%H:%M:%SZ", os.time()), -- GMT + nil, + 0, + nil) end function CDatabaseEngine:add_task_fs(task_id, filepath, recursive, task_type, rules) - __log.debug("add_task_fs CDatabaseEngine") + __log.debug("CDatabaseEngine:add_task_fs") assert(type(task_id) == "string", "task_id must be a value of string type") assert(type(filepath) == "nil" or type(filepath) == "string", "filepath must be a value of nil or string type") assert(type(recursive) == "nil" or type(recursive) == "boolean", "recursive must be a value of nil or boolean type") @@ -335,16 +390,16 @@ function CDatabaseEngine:add_task_fs(task_id, filepath, recursive, task_type, ru end return self:exec_query(self.queries.add_task_status, - task_id, - 0, - os.date('!%Y-%m-%dT%H:%M:%SZ', os.time()), -- GMT - nil, - 0, - nil) + task_id, + 0, + os.date("!%Y-%m-%dT%H:%M:%SZ", os.time()), -- GMT + nil, + 0, + nil) end function CDatabaseEngine:add_task_result(task_id, status, time_end, err) - __log.debug("add_task_result CDatabaseEngine") + __log.debug("CDatabaseEngine:add_task_result") assert(type(task_id) == "string", "task_id must be a value of string type") assert(type(status) == "number", "status must be a value of number type") assert(type(time_end) == "nil" or type(time_end) == "string", "time_end must be a value of nil or string type") @@ -355,7 +410,7 @@ function CDatabaseEngine:add_task_result(task_id, status, time_end, err) return nil end - if query['status'] ~= 0 then -- specific status is set + if query["status"] ~= 0 then -- specific status is set return nil end @@ -363,7 +418,7 @@ function CDatabaseEngine:add_task_result(task_id, status, time_end, err) end function CDatabaseEngine:add_task_result_process(task_id, proc_id, proc_image, err, detects) - __log.debug("add_task_result_process CDatabaseEngine") + __log.debug("CDatabaseEngine:add_task_result_process") assert(type(task_id) == "string", "task_id must be a value of string type") assert(type(proc_id) == "nil" or type(proc_id) == "number", "proc_id must be a value of nil or string type") assert(type(proc_image) == "nil" or type(proc_image) == "string", "proc_image must be a value of nil or string type") @@ -375,7 +430,7 @@ function CDatabaseEngine:add_task_result_process(task_id, proc_id, proc_image, e return nil end - local object = query['objects'] + local object = query["objects"] local res = self:exec_query(self.queries.update_task_status_add_object, object + 1, task_id) if not res then @@ -388,8 +443,7 @@ function CDatabaseEngine:add_task_result_process(task_id, proc_id, proc_image, e end if detects ~= nil then - for _,rule in ipairs(detects) do - + for _, rule in ipairs(detects) do res = self:exec_query(self.queries.add_task_detect, task_id, object, rule.rule_id) if not res then @@ -402,7 +456,7 @@ function CDatabaseEngine:add_task_result_process(task_id, proc_id, proc_image, e end function CDatabaseEngine:add_task_result_file(task_id, filepath, sha256, err, detects) - __log.debug("add_task_result_file CDatabaseEngine") + __log.debug("CDatabaseEngine:add_task_result_file") assert(type(task_id) == "string", "task_id must be a value of string type") assert(type(filepath) == "nil" or type(filepath) == "string", "filepath must be a value of nil or string type") assert(type(sha256) == "nil" or type(sha256) == "string", "sha256 must be a value of nil or string type") @@ -414,7 +468,7 @@ function CDatabaseEngine:add_task_result_file(task_id, filepath, sha256, err, de return nil end - local object = query['objects'] + local object = query["objects"] local res = self:exec_query(self.queries.update_task_status_add_object, object + 1, task_id) if not res then @@ -427,7 +481,7 @@ function CDatabaseEngine:add_task_result_file(task_id, filepath, sha256, err, de end if detects ~= nil then - for _,rule in ipairs(detects) do + for _, rule in ipairs(detects) do res = self:exec_query(self.queries.add_task_detect, task_id, object, rule.rule_id) if not res then return res @@ -438,76 +492,127 @@ function CDatabaseEngine:add_task_result_file(task_id, filepath, sha256, err, de return true end +-- execute cache queries + +function CDatabaseEngine:add_to_process_cache(process_id, process_image, last_scan_time, scan_results) + __log.info("CDatabaseEngine:add_to_process_cache") + + assert(process_id and type(process_id) == "number", "invalid process_id parameter") + assert(process_image and type(process_image) == "string", "invalid process_image parameter") + assert(last_scan_time and type(last_scan_time) == "number", "invalid last_scan_time parameter") + assert(scan_results and type(scan_results) == "string", "invalid scan_results parameter") + + return self:exec_query(self.queries.add_to_process_cache, process_id, process_image, last_scan_time, scan_results) +end + +function CDatabaseEngine:get_from_process_cache(process_id, process_image) + __log.info("CDatabaseEngine:get_from_process_cache") + + assert(type(process_id) == "number", "process_id must be a value of number type") + assert(type(process_image) == "string", "process_image must be a value of number type") + + local results = self:select_query_unpack_single(self.queries.get_from_process_cache, process_id, process_image) + if not results then + __log.info("CDatabaseEngine:get_scan_results_from_cache ~ empty query results") + return nil + end + return results +end + +function CDatabaseEngine:get_all_from_process_cache() + __log.info("CDatabaseEngine:get_all_from_process_cache") + + local results = self:select_query(self.queries.get_all_from_process_cache) + if not results then + __log.info("CDatabaseEngine:get_all_from_process_cache ~ empty query results") + return nil + end + return results +end + +function CDatabaseEngine:delete_from_process_cache(process_id, process_image) + __log.info("CDatabaseEngine:delete_from_process_cache") + + assert(type(process_id) == "number", "process_id must be a value of number type") + assert(type(process_image) == "string", "process_image must be a value of number type") + + -- TODO? self:delete_query(<>) + self:select_query_unpack_single(self.queries.delete_from_process_cache, process_id, process_image) +end + +function CDatabaseEngine:clear_process_cache() + __log.info("CDatabaseEngine:clear_process_cache") + + self:exec_query(self.queries.delete_all_from_process_cache) +end + function CDatabaseEngine:get_rule_meta(rule_id) - __log.debug("get_rule_meta CDatabaseEngine") - assert(type(rule_id) == "string","rule_id must be a value of string type") + __log.debug("CDatabaseEngine:get_rule_meta") + assert(type(rule_id) == "string", "rule_id must be a value of string type") local query = self:select_query_unpack_single(self.queries.get_rule_meta, rule_id) if not query then - __log.debug("get_rule_meta CDatabaseEngine, empty query results") + __log.debug("CDatabaseEngine:get_rule_meta, empty query results") return nil end local res = { - rule_name = query['rule_name'], - malware_class = query['malware_class'], - malware_family = query['malware_family'], - rule_severity = query['rule_severity'], - rule_type = query['rule_type'], - is_silent = query['is_silent'] == 1, - description = query['description'], - date = query['date'], - hash = glue.gsplit(query['hash'], "|"), - reference = glue.gsplit(query['reference'], "|"), - rule_precision = query['rule_precision'] + rule_name = query["rule_name"], + malware_class = query["malware_class"], + malware_family = query["malware_family"], + rule_severity = query["rule_severity"], + rule_type = query["rule_type"], + is_silent = query["is_silent"] == 1, + description = query["description"], + date = query["date"], + hash = glue.gsplit(query["hash"], "|"), + reference = glue.gsplit(query["reference"], "|"), + rule_precision = query["rule_precision"], } return res end local function compose_filter(filters) - if filters == nil or #filters == 0 then - return '' + return "" end - local str = '' + local str = "" - for i,filter in ipairs(filters) do - - if filter.value == '' then + for i, filter in ipairs(filters) do + if filter.value == "" then goto continue end if type(filter.value) == "string" then - str = str .. filter.field .. ' LIKE \"%' .. filter.value .. '%\"' + str = str .. filter.field .. " LIKE \"%" .. filter.value .. "%\"" else - str = str .. filter.field .. ' == ' .. filter.value + str = str .. filter.field .. " == " .. filter.value end if i ~= #filters then - str = str .. ' AND ' + str = str .. " AND " end ::continue:: end - if str ~= '' then - str = ' WHERE ' .. str + if str ~= "" then + str = " WHERE " .. str end return str end local function compose_order_and_limit(page, page_size, sort) - - local str = '' + local str = "" if sort ~= nil and sort.prop ~= nil and sort.order ~= nil then - str = str .. ' ORDER BY ' .. sort.prop .. (sort.order == 'ascending' and ' ASC' or ' DESC') + str = str .. " ORDER BY " .. sort.prop .. (sort.order == "ascending" and " ASC" or " DESC") end if page ~= nil and page_size ~= nil then - str = str .. ' LIMIT ' .. page_size .. ' OFFSET ' .. (page - 1) * page_size + str = str .. " LIMIT " .. page_size .. " OFFSET " .. (page - 1) * page_size end return str @@ -516,30 +621,28 @@ end -- function CDatabaseEngine:db_request(request_type, page, page_size, sort, filters, request_params) - local res = {} --local query local filters_str = compose_filter(filters) local order_and_limit_str = compose_order_and_limit(page, page_size, sort) - if request_type == 'db_req_active_rules' then - - if filters_str == '' then - filters_str = ' WHERE active = 1' + if request_type == "db_req_active_rules" then + if filters_str == "" then + filters_str = " WHERE active = 1" else - filters_str = filters_str .. ' AND active = 1' + filters_str = filters_str .. " AND active = 1" end - local main_part = 'FROM rules' .. filters_str + local main_part = "FROM rules" .. filters_str - local select = self:select_query_unpack_single('SELECT COUNT(*) ' .. main_part) + local select = self:select_query_unpack_single("SELECT COUNT(*) " .. main_part) if select == nil then res.error = "unable to get count of items" return res end - res.total = select['COUNT(*)'] + res.total = select["COUNT(*)"] res.rules = self:select_query("SELECT * " .. main_part .. order_and_limit_str) if not res.rules then @@ -547,22 +650,20 @@ function CDatabaseEngine:db_request(request_type, page, page_size, sort, filters res.total = nil return res end - elseif request_type == "db_req_tasks" then - - if filters_str == '' then - filters_str = ' WHERE A.task_id == B.task_id' + if filters_str == "" then + filters_str = " WHERE A.task_id == B.task_id" else - filters_str = filters_str .. ' AND A.task_id == B.task_id' + filters_str = filters_str .. " AND A.task_id == B.task_id" end - local select = self:select_query_unpack_single('SELECT COUNT(*) FROM tasks A,task_status B' .. filters_str) + local select = self:select_query_unpack_single("SELECT COUNT(*) FROM tasks A,task_status B" .. filters_str) if select == nil then res.error = "unable to get count of items" return res end - res.total = select['COUNT(*)'] + res.total = select["COUNT(*)"] res.tasks = self:select_query([[ SELECT A.task_id, @@ -588,44 +689,43 @@ function CDatabaseEngine:db_request(request_type, page, page_size, sort, filters return res end - for _,task in ipairs(res.tasks) do - if task.task_type == 1 then -- CUSTOM_PROC = 1 - task.task_params = self:select_query_unpack_single('SELECT proc_id,proc_image FROM task_params_proc WHERE task_id = ?', task.task_id) + for _, task in ipairs(res.tasks) do + if task.task_type == 1 then -- CUSTOM_PROC = 1 + task.task_params = self:select_query_unpack_single( + "SELECT proc_id,proc_image FROM task_params_proc WHERE task_id = ?", task.task_id) elseif task.task_type == 2 then -- CUSTOM_FS = 2 - task.task_params = self:select_query_unpack_single('SELECT filepath,recursive FROM task_params_fs WHERE task_id = ?', task.task_id) + task.task_params = self:select_query_unpack_single( + "SELECT filepath,recursive FROM task_params_fs WHERE task_id = ?", task.task_id) task.task_params.recursive = task.task_params.recursive == 1 end end - - elseif request_type == 'db_req_task_detects' then - + elseif request_type == "db_req_task_detects" then local task_info = self:select_query_unpack_single(self.queries.get_task, request_params.task_id) if task_info == nil then res.error = "unable to get task info" return res end - if filters_str == '' then - filters_str = ' WHERE A.task_id = ?' + if filters_str == "" then + filters_str = " WHERE A.task_id = ?" else - filters_str = filters_str .. ' AND A.task_id = ?' + filters_str = filters_str .. " AND A.task_id = ?" end local item_params local items_table if task_info.objects_type == 1 then - item_params = ' A.proc_image,A.proc_id,' - items_table = ' task_result_process A,' + item_params = " A.proc_image,A.proc_id," + items_table = " task_result_process A," else - item_params = ' A.filepath,A.sha256,' - items_table = ' task_result_file A,' + item_params = " A.filepath,A.sha256," + items_table = " task_result_file A," end local rule_params local main_part if task_info.custom_rules == nil then - rule_params = "C.rule_name,C.malware_class,C.rule_precision" main_part = [[ FROM @@ -637,7 +737,6 @@ function CDatabaseEngine:db_request(request_type, page, page_size, sort, filters AND A.object_id = B.object_id ]] else - rule_params = "B.rule_id as rule_name" main_part = [[ FROM @@ -649,15 +748,15 @@ function CDatabaseEngine:db_request(request_type, page, page_size, sort, filters ]] end - local select = self:select_query_unpack_single('SELECT COUNT(*) ' .. main_part, request_params.task_id) + local select = self:select_query_unpack_single("SELECT COUNT(*) " .. main_part, request_params.task_id) if select == nil then res.error = "unable to get count of items" return res end - res.total = select['COUNT(*)'] + res.total = select["COUNT(*)"] - res.detects = self:select_query('SELECT' .. item_params .. rule_params .. main_part .. order_and_limit_str, request_params.task_id) + res.detects = self:select_query("SELECT" .. item_params .. rule_params .. main_part .. order_and_limit_str, request_params.task_id) if not res.detects then res.error = "unable to get items" res.total = nil @@ -669,6 +768,7 @@ function CDatabaseEngine:db_request(request_type, page, page_size, sort, filters return res end + -- -- in: table, table @@ -679,13 +779,12 @@ end function CDatabaseEngine:map_columns(cols, rows) assert(type(cols) == "table", "missing columns list") assert(type(rows) == "table", "missing rows list") - __log.debugf("map_columns CDatabaseEngine") if #rows == 0 then return {} end - return glue.map(rows, function(tk, row) + return glue.map(rows, function (tk, row) row = row or tk or {} local rrow = {} for i, col in ipairs(cols) do @@ -703,7 +802,6 @@ end -- string as error result when execution was failed function CDatabaseEngine:exec_query(query, ...) assert(type(query) == "string", "missing query string to exec it") - __log.debugf("exec_query CDatabaseEngine, query: %s", query) local err local status, stmt = pcall(self.db.prepare, self.db, query) @@ -711,7 +809,7 @@ function CDatabaseEngine:exec_query(query, ...) __log.errorf("failed to prepare db exec query, %s", tostring(stmt)) return status, stmt end - if select('#', ...) > 0 then + if select("#", ...) > 0 then stmt:bind_values(...) end status, err = pcall(stmt) @@ -731,15 +829,14 @@ end -- * empty table otherways function CDatabaseEngine:select_query(query, ...) assert(type(query) == "string", "missing query string to select it") - __log.debugf("select_query CDatabaseEngine, query: %s", query) local status, stmt = pcall(self.db.prepare, self.db, query) if not status then local error = tostring(stmt) __log.errorf("failed to prepare db query: %s", error) - return {error = error} + return { error = error } end - if select('#', ...) > 0 then + if select("#", ...) > 0 then stmt:bind_values(...) end @@ -755,7 +852,7 @@ function CDatabaseEngine:select_query(query, ...) return {} end - for i=0,tonumber(stmt:columns())-1 do + for i = 0, tonumber(stmt:columns()) - 1 do table.insert(cols, stmt:get_name(i)) end pcall(stmt.finalize, stmt) @@ -766,4 +863,4 @@ end function CDatabaseEngine:select_query_unpack_single(query, ...) local res = self:select_query(query, ...) return res and res[1] or nil -end \ No newline at end of file +end diff --git a/yara_scanner/1.0.0/cmodule/main.lua b/yara_scanner/1.0.0/cmodule/main.lua index eaf615d..9eaaef8 100644 --- a/yara_scanner/1.0.0/cmodule/main.lua +++ b/yara_scanner/1.0.0/cmodule/main.lua @@ -5,15 +5,28 @@ local cjson = require("cjson") local ffi = require("ffi") -local osx = ffi.os == 'OSX' or nil -local linux = ffi.os == 'Linux' or nil -local win = ffi.abi'win' or nil +local osx = ffi.os == "OSX" or nil +local linux = ffi.os == "Linux" or nil +local win = ffi.abi "win" or nil -- base config to actions engine local cfg = { config = {}, } +local YARA_DATABASE_FILE = "soldr_yara_v3.db" + +local yara_rules_files = { + WINDOWS_JSON = "rules/kb_windows.json", + LINUX_JSON = "rules/kb_linux.json", + OSX_JSON = "rules/kb_osx.json", + MEMORY_JSON = "rules/kb_memory.json", + WINDOWS_YAR = "rules/kb_windows.yar", + LINUX_YAR = "rules/kb_linux.yar", + OSX_YAR = "rules/kb_osx.yar", + MEMORY_YAR = "rules/kb_memory.yar" +} + -- actions engine initialize local acts_engine @@ -21,24 +34,22 @@ local acts_engine __api.set_recv_timeout(5000) -- 5s __api.add_cbs({ - data = function(src, data) + data = function (src, data) __log.debugf("receive data from '%s' with data %s", src, data) assert(acts_engine ~= nil, "actions engine instance is not initialized") return acts_engine:recv_data(src, data) end, - - file = function(src, path, name) + file = function (src, path, name) __log.infof("receive file from '%s' with name '%s' path '%s'", src, name, path) assert(acts_engine ~= nil, "actions engine instance is not initialized") return acts_engine:recv_file(src, path, name) end, - -- text = function(src, text, name) -- msg = function(src, msg, mtype) - action = function(src, data, name) + action = function (src, data, name) __log.infof("receive action '%s' from '%s' with data %s", name, src, data) assert(acts_engine ~= nil, "actions engine instance is not initialized") @@ -46,8 +57,7 @@ __api.add_cbs({ __log.infof("requested action '%s' was executed: %s", name, action_result) return action_result end, - - control = function(cmtype, data) + control = function (cmtype, data) __log.debugf("receive control msg '%s' with data %s", cmtype, data) assert(acts_engine ~= nil, "actions engine instance is not initialized") @@ -69,7 +79,7 @@ __api.add_cbs({ }) -- main database -cfg.db = sqlite.open("soldr_yara_v2.db", "create") +cfg.db = sqlite.open(YARA_DATABASE_FILE, "create") if not cfg.db then __log.error("failled to open database") return "failed" @@ -85,66 +95,87 @@ else -- add __gc to close database on exit module local db_prox = newproxy(true) - getmetatable(db_prox).__gc = function() if cfg.db then cfg.db:close() end end + getmetatable(db_prox).__gc = function () if cfg.db then cfg.db:close() end end cfg.db[db_prox] = true end --- rules meta +-- initialize rules + +local function get_file_content(path) + assert((type(path) == "string"), "Invalid type of path parameter") + local file_content = __files[path] + if file_content then + return file_content + end + return __files[path:gsub("/", "\\")] +end + +local function cleanup_json_files() + __files[yara_rules_files.WINDOWS_JSON] = nil + __files[yara_rules_files.LINUX_JSON] = nil + __files[yara_rules_files.OSX_JSON] = nil + __files[yara_rules_files.MEMORY_JSON] = nil +end + +local function cleanup_yar_files() + __files[yara_rules_files.WINDOWS_YAR] = nil + __files[yara_rules_files.LINUX_YAR] = nil + __files[yara_rules_files.OSX_YAR] = nil + __files[yara_rules_files.MEMORY_YAR] = nil +end + +local function init_rules() + -- TODO refactoring + do + local rules_meta_json + if win then + rules_meta_json = get_file_content(yara_rules_files.WINDOWS_JSON) + elseif linux then + rules_meta_json = get_file_content(yara_rules_files.LINUX_JSON) + elseif osx then + rules_meta_json = get_file_content(yara_rules_files.OSX_JSON) + else + error("unknown platform") + end + assert(type(rules_meta_json == "string")) + -- TODO handle rules_meta_json ~ nil + cfg.rules_meta_files = cjson.decode(rules_meta_json) + assert(type(cfg.rules_meta_files == "table")) + + rules_meta_json = get_file_content(yara_rules_files.MEMORY_JSON) + assert(type(rules_meta_json == "string")) + -- TODO handle rules_meta_json ~ nil + cfg.rules_meta_mem = cjson.decode(rules_meta_json) + assert(type(cfg.rules_meta_mem == "table")) + end + + cleanup_json_files() -do - local rules_meta_json if win then - rules_meta_json = __files['rules/kb_windows.json'] + cfg.config_suffix = "_win" + -- TODO handle slashes + cfg.filepath_library = __tmpdir .. "\\yara_scanner.dll" + cfg.rules_files = get_file_content(yara_rules_files.WINDOWS_YAR) elseif linux then - rules_meta_json = __files['rules/kb_linux.json'] + cfg.config_suffix = "_linux" + cfg.filepath_library = __tmpdir .. "/libyara_scanner.so" + cfg.rules_files = get_file_content(yara_rules_files.LINUX_YAR) elseif osx then - rules_meta_json = __files['rules/kb_osx.json'] + cfg.config_suffix = "_mac" + cfg.filepath_library = __tmpdir .. "/libyara_scanner.so" + cfg.rules_files = get_file_content(yara_rules_files.OSX_YAR) else error("unknown platform") end - assert(type(rules_meta_json == "string")) - cfg.rules_meta_files = cjson.decode(rules_meta_json) - assert(type(cfg.rules_meta_files == "table")) + cfg.rules_mem = get_file_content(yara_rules_files.MEMORY_YAR) - rules_meta_json = __files['rules/kb_memory.json'] - assert(type(rules_meta_json == "string")) + cleanup_yar_files() - cfg.rules_meta_mem = cjson.decode(rules_meta_json) - assert(type(cfg.rules_meta_mem == "table")) + collectgarbage("collect") end -__files['rules/kb_windows.json'] = nil -__files['rules/kb_linux.json'] = nil -__files['rules/kb_osx.json'] = nil -__files['rules/kb_memory.json'] = nil - --- files - -if win then - cfg.config_suffix = '_win' - cfg.filepath_library = __tmpdir .. '/yara_scanner.dll' - cfg.rules_files = __files['rules/kb_windows.yar'] -elseif linux then - cfg.config_suffix = '_linux' - cfg.filepath_library = __tmpdir .. '/libyara_scanner.so' - cfg.rules_files = __files['rules/kb_linux.yar'] -elseif osx then - cfg.config_suffix = '_mac' - cfg.filepath_library = __tmpdir .. '/libyara_scanner.so' - cfg.rules_files = __files['rules/kb_osx.yar'] -else - error("unknown platform") -end - -cfg.rules_mem = __files['rules/kb_memory.yar'] - -__files['rules/kb_windows.yar'] = nil -__files['rules/kb_linux.yar'] = nil -__files['rules/kb_osx.yar'] = nil -__files['rules/kb_memory.yar'] = nil - -collectgarbage("collect") +init_rules() -- os @@ -160,13 +191,15 @@ acts_engine = CActsEngine(cfg) __log.infof("module '%s' was started", __config.ctx.name) -acts_engine:push_event("yr_module_started", {reason = "regular start"}) +acts_engine:push_event("yr_module_started", { reason = "regular start" }) acts_engine:run() -acts_engine:push_event("yr_module_stopped", {reason = "regular stop"}) - +acts_engine:push_event("yr_module_stopped", { reason = "regular stop" }) +__api.del_cbs({ "data", "file", "action", "control" }) __log.infof("module '%s' was stopped", __config.ctx.name) --- explicit destroy engines +acts_engine:cleanup() + +-- explicit engine destroy acts_engine = nil collectgarbage("collect") diff --git a/yara_scanner/1.0.0/cmodule/mmap.lua b/yara_scanner/1.0.0/cmodule/mmap.lua index b995bdc..a572bd6 100644 --- a/yara_scanner/1.0.0/cmodule/mmap.lua +++ b/yara_scanner/1.0.0/cmodule/mmap.lua @@ -2,73 +2,94 @@ local ffi = require("ffi") local M = {} -if ffi.os == 'Linux' then +local function init_if_linux() + if ffi.os ~= "Linux" then + return + end + ffi.cdef [[ + typedef int64_t off64_t; -ffi.cdef [[ - typedef int64_t off64_t; + int open(const char *pathname, int flags); + int close(int fd); - int open(const char *pathname, int flags); - int close(int fd); + void* mmap(void *addr, size_t length, int prot, int flags, int fd, off64_t offset); + int munmap(void *addr, size_t length); + ]] - void* mmap(void *addr, size_t length, int prot, int flags, int fd, off64_t offset); - int munmap(void *addr, size_t length); -]] + local function try_to_cdef_stat_x64() + if ffi.arch ~= "x64" then + return false + end + if pcall(ffi.typeof, "struct stat") then + -- struct "stat" was already defined + return false + end + ffi.cdef [[ + struct stat { + uint64_t st_dev; + uint64_t st_ino; + uint64_t st_nlink; + uint32_t st_mode; + uint32_t st_uid; + uint32_t st_gid; + uint32_t __pad0; + uint64_t st_rdev; + int64_t st_size; + int64_t st_blksize; + int64_t st_blocks; + uint64_t st_atime; + uint64_t st_atime_nsec; + uint64_t st_mtime; + uint64_t st_mtime_nsec; + uint64_t st_ctime; + uint64_t st_ctime_nsec; + int64_t __unused[3]; + }; + ]] + return true + end -if ffi.arch == 'x64' then -ffi.cdef [[ - struct stat { - uint64_t st_dev; - uint64_t st_ino; - uint64_t st_nlink; - uint32_t st_mode; - uint32_t st_uid; - uint32_t st_gid; - uint32_t __pad0; - uint64_t st_rdev; - int64_t st_size; - int64_t st_blksize; - int64_t st_blocks; - uint64_t st_atime; - uint64_t st_atime_nsec; - uint64_t st_mtime; - uint64_t st_mtime_nsec; - uint64_t st_ctime; - uint64_t st_ctime_nsec; - int64_t __unused[3]; - }; -]] + local function try_to_cdef_stat_non_x64() + if ffi.arch == "x64" then + return false + end + if pcall(ffi.typeof, "struct stat") then + -- struct "stat" was already defined + return false + end + ffi.cdef [[ + struct stat { + uint64_t st_dev; + uint8_t __pad0[4]; + uint32_t __st_ino; + uint32_t st_mode; + uint32_t st_nlink; + uint32_t st_uid; + uint32_t st_gid; + uint64_t st_rdev; + uint8_t __pad3[4]; + int64_t st_size; + uint32_t st_blksize; + uint64_t st_blocks; + uint32_t st_atime; + uint32_t st_atime_nsec; + uint32_t st_mtime; + uint32_t st_mtime_nsec; + uint32_t st_ctime; + uint32_t st_ctime_nsec; + uint64_t st_ino; + }; + ]] + return true + end -else + if not try_to_cdef_stat_x64() then + try_to_cdef_stat_non_x64() + end -ffi.cdef [[ - struct stat { - uint64_t st_dev; - uint8_t __pad0[4]; - uint32_t __st_ino; - uint32_t st_mode; - uint32_t st_nlink; - uint32_t st_uid; - uint32_t st_gid; - uint64_t st_rdev; - uint8_t __pad3[4]; - int64_t st_size; - uint32_t st_blksize; - uint64_t st_blocks; - uint32_t st_atime; - uint32_t st_atime_nsec; - uint32_t st_mtime; - uint32_t st_mtime_nsec; - uint32_t st_ctime; - uint32_t st_ctime_nsec; - uint64_t st_ino; - }; -]] - -end - -ffi.cdef [[ - int fstat(int fd, struct stat *buf); -]] + ffi.cdef [[ + int fstat(int fd, struct stat *buf); + ]] local MAP_FAILED = -1; @@ -77,7 +98,6 @@ ffi.cdef [[ local MAP_PRIVATE = 2 function M.mmap_ro(filepath) - local fd = ffi.C.open(filepath, O_RDONLY) if fd < 0 then return nil @@ -99,19 +119,23 @@ ffi.cdef [[ return nil; end - return {fd = fd, addr = addr, size = size} + return { fd = fd, addr = addr, size = size } end function M.munmap(map) assert(ffi.C.munmap(map.addr, map.size) == 0) assert(ffi.C.close(map.fd)) end +end -elseif ffi.abi'win' then +local function init_if_windows() + if not (ffi.abi "win") then + return + end local lk32 = require("waffi.windows.kernel32") - local INVALID_HANDLE_VALUE = -1; + local INVALID_HANDLE_VALUE = ffi.cast("HANDLE", -1); local FILE_ATTRIBUTE_NORMAL = 0x00000080 local PAGE_READONLY = 0x02 @@ -131,7 +155,6 @@ elseif ffi.abi'win' then end function M.mmap_ro(filepath) - local hFile = lk32.CreateFileW( wcs(filepath), lk32.GENERIC_READ, @@ -173,14 +196,18 @@ elseif ffi.abi'win' then return nil; end - return {hMap = hMap, addr = addr, size = size} + return { hMap = hMap, addr = addr, size = size } end function M.munmap(map) assert(lk32.UnmapViewOfFile(map.addr)) assert(lk32.CloseHandle(map.hMap)) end - end -return M \ No newline at end of file +-- TODO extract separate script files for Windows, Linux + +init_if_linux() +init_if_windows() + +return M diff --git a/yara_scanner/1.0.0/cmodule/process_api/process_api.lua b/yara_scanner/1.0.0/cmodule/process_api/process_api.lua new file mode 100644 index 0000000..43001f6 --- /dev/null +++ b/yara_scanner/1.0.0/cmodule/process_api/process_api.lua @@ -0,0 +1,4 @@ +local ffi = require("ffi") +local os = string.lower(ffi.os) +local os_specific_process_api = ("process_api_%s"):format(os) +return require(os_specific_process_api) diff --git a/yara_scanner/1.0.0/cmodule/process_api/process_api_linux.lua b/yara_scanner/1.0.0/cmodule/process_api/process_api_linux.lua new file mode 100644 index 0000000..8de04c5 --- /dev/null +++ b/yara_scanner/1.0.0/cmodule/process_api/process_api_linux.lua @@ -0,0 +1,139 @@ +local ffi = require("ffi") +local lfs = require("lfs") +local luapath = require("path") +local glue = require("glue") + +local process_api = {} + +local wait_kill_sleep = 300 + +ffi.cdef [[ + typedef uint32_t pid_t; + int kill(pid_t proc_id, int sig); + pid_t getpid(); + + typedef struct pollfd + { + int fd; + short events; + short revents; + } pollfd_t; + + typedef uint32_t nfds_t; + int poll(struct pollfd *fds, nfds_t nfds, int timeout); +]] + +local function run_callback_safe(callback, args) + local result, retval1 = glue.pcall(callback, args) + if not result then + __log.error("callback failure: " .. retval1) + return false + end + return retval1 +end + +function process_api.get_process_path(pid) + local attrs = lfs.symlinkattributes(string.format("/proc/%s/exe", pid)) + if type(attrs) ~= "table" then + return "", "not found" + elseif attrs["mode"] ~= "link" then + return "", "invalid process id" + elseif type(attrs["target"]) ~= "string" then + return "", "permission deny" + end + return attrs["target"] +end + +function process_api.kill_process(pid) + local SIGNOP = 0 + local SIGKILL = 9 + + if ffi.C.kill(pid, SIGKILL) ~= 0 then + return false + end + + while ffi.C.kill(pid, SIGNOP) == 0 do + ffi.C.poll(nil, 0, wait_kill_sleep) + end + + return true +end + +-- might be a number or "self" +local function get_process_info_linux(proc_id_str) + assert(type(proc_id_str) == "string") + local file = io.open("/proc/" .. proc_id_str .. "/stat", "r") + if file ~= nil then + local info = file:read() + local _pid, _ppid = string.match(info or "", "(%S+) %S+ %S+ (%S+)") + local pid = tonumber(_pid) + local parent_pid = tonumber(_ppid) + file:close() + if pid ~= nil and parent_pid ~= nil then + return pid, parent_pid, false + else + return nil, nil, true + end + end + + return nil, nil, true +end + +function process_api.for_each_process(callback) + if not callback then + error("no callback provided") + end + + local imagepath, err + for file in lfs.dir("/proc") do + if file == "." or file == ".." or tonumber(file) == nil then + goto continue + end + + local attrs = lfs.attributes(string.format("/proc/%s", file)) + if type(attrs) ~= "table" or attrs["mode"] ~= "directory" then + -- __log.debugf("Wrong attributes for '%s'", file) + goto continue + end + + imagepath, err = process_api.get_process_path(file) + if err then + -- __log.debugf("Failed to get process path for '%s': %s", file, err) + goto continue + end + + local pid, parent_pid + local name = luapath.file(imagepath) + pid, parent_pid, err = get_process_info_linux(file) + if err then + -- __log.debugf("Failed to get process info for '%s': %s", file, err) + goto continue + end + + if pid == nil or parent_pid == nil then + __log.info("Invalid PID: -> " .. pid .. " expected ->" .. file) + end + + local args = { + pid = tonumber(pid), + name = name, + parent_pid = tonumber(parent_pid), + path = imagepath, + } + + if (run_callback_safe(callback, args)) then + return + end + + ::continue:: + end +end + +function process_api.update_agent_info() + local aid, apath + aid = get_process_info_linux("self") + apath = process_api.get_process_path("self") + return aid, apath +end + +return process_api diff --git a/yara_scanner/1.0.0/cmodule/process_api/process_api_osx.lua b/yara_scanner/1.0.0/cmodule/process_api/process_api_osx.lua new file mode 100644 index 0000000..6f68b74 --- /dev/null +++ b/yara_scanner/1.0.0/cmodule/process_api/process_api_osx.lua @@ -0,0 +1,69 @@ +local ffi = require("ffi") +local luapath = require("path") +local glue = require("glue") + +local process_api_linux = require("process_api_linux") + +local process_api = {} + +local function run_callback_safe(callback, args) + local result, retval1 = glue.pcall(callback, args) + if not result then + __log.error("callback failure: " .. retval1) + return false + end + return retval1 +end + +function process_api.get_process_path(pid) + assert(type(pid) == "number", "PID should a number") + local cmd = "/bin/ps o comm=\"\" " .. tostring(pid) -- comm for osx, command for linux + local cmd_handle = assert(io.popen(cmd, "r"), "failed to call io.popen") + local imagepath = assert(cmd_handle:read("*all"), "failed to read from pipe") + imagepath = string.gsub(imagepath, "^%s*(.-)%s*$", "%1") + __log.debugf("handlers.osx.get_process_path for '%d' -> '%s'", pid, imagepath) + cmd_handle:close() + if imagepath ~= nil and imagepath ~= "" then + return imagepath, nil + else + return nil, "Not found" + end +end + +function process_api.for_each_process(callback) + -- TODO move this to sysctl syscall to retrieve the process table. + local cmd = "/bin/ps axo pid=\"\",ppid=\"\",comm=\"\"" -- comm for osx, command for linux + local cmd_handle = assert(io.popen(cmd, "r"), "failed to call io.popen") + local cmd_res = assert(cmd_handle:read("*all"), "failed to read from pipe") + cmd_handle:close() + for str in string.gmatch(cmd_res, "([^" .. "\n" .. "]+)") do + local pid, parent_pid, imagepath = str:match("%s*(%S+)%s+(%S+) ([^.]+)") + if (imagepath ~= nil) then + imagepath = imagepath:gsub("^%s*(.-)%s*$", "%1") + end + __log.debugf("process_api.for_each_process PID -> '%s' PPID -> '%s' IMAGE -> '%s", pid, parent_pid, imagepath) + if pid ~= nil and parent_pid ~= nil and imagepath ~= nil then + local args = { + pid = tonumber(pid), + name = luapath.file(imagepath), + parent_pid = tonumber(parent_pid), + path = imagepath, + } + + if (run_callback_safe(callback, args)) then + return + end + end + end +end + +function process_api.update_agent_info() + local aid, apath + aid = ffi.C.getpid() + apath = process_api.get_process_path(aid) + return aid, apath +end + +process_api.kill_process = process_api_linux.kill_process + +return process_api diff --git a/yara_scanner/1.0.0/cmodule/process_api/process_api_windows.lua b/yara_scanner/1.0.0/cmodule/process_api/process_api_windows.lua new file mode 100644 index 0000000..98d9256 --- /dev/null +++ b/yara_scanner/1.0.0/cmodule/process_api/process_api_windows.lua @@ -0,0 +1,160 @@ +local bit = require("bit") +local ffi = require("ffi") +local glue = require("glue") +local lk32 = require("waffi.windows.kernel32") + +local process_api = {} + +local win_const = { + TH32CS_SNAPPROCESS = 0x00000002, + SYNCHRONIZE = 0x00100000, + INVALID_HANDLE_VALUE = ffi.cast("HANDLE", -1), +} + +ffi.cdef [[ + typedef struct tagPROCESSENTRY32W { + DWORD dwSize; + DWORD cntUsage; + DWORD th32ProcessID; + ULONG_PTR th32DefaultHeapID; + DWORD th32ModuleID; + DWORD cntThreads; + DWORD th32ParentProcessID; + LONG pcPriClassBase; + DWORD dwFlags; + WCHAR szExeFile[MAX_PATH]; + } PROCESSENTRY32W; + + BOOL Process32FirstW( HANDLE hSnapshot, LPPROCESSENTRY32W lppe); + BOOL Process32NextW( HANDLE hSnapshot, LPPROCESSENTRY32W lppe); +]] + +local function run_callback_safe(callback, args) + local result, retval1 = glue.pcall(callback, args) + if not result then + __log.error("callback failure: " .. retval1) + return false + end + return retval1 +end + +function process_api.create_buffer(size) + return size > 0 and + { ptr = ffi.new("char[?]", size), size = size } or + { ptr = nil, size = 0 } +end + +function process_api.to_utf8(wstr, buffer) + if wstr == ffi.NULL then return "" end + buffer = buffer ~= nil and buffer or process_api.create_buffer(0) + local size = buffer.ptr and ffi.C.WideCharToMultiByte(lk32.CP_UTF8, 0, wstr, -1, buffer.ptr, buffer.size, nil, nil) or 0 + if size == 0 then + size = ffi.C.WideCharToMultiByte(lk32.CP_UTF8, 0, wstr, -1, nil, 0, nil, nil) + if size == 0 then return "" end + buffer = process_api.create_buffer(size) + size = ffi.C.WideCharToMultiByte(lk32.CP_UTF8, 0, wstr, -1, buffer.ptr, buffer.size, nil, nil) + end + local utf8_string = size > 0 and ffi.string(buffer.ptr) or "" + return utf8_string, size +end + +--[[ + Call given callback for each process + Callback arg table: { + pid - process id + name - process name + parent_pid - process parent id + path - process image path + } + Callback return value: + bool - whether to stop iteration +--]] +function process_api.for_each_process(callback) + if not callback then + __log.error("no callback provided") + return + end + + local proc_entry = ffi.new("PROCESSENTRY32W[1]") + proc_entry[0].dwSize = ffi.sizeof("PROCESSENTRY32W") + local snap_handle = lk32.CreateToolhelp32Snapshot(win_const.TH32CS_SNAPPROCESS, 0) + assert(snap_handle ~= win_const.INVALID_HANDLE_VALUE, "failed to get list of processes") + + local name_buffer = process_api.create_buffer(1024) + + if (lk32.Process32FirstW(snap_handle, proc_entry[0]) == 1) then + while (lk32.Process32NextW(snap_handle, proc_entry[0]) == 1) do + local pid = tonumber(proc_entry[0].th32ProcessID) + local args = { + pid = pid, + name = process_api.to_utf8(proc_entry[0].szExeFile, name_buffer), + parent_pid = tonumber(proc_entry[0].th32ParentProcessID), + } + + if (run_callback_safe(callback, args)) then + break + end + end + else + __log.error("failed to get info from snapshot") + end + if snap_handle ~= ffi.NULL then + lk32.CloseHandle(snap_handle) + end +end + +function process_api.get_last_error() + local err = lk32.GetLastError() + __log.debugf("winapi last err: %d", tonumber(err)) + return err +end + +function process_api.get_process_handle(pid) + local flags = bit.bor(lk32.PROCESS_QUERY_LIMITED_INFORMATION, lk32.PROCESS_TERMINATE, lk32.PROCESS_VM_READ, win_const.SYNCHRONIZE) + local handle = lk32.OpenProcess(flags, false, pid) + if handle == ffi.NULL then + return nil, process_api.get_last_error() + end + return handle, nil +end + +function process_api.kill_process(pid) + local handle, error = process_api.get_process_handle(pid) + if error then + return false + end + if lk32.TerminateProcess(handle, 0) == 0 then + return false + end + lk32.WaitForSingleObject(handle, lk32.INFINITE) + return true +end + +-- by using less priveleged handle we can get path for any process +-- GetModuleFileNameExA didn't work on w7x64 +function process_api.get_process_path(pid) + local process_handle, err = lk32.OpenProcess(lk32.PROCESS_QUERY_LIMITED_INFORMATION, false, pid) + if process_handle == nil then + return "", err + end + local max_path = lk32.MAX_PATH + local filename = ffi.new("wchar_t[?]", max_path) + local filename_buffer = process_api.create_buffer(1024) + local size = ffi.new("DWORD[1]", 2048) + if lk32.QueryFullProcessImageNameW(process_handle, 0, filename, size) ~= 1 then + lk32.CloseHandle(process_handle) + return "", "failed to get process path" + end + lk32.CloseHandle(process_handle) + local path = process_api.to_utf8(filename, filename_buffer) + return path, nil +end + +function process_api.update_agent_info() + local aid, apath + aid = tonumber(lk32.GetCurrentProcessId()) + apath = process_api.get_process_path(aid):lower() + return aid, apath +end + +return process_api diff --git a/yara_scanner/1.0.0/cmodule/utils/yara_utils.lua b/yara_scanner/1.0.0/cmodule/utils/yara_utils.lua new file mode 100644 index 0000000..79c254d --- /dev/null +++ b/yara_scanner/1.0.0/cmodule/utils/yara_utils.lua @@ -0,0 +1,19 @@ +require("yaci") + +local YaraUtils = newclass("YaraUtils") + +local MINUTE_SECONDS = 60 + +function YaraUtils:init() +end + +function YaraUtils:get_UTC_time() + return os.time(os.date("!*t")) +end + +function YaraUtils:to_seconds(minutes) + assert((type(minutes) == "number"), "YaraUtils:to_seconds() ~ invalid minutes parameter") + return minutes * MINUTE_SECONDS +end + +return YaraUtils diff --git a/yara_scanner/1.0.0/cmodule/yara_cmodule.lua b/yara_scanner/1.0.0/cmodule/yara_cmodule.lua index 906de6a..d713bd7 100644 --- a/yara_scanner/1.0.0/cmodule/yara_cmodule.lua +++ b/yara_scanner/1.0.0/cmodule/yara_cmodule.lua @@ -122,7 +122,6 @@ yara_error_t yara_scan_task_stop(int task_id); ]] function CYaraModule:init(library_filename) - assert(type(library_filename) == "string", "library filename must be a string") self.module = ffi.load(library_filename) @@ -143,8 +142,10 @@ end function CYaraModule:set_callbacks(callback_result, callback_complete) assert(self.module ~= nil, "module is not loaded") - assert(type(callback_result) == "nil" or type(callback_result) == "function", "callback_result must be a function or nil") - assert(type(callback_complete) == "nil" or type(callback_complete) == "function", "callback_complete must be a function or nil") + assert(type(callback_result) == "nil" or type(callback_result) == "function", + "callback_result must be a function or nil") + assert(type(callback_complete) == "nil" or type(callback_complete) == "function", + "callback_complete must be a function or nil") local cbs = ffi.new("yara_callbacks_t[1]") cbs[0].scan_task_result = callback_result @@ -201,26 +202,26 @@ function CYaraModule:reload_rules(rules) local rules_opened = {} - for tag,t in pairs(rules) do + for tag, t in pairs(rules) do local map, own if t.string ~= nil then map = { addr = ffi.cast("const void*", t.string), - size = #t.string + size = #t.string, } elseif t.filepath ~= nil then map = assert(mmap.mmap_ro(t.filepath)) own = true end - table.insert(rules_opened, {tag = tag, map = map, own = own}) + table.insert(rules_opened, { tag = tag, map = map, own = own }) end local load_items = ffi.new("yara_rule_load_item_t[?]", #rules_opened) - for i,rule in ipairs(rules_opened) do + for i, rule in ipairs(rules_opened) do load_items[i - 1].tag = rule.tag load_items[i - 1].string_data = rule.map.addr load_items[i - 1].string_size = rule.map.size @@ -228,7 +229,7 @@ function CYaraModule:reload_rules(rules) local err = self.api.reload_rules(load_items, #rules_opened) - for _,rule in ipairs(rules_opened) do + for _, rule in ipairs(rules_opened) do if rule.own then --rule.map:free() mmap.munmap(rule.map) @@ -248,7 +249,7 @@ function CYaraModule:unload_rules(tags) local unload_tags = ffi.new("uint32_t[?]", #tags) - for i,tag in ipairs(tags) do + for i, tag in ipairs(tags) do unload_tags[i - 1] = tag end @@ -286,7 +287,7 @@ function CYaraModule:decode_result(result) res.rules = {} if result.rules_count ~= 0 then - for i=1,result.rules_count do + for i = 1, result.rules_count do table.insert(res.rules, ffi.string(result.rule_names[i - 1])) end end @@ -309,7 +310,7 @@ function CYaraModule:decode_error(err) local msg - local decode_error_callback_c = ffi.cast("pfn_yara_cb_decode_error_t", function(str, _) + local decode_error_callback_c = ffi.cast("pfn_yara_cb_decode_error_t", function (str, _) msg = ffi.string(str) end) @@ -324,7 +325,6 @@ function CYaraModule:decode_error(err) end function CYaraModule:prepare_process_path(path) - local _ = self if path == nil then @@ -349,15 +349,14 @@ function CYaraModule:scan_fs(filepath, recursive, tag, excludes) local results = {} - local result_callback_c = ffi.cast("pfn_yara_cb_scan_result_file_t", function(err, result, _) - + local result_callback_c = ffi.cast("pfn_yara_cb_scan_result_file_t", function (err, result, _) local msg if err ~= nil then msg = self:decode_error(err[0]) end local data = self:decode_result(result) - table.insert(results, {error = msg, filepath = data.filepath, sha256_filehash = data.sha256, rules = data.rules}) + table.insert(results, { error = msg, filepath = data.filepath, sha256_filehash = data.sha256, rules = data.rules }) end) local params = ffi.new("yara_scan_fs_params_t[1]") @@ -365,11 +364,10 @@ function CYaraModule:scan_fs(filepath, recursive, tag, excludes) params[0].recursive = recursive if excludes ~= nil and #excludes ~= 0 then - params[0].excludes.size = #excludes params[0].excludes.items = ffi.new("const char*[?]", #excludes) - for i,exclude in ipairs(excludes) do + for i, exclude in ipairs(excludes) do params[0].excludes.items[i - 1] = exclude end else @@ -395,15 +393,14 @@ function CYaraModule:scan_proc(pid, imagename, tag, excludes) local results = {} - local result_callback_c = ffi.cast("pfn_yara_cb_scan_result_process_t", function(err, result, _) - + local result_callback_c = ffi.cast("pfn_yara_cb_scan_result_process_t", function (err, result, _) local msg if err ~= nil then msg = self:decode_error(err[0]) end local data = self:decode_result(result) - table.insert(results, {error = msg, proc_image = data.imagepath, proc_id = data.pid, rules = data.rules}) + table.insert(results, { error = msg, proc_image = data.imagepath, proc_id = data.pid, rules = data.rules }) end) local params = ffi.new("yara_scan_proc_params_t[1]") @@ -411,11 +408,10 @@ function CYaraModule:scan_proc(pid, imagename, tag, excludes) params[0].imagename_pattern = self:prepare_process_path(imagename) if excludes ~= nil and #excludes ~= 0 then - params[0].excludes.size = #excludes params[0].excludes.items = ffi.new("const char*[?]", #excludes) - for i,exclude in ipairs(excludes) do + for i, exclude in ipairs(excludes) do params[0].excludes.items[i - 1] = exclude end else @@ -444,11 +440,10 @@ function CYaraModule:task_scan_fs(filepath, recursive, tag, excludes) params[0].recursive = recursive if excludes ~= nil and #excludes ~= 0 then - params[0].excludes.size = #excludes params[0].excludes.items = ffi.new("const char*[?]", #excludes) - for i,exclude in ipairs(excludes) do + for i, exclude in ipairs(excludes) do params[0].excludes.items[i - 1] = exclude end else @@ -477,11 +472,10 @@ function CYaraModule:task_scan_proc(pid, imagename, tag, excludes) params[0].imagename_pattern = self:prepare_process_path(imagename) if excludes ~= nil and #excludes ~= 0 then - params[0].excludes.size = #excludes params[0].excludes.items = ffi.new("const char*[?]", #excludes) - for i,exclude in ipairs(excludes) do + for i, exclude in ipairs(excludes) do params[0].excludes.items[i - 1] = exclude end else diff --git a/yara_scanner/1.0.0/config/action_config_schema.json b/yara_scanner/1.0.0/config/action_config_schema.json index 188821d..5bf5355 100644 --- a/yara_scanner/1.0.0/config/action_config_schema.json +++ b/yara_scanner/1.0.0/config/action_config_schema.json @@ -39,6 +39,44 @@ } ] }, + "yr_object_scan_proc_non_cached": { + "allOf": [ + { + "$ref": "#/definitions/base.action" + }, + { + "properties": { + "fields": { + "default": [ + "object.process.fullpath", + "object.process.id" + ], + "items": { + "enum": [ + "object.process.fullpath", + "object.process.id" + ], + "type": "string" + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "priority": { + "default": 78, + "maximum": 78, + "minimum": 78, + "type": "integer" + } + }, + "required": [ + "fields", + "priority" + ], + "type": "object" + } + ] + }, "yr_object_task_scan_proc": { "allOf": [ { @@ -151,6 +189,44 @@ } ] }, + "yr_subject_scan_proc_non_cached": { + "allOf": [ + { + "$ref": "#/definitions/base.action" + }, + { + "properties": { + "fields": { + "default": [ + "subject.process.fullpath", + "subject.process.id" + ], + "items": { + "enum": [ + "subject.process.fullpath", + "subject.process.id" + ], + "type": "string" + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "priority": { + "default": 78, + "maximum": 78, + "minimum": 78, + "type": "integer" + } + }, + "required": [ + "fields", + "priority" + ], + "type": "object" + } + ] + }, "yr_subject_task_scan_proc": { "allOf": [ { @@ -344,9 +420,11 @@ }, "required": [ "yr_object_scan_proc", + "yr_object_scan_proc_non_cached", "yr_object_task_scan_proc", "yr_scan_fs", "yr_subject_scan_proc", + "yr_subject_scan_proc_non_cached", "yr_subject_task_scan_proc", "yr_task_fastscan_fs", "yr_task_fastscan_proc", diff --git a/yara_scanner/1.0.0/config/config_schema.json b/yara_scanner/1.0.0/config/config_schema.json index cfc288b..6c58e2a 100644 --- a/yara_scanner/1.0.0/config/config_schema.json +++ b/yara_scanner/1.0.0/config/config_schema.json @@ -321,8 +321,27 @@ "itemCollapse": true } } + }, + "yara_process_caching_time": { + "default": 60, + "rules": { + "number": true, + "required": true + }, + "type": "number", + "ui": { + "columns": 10, + "widget": "input-number", + "widgetConfig": { + "clearable": false, + "columns": 10, + "max": 240, + "min": 0, + "step": 1 + } + } } }, "required": [], "type": "object" -} \ No newline at end of file +} diff --git a/yara_scanner/1.0.0/config/current_action_config.json b/yara_scanner/1.0.0/config/current_action_config.json index 7acc73f..c091446 100644 --- a/yara_scanner/1.0.0/config/current_action_config.json +++ b/yara_scanner/1.0.0/config/current_action_config.json @@ -6,6 +6,13 @@ ], "priority": 78 }, + "yr_object_scan_proc_non_cached": { + "fields": [ + "object.process.fullpath", + "object.process.id" + ], + "priority": 78 + }, "yr_object_task_scan_proc": { "fields": [ "object.process.fullpath", @@ -26,6 +33,13 @@ ], "priority": 78 }, + "yr_subject_scan_proc_non_cached": { + "fields": [ + "subject.process.fullpath", + "subject.process.id" + ], + "priority": 78 + }, "yr_subject_task_scan_proc": { "fields": [ "subject.process.fullpath", diff --git a/yara_scanner/1.0.0/config/current_config.json b/yara_scanner/1.0.0/config/current_config.json index d73bd79..340fdbe 100644 --- a/yara_scanner/1.0.0/config/current_config.json +++ b/yara_scanner/1.0.0/config/current_config.json @@ -379,5 +379,6 @@ "enabled": true, "malware_class": "NET_WORM" } - ] -} \ No newline at end of file + ], + "yara_process_caching_time": 60 +} diff --git a/yara_scanner/1.0.0/config/current_event_config.json b/yara_scanner/1.0.0/config/current_event_config.json index 9a05a00..d0a1c50 100644 --- a/yara_scanner/1.0.0/config/current_event_config.json +++ b/yara_scanner/1.0.0/config/current_event_config.json @@ -120,6 +120,25 @@ ], "type": "atomic" }, + "yr_object_process_matched_high_cached": { + "actions": [ + { + "fields": [], + "module_name": "this", + "name": "log_to_db", + "priority": 10 + } + ], + "fields": [ + "malware_class", + "object.process.fullpath", + "object.process.id", + "rule_name", + "rule_precision", + "rule_type" + ], + "type": "atomic" + }, "yr_object_process_matched_low": { "actions": [ { @@ -139,6 +158,25 @@ ], "type": "atomic" }, + "yr_object_process_matched_low_cached": { + "actions": [ + { + "fields": [], + "module_name": "this", + "name": "log_to_db", + "priority": 10 + } + ], + "fields": [ + "malware_class", + "object.process.fullpath", + "object.process.id", + "rule_name", + "rule_precision", + "rule_type" + ], + "type": "atomic" + }, "yr_object_process_matched_medium": { "actions": [ { @@ -158,6 +196,25 @@ ], "type": "atomic" }, + "yr_object_process_matched_medium_cached": { + "actions": [ + { + "fields": [], + "module_name": "this", + "name": "log_to_db", + "priority": 10 + } + ], + "fields": [ + "malware_class", + "object.process.fullpath", + "object.process.id", + "rule_name", + "rule_precision", + "rule_type" + ], + "type": "atomic" + }, "yr_process_matched_custom": { "actions": [ { @@ -194,6 +251,25 @@ ], "type": "atomic" }, + "yr_subject_process_matched_high_cached": { + "actions": [ + { + "fields": [], + "module_name": "this", + "name": "log_to_db", + "priority": 10 + } + ], + "fields": [ + "malware_class", + "rule_name", + "rule_precision", + "rule_type", + "subject.process.fullpath", + "subject.process.id" + ], + "type": "atomic" + }, "yr_subject_process_matched_low": { "actions": [ { @@ -213,6 +289,25 @@ ], "type": "atomic" }, + "yr_subject_process_matched_low_cached": { + "actions": [ + { + "fields": [], + "module_name": "this", + "name": "log_to_db", + "priority": 10 + } + ], + "fields": [ + "malware_class", + "rule_name", + "rule_precision", + "rule_type", + "subject.process.fullpath", + "subject.process.id" + ], + "type": "atomic" + }, "yr_subject_process_matched_medium": { "actions": [ { @@ -231,5 +326,24 @@ "subject.process.id" ], "type": "atomic" + }, + "yr_subject_process_matched_medium_cached": { + "actions": [ + { + "fields": [], + "module_name": "this", + "name": "log_to_db", + "priority": 10 + } + ], + "fields": [ + "malware_class", + "rule_name", + "rule_precision", + "rule_type", + "subject.process.fullpath", + "subject.process.id" + ], + "type": "atomic" } } \ No newline at end of file diff --git a/yara_scanner/1.0.0/config/default_action_config.json b/yara_scanner/1.0.0/config/default_action_config.json index 7acc73f..c091446 100644 --- a/yara_scanner/1.0.0/config/default_action_config.json +++ b/yara_scanner/1.0.0/config/default_action_config.json @@ -6,6 +6,13 @@ ], "priority": 78 }, + "yr_object_scan_proc_non_cached": { + "fields": [ + "object.process.fullpath", + "object.process.id" + ], + "priority": 78 + }, "yr_object_task_scan_proc": { "fields": [ "object.process.fullpath", @@ -26,6 +33,13 @@ ], "priority": 78 }, + "yr_subject_scan_proc_non_cached": { + "fields": [ + "subject.process.fullpath", + "subject.process.id" + ], + "priority": 78 + }, "yr_subject_task_scan_proc": { "fields": [ "subject.process.fullpath", diff --git a/yara_scanner/1.0.0/config/default_config.json b/yara_scanner/1.0.0/config/default_config.json index d73bd79..340fdbe 100644 --- a/yara_scanner/1.0.0/config/default_config.json +++ b/yara_scanner/1.0.0/config/default_config.json @@ -379,5 +379,6 @@ "enabled": true, "malware_class": "NET_WORM" } - ] -} \ No newline at end of file + ], + "yara_process_caching_time": 60 +} diff --git a/yara_scanner/1.0.0/config/default_event_config.json b/yara_scanner/1.0.0/config/default_event_config.json index 9a05a00..d0a1c50 100644 --- a/yara_scanner/1.0.0/config/default_event_config.json +++ b/yara_scanner/1.0.0/config/default_event_config.json @@ -120,6 +120,25 @@ ], "type": "atomic" }, + "yr_object_process_matched_high_cached": { + "actions": [ + { + "fields": [], + "module_name": "this", + "name": "log_to_db", + "priority": 10 + } + ], + "fields": [ + "malware_class", + "object.process.fullpath", + "object.process.id", + "rule_name", + "rule_precision", + "rule_type" + ], + "type": "atomic" + }, "yr_object_process_matched_low": { "actions": [ { @@ -139,6 +158,25 @@ ], "type": "atomic" }, + "yr_object_process_matched_low_cached": { + "actions": [ + { + "fields": [], + "module_name": "this", + "name": "log_to_db", + "priority": 10 + } + ], + "fields": [ + "malware_class", + "object.process.fullpath", + "object.process.id", + "rule_name", + "rule_precision", + "rule_type" + ], + "type": "atomic" + }, "yr_object_process_matched_medium": { "actions": [ { @@ -158,6 +196,25 @@ ], "type": "atomic" }, + "yr_object_process_matched_medium_cached": { + "actions": [ + { + "fields": [], + "module_name": "this", + "name": "log_to_db", + "priority": 10 + } + ], + "fields": [ + "malware_class", + "object.process.fullpath", + "object.process.id", + "rule_name", + "rule_precision", + "rule_type" + ], + "type": "atomic" + }, "yr_process_matched_custom": { "actions": [ { @@ -194,6 +251,25 @@ ], "type": "atomic" }, + "yr_subject_process_matched_high_cached": { + "actions": [ + { + "fields": [], + "module_name": "this", + "name": "log_to_db", + "priority": 10 + } + ], + "fields": [ + "malware_class", + "rule_name", + "rule_precision", + "rule_type", + "subject.process.fullpath", + "subject.process.id" + ], + "type": "atomic" + }, "yr_subject_process_matched_low": { "actions": [ { @@ -213,6 +289,25 @@ ], "type": "atomic" }, + "yr_subject_process_matched_low_cached": { + "actions": [ + { + "fields": [], + "module_name": "this", + "name": "log_to_db", + "priority": 10 + } + ], + "fields": [ + "malware_class", + "rule_name", + "rule_precision", + "rule_type", + "subject.process.fullpath", + "subject.process.id" + ], + "type": "atomic" + }, "yr_subject_process_matched_medium": { "actions": [ { @@ -231,5 +326,24 @@ "subject.process.id" ], "type": "atomic" + }, + "yr_subject_process_matched_medium_cached": { + "actions": [ + { + "fields": [], + "module_name": "this", + "name": "log_to_db", + "priority": 10 + } + ], + "fields": [ + "malware_class", + "rule_name", + "rule_precision", + "rule_type", + "subject.process.fullpath", + "subject.process.id" + ], + "type": "atomic" } } \ No newline at end of file diff --git a/yara_scanner/1.0.0/config/event_config_schema.json b/yara_scanner/1.0.0/config/event_config_schema.json index 29794eb..c5b288b 100644 --- a/yara_scanner/1.0.0/config/event_config_schema.json +++ b/yara_scanner/1.0.0/config/event_config_schema.json @@ -250,6 +250,45 @@ } ] }, + "yr_object_process_matched_high_cached": { + "allOf": [ + { + "$ref": "#/definitions/events.atomic" + }, + { + "properties": { + "fields": { + "default": [ + "malware_class", + "object.process.fullpath", + "object.process.id", + "rule_name", + "rule_precision", + "rule_type" + ], + "items": { + "enum": [ + "malware_class", + "object.process.fullpath", + "object.process.id", + "rule_name", + "rule_precision", + "rule_type" + ], + "type": "string" + }, + "maxItems": 6, + "minItems": 6, + "type": "array" + } + }, + "required": [ + "fields" + ], + "type": "object" + } + ] + }, "yr_object_process_matched_low": { "allOf": [ { @@ -289,6 +328,45 @@ } ] }, + "yr_object_process_matched_low_cached": { + "allOf": [ + { + "$ref": "#/definitions/events.atomic" + }, + { + "properties": { + "fields": { + "default": [ + "malware_class", + "object.process.fullpath", + "object.process.id", + "rule_name", + "rule_precision", + "rule_type" + ], + "items": { + "enum": [ + "malware_class", + "object.process.fullpath", + "object.process.id", + "rule_name", + "rule_precision", + "rule_type" + ], + "type": "string" + }, + "maxItems": 6, + "minItems": 6, + "type": "array" + } + }, + "required": [ + "fields" + ], + "type": "object" + } + ] + }, "yr_object_process_matched_medium": { "allOf": [ { @@ -328,6 +406,45 @@ } ] }, + "yr_object_process_matched_medium_cached": { + "allOf": [ + { + "$ref": "#/definitions/events.atomic" + }, + { + "properties": { + "fields": { + "default": [ + "malware_class", + "object.process.fullpath", + "object.process.id", + "rule_name", + "rule_precision", + "rule_type" + ], + "items": { + "enum": [ + "malware_class", + "object.process.fullpath", + "object.process.id", + "rule_name", + "rule_precision", + "rule_type" + ], + "type": "string" + }, + "maxItems": 6, + "minItems": 6, + "type": "array" + } + }, + "required": [ + "fields" + ], + "type": "object" + } + ] + }, "yr_process_matched_custom": { "allOf": [ { @@ -402,6 +519,45 @@ } ] }, + "yr_subject_process_matched_high_cached": { + "allOf": [ + { + "$ref": "#/definitions/events.atomic" + }, + { + "properties": { + "fields": { + "default": [ + "malware_class", + "rule_name", + "rule_precision", + "rule_type", + "subject.process.fullpath", + "subject.process.id" + ], + "items": { + "enum": [ + "malware_class", + "rule_name", + "rule_precision", + "rule_type", + "subject.process.fullpath", + "subject.process.id" + ], + "type": "string" + }, + "maxItems": 6, + "minItems": 6, + "type": "array" + } + }, + "required": [ + "fields" + ], + "type": "object" + } + ] + }, "yr_subject_process_matched_low": { "allOf": [ { @@ -441,6 +597,45 @@ } ] }, + "yr_subject_process_matched_low_cached": { + "allOf": [ + { + "$ref": "#/definitions/events.atomic" + }, + { + "properties": { + "fields": { + "default": [ + "malware_class", + "rule_name", + "rule_precision", + "rule_type", + "subject.process.fullpath", + "subject.process.id" + ], + "items": { + "enum": [ + "malware_class", + "rule_name", + "rule_precision", + "rule_type", + "subject.process.fullpath", + "subject.process.id" + ], + "type": "string" + }, + "maxItems": 6, + "minItems": 6, + "type": "array" + } + }, + "required": [ + "fields" + ], + "type": "object" + } + ] + }, "yr_subject_process_matched_medium": { "allOf": [ { @@ -479,6 +674,45 @@ "type": "object" } ] + }, + "yr_subject_process_matched_medium_cached": { + "allOf": [ + { + "$ref": "#/definitions/events.atomic" + }, + { + "properties": { + "fields": { + "default": [ + "malware_class", + "rule_name", + "rule_precision", + "rule_type", + "subject.process.fullpath", + "subject.process.id" + ], + "items": { + "enum": [ + "malware_class", + "rule_name", + "rule_precision", + "rule_type", + "subject.process.fullpath", + "subject.process.id" + ], + "type": "string" + }, + "maxItems": 6, + "minItems": 6, + "type": "array" + } + }, + "required": [ + "fields" + ], + "type": "object" + } + ] } }, "required": [ @@ -489,12 +723,18 @@ "yr_module_started", "yr_module_stopped", "yr_object_process_matched_high", + "yr_object_process_matched_high_cached", "yr_object_process_matched_low", + "yr_object_process_matched_low_cached", "yr_object_process_matched_medium", + "yr_object_process_matched_medium_cached", "yr_process_matched_custom", "yr_subject_process_matched_high", + "yr_subject_process_matched_high_cached", "yr_subject_process_matched_low", - "yr_subject_process_matched_medium" + "yr_subject_process_matched_low_cached", + "yr_subject_process_matched_medium", + "yr_subject_process_matched_medium_cached" ], "type": "object" } \ No newline at end of file diff --git a/yara_scanner/1.0.0/config/info.json b/yara_scanner/1.0.0/config/info.json index 6ade260..579c402 100644 --- a/yara_scanner/1.0.0/config/info.json +++ b/yara_scanner/1.0.0/config/info.json @@ -19,9 +19,11 @@ "system": false, "actions": [ "yr_object_scan_proc", + "yr_object_scan_proc_non_cached", "yr_object_task_scan_proc", "yr_scan_fs", "yr_subject_scan_proc", + "yr_subject_scan_proc_non_cached", "yr_subject_task_scan_proc", "yr_task_fastscan_fs", "yr_task_fastscan_proc", @@ -37,12 +39,18 @@ "yr_module_started", "yr_module_stopped", "yr_object_process_matched_high", + "yr_object_process_matched_high_cached", "yr_object_process_matched_low", + "yr_object_process_matched_low_cached", "yr_object_process_matched_medium", + "yr_object_process_matched_medium_cached", "yr_process_matched_custom", "yr_subject_process_matched_high", + "yr_subject_process_matched_high_cached", "yr_subject_process_matched_low", - "yr_subject_process_matched_medium" + "yr_subject_process_matched_low_cached", + "yr_subject_process_matched_medium", + "yr_subject_process_matched_medium_cached" ], "fields": [ "malware_class", diff --git a/yara_scanner/1.0.0/config/locale.json b/yara_scanner/1.0.0/config/locale.json index 00a2fde..97130ad 100644 --- a/yara_scanner/1.0.0/config/locale.json +++ b/yara_scanner/1.0.0/config/locale.json @@ -89,6 +89,16 @@ "title": "Классы вредоносного ПО", "description": "Файл признается вредоносным, если он принадлежит к одному из классов" } + }, + "yara_process_caching_time": { + "en": { + "title": "Storage period for process scan results (in minutes)", + "description": "" + }, + "ru": { + "title": "Время хранения результатов сканирования процесса (в минутах)", + "description": "" + } } }, "secure_config": {}, @@ -225,6 +235,16 @@ "description": "Проверка процесса-объекта YARA-правилами в синхронном режиме" } }, + "yr_object_scan_proc_non_cached": { + "en": { + "title": "Scan an object process with YARA rules in priority order (do not use results from cache)", + "description": "Synchronous scanning of an object process with YARA rules. Results from cache will not be used" + }, + "ru": { + "title": "Проверить процесс-объект YARA-правилами в приоритетном порядке (не брать результаты из кэша)", + "description": "Проверка процесса-объекта YARA-правилами в синхронном режиме, результаты из кэша учитываться не будут" + } + }, "yr_object_task_scan_proc": { "en": { "title": "Start a scan task of an object process with YARA rules", @@ -255,6 +275,16 @@ "description": "Проверка процесса-субъекта YARA-правилами в синхронном режиме" } }, + "yr_subject_scan_proc_non_cached": { + "en": { + "title": "Scan a subject process with YARA rules in priority order (do not use results from cache)", + "description": "Synchronous scanning of a subject process with YARA rules. Results from cache will not be used" + }, + "ru": { + "title": "Проверить процесс-субъект YARA-правилами в приоритетном порядке (не брать результаты из кэша)", + "description": "Проверка процесса-субъекта YARA-правилами в синхронном режиме, результаты из кэша учитываться не будут" + } + }, "yr_subject_task_scan_proc": { "en": { "title": "Start a scan task of a subject process with YARA rules", @@ -387,6 +417,16 @@ "description": "" } }, + "yr_object_process_matched_high_cached": { + "en": { + "title": "[Cache] The module detected a malicious object process. Severity: high", + "description": "" + }, + "ru": { + "title": "[Кэш] Модуль обнаружил вредоносный процесс-объект. Уровень опасности: высокий", + "description": "" + } + }, "yr_object_process_matched_low": { "en": { "title": "The module detected a suspicious object process. Severity: low", @@ -397,6 +437,16 @@ "description": "" } }, + "yr_object_process_matched_low_cached": { + "en": { + "title": "[Cache] The module detected a suspicious object process. Severity: low", + "description": "" + }, + "ru": { + "title": "[Кэш] Модуль обнаружил подозрительный процесс-объект. Уровень опасности: низкий", + "description": "" + } + }, "yr_object_process_matched_medium": { "en": { "title": "The module detected a suspicious object process. Severity: medium", @@ -407,6 +457,16 @@ "description": "" } }, + "yr_object_process_matched_medium_cached": { + "en": { + "title": "[Cache] The module detected a suspicious object process. Severity: medium", + "description": "" + }, + "ru": { + "title": "[Кэш] Модуль обнаружил подозрительный процесс-объект. Уровень опасности: средний", + "description": "" + } + }, "yr_process_matched_custom": { "en": { "title": "The module detected a malicious process by custom rule", @@ -427,6 +487,16 @@ "description": "" } }, + "yr_subject_process_matched_high_cached": { + "en": { + "title": "[Cache] The module detected a malicious subject process. Severity: high", + "description": "" + }, + "ru": { + "title": "[Кэш] Модуль обнаружил вредоносный процесс-субъект. Уровень опасности: высокий", + "description": "" + } + }, "yr_subject_process_matched_low": { "en": { "title": "The module detected a suspicious subject process. Severity: low", @@ -437,6 +507,16 @@ "description": "" } }, + "yr_subject_process_matched_low_cached": { + "en": { + "title": "[Cache] The module detected a suspicious subject process. Severity: low", + "description": "" + }, + "ru": { + "title": "[Кэш] Модуль обнаружил подозрительный процесс-субъект. Уровень опасности: низкий", + "description": "" + } + }, "yr_subject_process_matched_medium": { "en": { "title": "The module detected a suspicious subject process. Severity: medium", @@ -446,13 +526,25 @@ "title": "Модуль обнаружил подозрительный процесс-субъект. Уровень опасности: средний", "description": "" } + }, + "yr_subject_process_matched_medium_cached": { + "en": { + "title": "[Cache] The module detected a suspicious subject process. Severity: medium", + "description": "" + }, + "ru": { + "title": "[Кэш] Модуль обнаружил подозрительный процесс-субъект. Уровень опасности: средний", + "description": "" + } } }, "action_config": { "yr_object_scan_proc": {}, + "yr_object_scan_proc_non_cached": {}, "yr_object_task_scan_proc": {}, "yr_scan_fs": {}, "yr_subject_scan_proc": {}, + "yr_subject_scan_proc_non_cached": {}, "yr_subject_task_scan_proc": {}, "yr_task_fastscan_fs": {}, "yr_task_fastscan_proc": {}, @@ -468,12 +560,18 @@ "yr_module_started": {}, "yr_module_stopped": {}, "yr_object_process_matched_high": {}, + "yr_object_process_matched_high_cached": {}, "yr_object_process_matched_low": {}, + "yr_object_process_matched_low_cached": {}, "yr_object_process_matched_medium": {}, + "yr_object_process_matched_medium_cached": {}, "yr_process_matched_custom": {}, "yr_subject_process_matched_high": {}, + "yr_subject_process_matched_high_cached": {}, "yr_subject_process_matched_low": {}, - "yr_subject_process_matched_medium": {} + "yr_subject_process_matched_low_cached": {}, + "yr_subject_process_matched_medium": {}, + "yr_subject_process_matched_medium_cached": {} }, "tags": { "responder": {