diff --git a/modules/signatures/windows/ransomware_asynchronous_encryption.py b/modules/signatures/windows/ransomware_asynchronous_encryption.py new file mode 100644 index 00000000..ec46857d --- /dev/null +++ b/modules/signatures/windows/ransomware_asynchronous_encryption.py @@ -0,0 +1,60 @@ +# Copyright (C) 2026 Kevin Ross, identification of the behavior covered in this signature and some of the signature was generated by Gemini +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from lib.cuckoo.common.abstracts import Signature + +class RansomwareIOCPAsynchronousEncryption(Signature): + name = "ransomware_iocp_asynchronous_encryption" + description = "Binds a large number of files to I/O Completion Ports (IOCP), possible ransomware asynchronous encryption" + severity = 4 + confidence = 80 + categories = ["ransomware"] + authors = ["Kevin Ross", "Gemini"] + minimum = "1.3" + evented = True + ttps = ["T1486"] + + filter_apinames = {"NtSetInformationFile"} + + def __init__(self, *args, **kwargs): + Signature.__init__(self, *args, **kwargs) + self.iocp_files = set() + self.example_file = None + + def on_call(self, call, process): + info_class = self.get_argument(call, "FileInformationClass") + + if info_class == 30 or str(info_class) == "30" or str(info_class).upper() == "FILECOMPLETIONINFORMATION": + filepath = self.get_argument(call, "HandleName") + + if isinstance(filepath, str) and "\\" in filepath: + filepath_lower = filepath.lower() + + # Ignore system devices/pipes + if "\\??\\" in filepath_lower or "\\device\\" in filepath_lower: + return + + if filepath_lower not in self.iocp_files: + self.iocp_files.add(filepath_lower) + if len(self.iocp_files) <= 15: + self.mark_call() + + def on_complete(self): + if len(self.iocp_files) > 50: + self.data.append({ + "total_files_bound_to_iocp": len(self.iocp_files), + }) + return True + return False