Skip to content
Open
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
3 changes: 3 additions & 0 deletions analyzer/windows/modules/packages/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ def start(self, path):
log.warning("Couldn't copy %s to root of C: %s", d, str(e))

file_name = self.options.get(OPT_FILE)
if file_name:
file_name = file_name.replace("/", "\\\\")

# If no file name is provided via option, discover files to execute.
if not file_name:
ret_list = []
Expand Down
3 changes: 3 additions & 0 deletions analyzer/windows/modules/packages/rar.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ def start(self, path):
self.extract_rar(path, root, password)

file_name = self.options.get(OPT_FILE)
if file_name:
file_name = file_name.replace("/", "\\\\")

# If no file name is provided via option, take the first file.
if not file_name:
# If no file names to choose from, bail
Expand Down
3 changes: 3 additions & 0 deletions analyzer/windows/modules/packages/zip.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ def start(self, path):
extract_archive(seven_zip_path, nested_7z, root, password, try_multiple_passwords)

file_name = self.options.get(OPT_FILE)
if file_name:
file_name = file_name.replace("/", "\\\\")

# If no file name is provided via option, discover files to execute.
if not file_name:
# If no file names to choose from, bail
Expand Down
3 changes: 3 additions & 0 deletions analyzer/windows/modules/packages/zip_compound.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@

# Enforce the requirement of having a specified file. No guessing.
target_file = target_file or self.options.get(OPT_FILE)
if target_file:
target_file = target_file.replace("/", "\\\\")

Check failure on line 79 in analyzer/windows/modules/packages/zip_compound.py

View workflow job for this annotation

GitHub Actions / test (3.10)

Ruff (W293)

analyzer/windows/modules/packages/zip_compound.py:79:1: W293 Blank line contains whitespace
if not target_file:
raise CuckooPackageError("File must be specified in the JSON or the web submission UI!")

Expand Down
2 changes: 2 additions & 0 deletions conf/default/processing.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ targetinfo = yes
dropped = yes
# Ex procdump standalone module
procdump = yes
# Automatically rebuild and reconstruct .NET PE headers for dumped assemblies to allow clean dnSpy parsing
dotnet_rebuild = yes
# Amount of text to carve from plaintext files (bytes)
buffer = 8192
# Process files not bigger than value below in Mb. We saw that after 90Mb it has biggest delay
Expand Down
6 changes: 4 additions & 2 deletions lib/cuckoo/common/demux.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,8 @@ def demux_sflock(
# Find interesting files (exe/dll) and submit the ORIGINAL archive
# with instructions to run specifically those files.
execs = find_payload_to_run(unpacked.filepaths)
submit_opts = [f"file={runable}" for runable in execs]
execs_fixed = [r.replace("/", "\\\\") for r in execs]
submit_opts = [f"file={runable}" for runable in execs_fixed]
# returning empty retlist so it will use parent file
return [], "", submit_opts

Expand All @@ -388,7 +389,8 @@ def demux_sflock(
extracted = _sf_children(current_child)
path = extracted[0]
if path:
submit_opts += [f"file={runable}" for runable in execs]
execs_fixed = [r.replace("/", "\\\\") for r in execs]
submit_opts += [f"file={runable}" for runable in execs_fixed]
retlist.append(extracted)
else:
# It's just a single regular file (e.g., malware.exe inside a zip).
Expand Down
62 changes: 62 additions & 0 deletions lib/cuckoo/common/dotnet_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,65 @@ def dotnet_user_strings(file: str = False, data: bytes = False, dn_whitelisting:

dn.close()
return dn_strings

import struct

def rebuild_dotnet_pe(data: bytes) -> bytes:
"""
Rebuilds a .NET PE file dumped from memory (Memory Layout).
Fixes section alignments and attempts to restore missing .NET headers
by locating the BSJB metadata signature.
"""
try:
import pefile
except ImportError:
return data

try:
pe = pefile.PE(data=data, fast_load=False)
except pefile.PEFormatError:
return data

modified = bytearray(data)

# 1. Align Sections (Memory to File Layout conversion)
# Match FileAlignment to SectionAlignment
pe.OPTIONAL_HEADER.FileAlignment = pe.OPTIONAL_HEADER.SectionAlignment

# Update Section Headers
for section in pe.sections:
section.PointerToRawData = section.VirtualAddress
# SizeOfRawData should be VirtualSize aligned to FileAlignment
alignment_mask = pe.OPTIONAL_HEADER.FileAlignment - 1
section.SizeOfRawData = (section.Misc_VirtualSize + alignment_mask) & ~alignment_mask

# Overwrite physical bytes in header
struct.pack_into("<I", modified, section.get_file_offset() + 16, section.SizeOfRawData)
struct.pack_into("<I", modified, section.get_file_offset() + 20, section.PointerToRawData)

# Overwrite FileAlignment in the optional header
struct.pack_into("<I", modified, pe.OPTIONAL_HEADER.get_file_offset() + 36, pe.OPTIONAL_HEADER.FileAlignment)

# 2. Restore .NET Data Directory if missing
dotnet_dir_index = 14 # IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR
if dotnet_dir_index < len(pe.OPTIONAL_HEADER.DATA_DIRECTORY):
dotnet_dir = pe.OPTIONAL_HEADER.DATA_DIRECTORY[dotnet_dir_index]
if dotnet_dir.VirtualAddress == 0 or dotnet_dir.Size == 0:
bsjb_offset = modified.find(b"BSJB")
if bsjb_offset != -1:
# Naive backwards scan for IMAGE_COR20_HEADER (cb=0x48, Major=2, Minor=5, MetaData=bsjb_offset)
metadata_rva = bsjb_offset
search_pattern = struct.pack("<IHHUI", 0x48, 2, 5, metadata_rva, 0)
cor20_rva = 0

search_start = max(0, bsjb_offset - 1024)
for i in range(bsjb_offset, search_start, -1):
if modified[i:i+12] == search_pattern[:12]:
cor20_rva = i
break

if cor20_rva != 0:
dir_offset = dotnet_dir.get_file_offset()
struct.pack_into("<II", modified, dir_offset, cor20_rva, 0x48)

return bytes(modified)
31 changes: 30 additions & 1 deletion modules/processing/CAPE.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,34 @@ def process_file(self, file_path, append_file, metadata: dict, *, category: str,

cape_names = set()
buf_size = self.options.get("buffer", 8192)
# ToDo filename argument for procdump

type_string = ""
metastrings = metadata.get("metadata", "").split(";?")
if len(metastrings) > 0 and metastrings[0].isdigit() and int(metastrings[0]) == TYPE_STRING:
if len(metastrings) > 4:
type_string = metastrings[3]

# Optimize to not load all if duplicated, it stores sha256 in file object
if processing_conf.CAPE.get("dotnet_rebuild", False) and category in ("procdump", "dropped"):
if ".NET" in type_string:
try:
import os
file_size_limit = processing_conf.CAPE.get("max_file_size", 90) * 1024 * 1024
if os.path.getsize(file_path) < file_size_limit:
with open(file_path, "rb") as f_in:
pe_data = f_in.read()

# Apply synthetic memory layout alignment and BSJB headers
if pe_data.startswith(b"MZ") and b"BSJB" in pe_data:
from lib.cuckoo.common.dotnet_utils import rebuild_dotnet_pe
rebuilt_data = rebuild_dotnet_pe(pe_data)
if rebuilt_data and rebuilt_data != pe_data:
with open(file_path, "wb") as f_out:
f_out.write(rebuilt_data)
log.debug("Successfully dynamically rebuilt .NET PE headers for %s", file_path)
except Exception as e:
log.error("Failed to execute .NET PE rebuilder on %s: %s", file_path, str(e))

f = File(file_path, metadata.get("metadata", ""))
sha256 = f.get_sha256()

Expand Down Expand Up @@ -212,6 +237,10 @@ def process_file(self, file_path, append_file, metadata: dict, *, category: str,
options_match = db_file.get("options_hash", "") == options_hash
file_info = db_file
cached = True

# we still need append flag
type_string, append_file = self._metadata_processing(metadata, file_info, append_file)

if yara_match and options_match:
run_static = False
if HAVE_VIRUSTOTAL:
Expand Down
Loading