From e30f2d61e030dfe19389785bcdd692f5d53d4bda Mon Sep 17 00:00:00 2001 From: Karl Hiramoto Date: Tue, 29 Sep 2026 08:45:00 +0000 Subject: [PATCH] fix(behavior): fix Summary read_files None bug and improve Enhanced registry write tracking (#3261) - Fix Summary.event_apicall to pass (srcfilename or filename) instead of srcfilename (which is None for file_read) when recording read_files and per-process file_activities["read_files"]. - Include NtSetValueKey, RegSetValueA, and RegSetValueW in Enhanced registry write API list alongside RegSetValueExA/W. - Only emit Enhanced registry write events for RegCreateKeyExA/W when Disposition == 1 (REG_CREATED_NEW_KEY), ignoring Disposition == 2 (REG_OPENED_EXISTING_KEY). - Add unit tests in tests/test_behavior.py covering Summary read_files and Enhanced registry writes. --- modules/processing/behavior.py | 17 +++++-- tests/test_behavior.py | 81 +++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 5 deletions(-) diff --git a/modules/processing/behavior.py b/modules/processing/behavior.py index 633b888cade..f22ce9fbaa3 100644 --- a/modules/processing/behavior.py +++ b/modules/processing/behavior.py @@ -779,9 +779,8 @@ def event_apicall(self, call, process): and (access & 0x80000000 or access & 0x10000000 or access & 0x02000000 or access & 0x1) and filename not in self.read_files ): - # self.read_files.append(filename) - self._filtering_helper(self.read_files, srcfilename) - self._add_file_activity(process, "read_files", srcfilename) + self._filtering_helper(self.read_files, srcfilename or filename) + self._add_file_activity(process, "read_files", srcfilename or filename) if ( access and (access & 0x40000000 or access & 0x10000000 or access & 0x02000000 or access & 0x6) @@ -947,7 +946,13 @@ def __init__(self, details=False): { "event": "write", "object": "registry", - "apis": ["RegSetValueExA", "RegSetValueExW"], + "apis": [ + "RegSetValueExA", + "RegSetValueExW", + "NtSetValueKey", + "RegSetValueA", + "RegSetValueW", + ], "args": [("regkey", "FullName"), ("content", "Buffer")], }, { @@ -1042,6 +1047,10 @@ def _get_service_action(control_code): item = self.api_map.get(call["api"]) if item: args = _load_args(call) + if call["api"] in ("RegCreateKeyExA", "RegCreateKeyExW"): + disposition = args.get("Disposition") + if disposition is not None and int(disposition) != 1: + return None self.eid += 1 event = { diff --git a/tests/test_behavior.py b/tests/test_behavior.py index f5165075d63..fb0daf8c000 100644 --- a/tests/test_behavior.py +++ b/tests/test_behavior.py @@ -3,7 +3,8 @@ # See the file 'docs/LICENSE' for copying permission. from lib.cuckoo.common.config import Config -from modules.processing.behavior import ParseProcessLog +from lib.cuckoo.common.dictionary import Dictionary +from modules.processing.behavior import Enhanced, ParseProcessLog, Summary cfg = Config("processing") @@ -14,3 +15,81 @@ def test_init(self): str(ParseProcessLog("CAPEv2/tests/test_bson.bson", cfg.behavior)) == "" ) + + +class TestSummaryAndEnhanced: + def test_summary_read_files_and_file_activities(self): + options = Dictionary({"replace_patterns": False, "file_activities": True}) + summary = Summary(options=options) + process = { + "process_id": 2256, + "file_activities": { + "read_files": [], + "write_files": [], + "delete_files": [], + }, + } + call = { + "api": "NtOpenFile", + "category": "filesystem", + "status": True, + "arguments": [ + {"name": "FileHandle", "value": "0x000002cc"}, + {"name": "DesiredAccess", "value": "0x00100021"}, + {"name": "FileName", "value": r"C:\Users\Bruno\AppData\Local\Temp\data.bin"}, + {"name": "ShareAccess", "value": "5"}, + ], + } + summary.event_apicall(call, process) + result = summary.run() + assert r"C:\Users\Bruno\AppData\Local\Temp\data.bin" in result["read_files"] + assert r"C:\Users\Bruno\AppData\Local\Temp\data.bin" in process["file_activities"]["read_files"] + + def test_enhanced_registry_writes_and_disposition(self): + enhanced = Enhanced() + + nt_set_call = { + "api": "NtSetValueKey", + "category": "registry", + "timestamp": "2026-09-29 13:42:00,360", + "arguments": [ + { + "name": "FullName", + "value": r"HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\Cache\Cookies\CachePrefix", + }, + {"name": "Buffer", "value": "Cookie:"}, + ], + } + ev = enhanced._process_call(nt_set_call) + assert ev is not None + assert ev["event"] == "write" + assert ev["object"] == "registry" + assert ev["data"]["content"] == "Cookie:" + + # RegCreateKeyExW with Disposition=2 (REG_OPENED_EXISTING_KEY) should not be treated as a write + open_existing_call = { + "api": "RegCreateKeyExW", + "category": "registry", + "timestamp": "2026-09-29 13:42:00,400", + "arguments": [ + {"name": "FullName", "value": r"HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SecurityProviders\Schannel"}, + {"name": "Disposition", "value": "2"}, + ], + } + assert enhanced._process_call(open_existing_call) is None + + # RegCreateKeyExW with Disposition=1 (REG_CREATED_NEW_KEY) is recorded as a write + create_new_call = { + "api": "RegCreateKeyExW", + "category": "registry", + "timestamp": "2026-09-29 13:42:00,410", + "arguments": [ + {"name": "FullName", "value": r"HKEY_CURRENT_USER\Software\NewMalwareKey"}, + {"name": "Disposition", "value": "1"}, + ], + } + ev_create = enhanced._process_call(create_new_call) + assert ev_create is not None + assert ev_create["event"] == "write" + assert ev_create["data"]["regkey"] == r"HKEY_CURRENT_USER\Software\NewMalwareKey" +