From 961dd3cbb8feb36915ca0d0974565dbf83d0ac1a Mon Sep 17 00:00:00 2001 From: doomedraven Date: Mon, 20 Jul 2026 11:21:38 +0200 Subject: [PATCH 1/3] Exclude MSI Installer from office file check --- lib/cuckoo/common/demux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cuckoo/common/demux.py b/lib/cuckoo/common/demux.py index 5275b721593..ff5109d2e75 100644 --- a/lib/cuckoo/common/demux.py +++ b/lib/cuckoo/common/demux.py @@ -487,7 +487,7 @@ def demux_sample( magic = File(filename).get_type() or "" # --- 3. Handle Password-Protected Office Files --- - is_office = "Microsoft" in magic or any(x in magic for x in OFFICE_TYPES) + is_office = ("Microsoft" in magic or any(x in magic for x in OFFICE_TYPES)) and "MSI Installer" not in magic if is_office and use_sflock: password = options2passwd(options) if HAS_SFLOCK and password: From 7939b453cace4e5cf7f2ca89df4b2272e9bb1b60 Mon Sep 17 00:00:00 2001 From: doomedraven Date: Mon, 20 Jul 2026 15:06:07 +0200 Subject: [PATCH 2/3] Enhance file handling and add URL decoding Added URL decoding for file names and improved file handling with unique file descriptors to prevent overwriting. Enhanced error handling during directory creation and file extraction. --- .../common/integrations/pyinstxtractor.py | 63 ++++++++++++++----- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/lib/cuckoo/common/integrations/pyinstxtractor.py b/lib/cuckoo/common/integrations/pyinstxtractor.py index 497fdee37ef..130d5a8cc4e 100644 --- a/lib/cuckoo/common/integrations/pyinstxtractor.py +++ b/lib/cuckoo/common/integrations/pyinstxtractor.py @@ -89,6 +89,7 @@ import struct import sys import zlib +import urllib.parse from contextlib import suppress from uuid import uuid4 as uniquename @@ -226,9 +227,17 @@ def parseTOC(self): log.warning("[!] File name %s contains invalid bytes. Using random name %s", name, newName) name = newName + while "%" in name: + new_name = urllib.parse.unquote(name) + if new_name == name: + break + name = new_name + # Prevent writing outside the extraction directory if name.startswith("/"): name = name.lstrip("/") + name = name.replace("\\", "/") + name = name.replace("..", "__") if len(name) == 0: name = str(uniquename()) @@ -241,14 +250,30 @@ def parseTOC(self): parsedLen += entrySize log.info("[+] Found %d files in CArchive", len(self.tocList)) + def _get_unique_fd(self, filepath): + flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY + current_path = filepath + while True: + try: + fd = os.open(current_path, flags) + return fd, current_path + except FileExistsError: + base, ext = os.path.splitext(filepath) + current_path = base + "_" + str(uniquename()) + ext + def _writeRawData(self, filepath, data): nm = filepath.replace("\\", os.path.sep).replace("/", os.path.sep).replace("..", "__") nmDir = os.path.dirname(nm) if nmDir != "" and not os.path.exists(nmDir): # Check if path exists, create if not - os.makedirs(nmDir) + try: + os.makedirs(nmDir) + except FileExistsError: + pass - with open(nm, "wb") as f: + fd, final_filename = self._get_unique_fd(nm) + with os.fdopen(fd, "wb") as f: f.write(data) + return final_filename def extractFiles(self): log.debug("[+] Beginning extraction...please standby") @@ -260,7 +285,10 @@ def extractFiles(self): # os.chdir(extractionDir) for entry in self.tocList: - destination_entry = os.path.join(self.destination_folder, entry.name) + destination_entry = os.path.abspath(os.path.join(self.destination_folder, entry.name)) + if not destination_entry.startswith(os.path.abspath(self.destination_folder) + os.sep): + log.warning("[!] Path traversal attempt detected. Skipping %s", entry.name) + continue self.fPtr.seek(entry.position, os.SEEK_SET) data = self.fPtr.read(entry.cmprsdDataSize) @@ -280,7 +308,7 @@ def extractFiles(self): # These are runtime options, not files continue - basePath = os.path.dirname(entry.name) + basePath = os.path.dirname(destination_entry) if basePath != "": # Check if path exists, create if not if not os.path.exists(basePath): @@ -290,11 +318,11 @@ def extractFiles(self): # s -> ARCHIVE_ITEM_PYSOURCE # Entry point are expected to be python scripts log.info("[+] Possible entry point: %s.pyc", entry.name) - + + final_filename = self._writePyc(destination_entry + ".pyc", data) if self.pycMagic == b"\0" * 4: # if we don't have the pyc header yet, fix them in a later pass - self.barePycList.append(destination_entry + ".pyc") - self._writePyc(destination_entry + ".pyc", data) + self.barePycList.append(final_filename) elif entry.typeCmprsData == (b"M", b"m") and not self.only_entrypoints: # M -> ARCHIVE_ITEM_PYPACKAGE @@ -310,16 +338,16 @@ def extractFiles(self): self._writeRawData(destination_entry + ".pyc", data) else: # >= pyinstaller 5.3 + final_filename = self._writePyc(destination_entry + ".pyc", data) if self.pycMagic == b"\0" * 4: # if we don't have the pyc header yet, fix them in a later pass - self.barePycList.append(destination_entry + ".pyc") - self._writePyc(destination_entry + ".pyc", data) + self.barePycList.append(final_filename) else: if not self.only_entrypoints: - self._writeRawData(destination_entry, data) + final_filename = self._writeRawData(destination_entry, data) if entry.typeCmprsData in (b"z", b"Z"): - self._extractPyz(destination_entry) + self._extractPyz(final_filename) # Fix bare pyc's if any self._fixBarePycs() @@ -331,7 +359,8 @@ def _fixBarePycs(self): pycFile.write(self.pycMagic) def _writePyc(self, filename, data): - with open(filename, "wb") as pycFile: + fd, final_filename = self._get_unique_fd(filename) + with os.fdopen(fd, "wb") as pycFile: pycFile.write(self.pycMagic) # pyc magic if self.pymaj >= 3 and self.pymin >= 7: # PEP 552 -- Deterministic pycs @@ -344,6 +373,7 @@ def _writePyc(self, filename, data): pycFile.write(b"\0" * 4) # Size parameter added in Python 3.3 pycFile.write(data) + return final_filename def _extractPyz(self, name): dirName = name + "_extracted" @@ -407,14 +437,19 @@ def _extractPyz(self, name): fileDir = os.path.dirname(filePath) if not os.path.exists(fileDir): - os.makedirs(fileDir) + try: + os.makedirs(fileDir) + except FileExistsError: + pass try: data = f.read(length) data = zlib.decompress(data) except Exception as e: print("[!] Error: Failed to decompress %s, probably encrypted. Extracting as is. Error: %s", filePath, str(e)) - open(filePath + ".encrypted", "wb").write(data) + fd, final_enc_filename = self._get_unique_fd(filePath + ".encrypted") + with os.fdopen(fd, "wb") as f_enc: + f_enc.write(data) else: self._writePyc(filePath, data) From f73e577cd7107ffdfb5adc51eca8656f0a1c6027 Mon Sep 17 00:00:00 2001 From: doomedraven Date: Mon, 20 Jul 2026 15:19:45 +0200 Subject: [PATCH 3/3] Update pyinstxtractor.py --- lib/cuckoo/common/integrations/pyinstxtractor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/cuckoo/common/integrations/pyinstxtractor.py b/lib/cuckoo/common/integrations/pyinstxtractor.py index 130d5a8cc4e..53e69fbf103 100644 --- a/lib/cuckoo/common/integrations/pyinstxtractor.py +++ b/lib/cuckoo/common/integrations/pyinstxtractor.py @@ -318,7 +318,6 @@ def extractFiles(self): # s -> ARCHIVE_ITEM_PYSOURCE # Entry point are expected to be python scripts log.info("[+] Possible entry point: %s.pyc", entry.name) - final_filename = self._writePyc(destination_entry + ".pyc", data) if self.pycMagic == b"\0" * 4: # if we don't have the pyc header yet, fix them in a later pass