Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/cuckoo/common/demux.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
62 changes: 48 additions & 14 deletions lib/cuckoo/common/integrations/pyinstxtractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
import struct
import sys
import zlib
import urllib.parse
from contextlib import suppress
from uuid import uuid4 as uniquename

Expand Down Expand Up @@ -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())
Expand All @@ -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")
Expand All @@ -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)

Expand All @@ -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):
Expand All @@ -290,11 +318,10 @@ 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
Expand All @@ -310,16 +337,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()
Expand All @@ -331,7 +358,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
Expand All @@ -344,6 +372,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"
Expand Down Expand Up @@ -407,14 +436,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)

Expand Down
Loading