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
Binary file modified analyzer/windows/dll/capemon.dll
Binary file not shown.
Binary file modified analyzer/windows/dll/capemon_x64.dll
Binary file not shown.
3 changes: 2 additions & 1 deletion analyzer/windows/lib/common/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@
OPT_SERVICEDESC = "servicedesc"
OPT_RUNASX86 = "runasx86"
OPT_UNPACKER = "unpacker"
OPT_RECURSION_DEPTH = "recursion_depth"

ARCHIVE_OPTIONS = (OPT_FILE, OPT_PASSWORD)
ARCHIVE_OPTIONS = (OPT_FILE, OPT_PASSWORD, OPT_RECURSION_DEPTH)
DLL_OPTIONS = (OPT_ARGUMENTS, OPT_DLLLOADER, OPT_FUNCTION)
SERVICE_OPTIONS = (OPT_SERVICENAME, OPT_SERVICEDESC, OPT_ARGUMENTS)

Expand Down
9 changes: 7 additions & 2 deletions analyzer/windows/lib/common/zip_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def extract_archive(seven_zip_path, archive_path, extract_path, password="infect
stdin=subprocess.DEVNULL,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
check=False
)
stdoutput, stderr = p.stdout, p.stderr
log.debug("%s %s", p.stdout, p.stderr)
Expand All @@ -76,6 +77,7 @@ def extract_archive(seven_zip_path, archive_path, extract_path, password="infect
stdin=subprocess.DEVNULL,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
check=False
)
stdoutput, stderr = p.stdout, p.stderr
log.debug("%s - %s", p.stdout, p.stderr)
Expand Down Expand Up @@ -107,9 +109,11 @@ def get_file_names(seven_zip_path, archive_path):
stdin=subprocess.DEVNULL,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
encoding="utf-8",
errors="replace",
check=False
)
stdoutput = p.stdout.decode("utf-8", errors="replace")
stdoutput_lines = stdoutput.splitlines()
stdoutput_lines = p.stdout.splitlines()
in_table = False
items_under_header = False
file_names = []
Expand Down Expand Up @@ -264,6 +268,7 @@ def winrar_extractor(winrar_binary, extract_path, archive_path):
stdin=subprocess.DEVNULL,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
check=False
)
# stdoutput, stderr = p.stdout, p.stderr
log.debug("%s - %s", p.stdout, p.stderr)
Expand Down
63 changes: 59 additions & 4 deletions analyzer/windows/modules/packages/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import logging
import os
import shutil
import subprocess
from pathlib import Path

from lib.common.abstracts import Package
Expand All @@ -16,6 +17,7 @@
OPT_FUNCTION,
OPT_MULTI_PASSWORD,
OPT_PASSWORD,
OPT_RECURSION_DEPTH,
)
from lib.common.exceptions import CuckooPackageError
from lib.common.zip_utils import (
Expand Down Expand Up @@ -44,6 +46,7 @@ class Archive(Package):
("SystemRoot", "system32", "xpsrchvw.exe"),
("ProgramFiles", "7-Zip", "7z.exe"),
("ProgramFiles", "WinRAR", "WinRAR.exe"),
("ProgramFiles", "die", "diec.exe"),
("ProgramFiles", "Microsoft Office", "WINWORD.EXE"),
("ProgramFiles", "Microsoft Office", "Office*", "WINWORD.EXE"),
("ProgramFiles", "Microsoft Office*", "root", "Office*", "WINWORD.EXE"),
Expand All @@ -65,6 +68,8 @@ class Archive(Package):
Various options apply depending on the file type.
The options '{OPT_FUNCTION}' and '{OPT_DLLLOADER}' will be applied to .DLL execution attempts.
The option '{OPT_ARGUMENTS}' will be applied to a .DLL or a PE executable.
For recursive extraction guest Windows VM must contain die app (Detect It Easy) with extra
database in Program Files.
"""
option_names = sorted(set(DLL_OPTIONS + ARCHIVE_OPTIONS + (OPT_MULTI_PASSWORD,)))

Expand All @@ -76,6 +81,8 @@ def start(self, path):
seven_zip_path = self.get_path_app_in_path("7z.exe")
password = self.options.get(OPT_PASSWORD, "infected")
archive_name = Path(path).name
recursion_depth = max(0, int(self.options.get(OPT_RECURSION_DEPTH, 0)))
diec_path = self.get_path_app_in_path("diec.exe") if recursion_depth > 0 else None

# We are extracting the archive to C:\\<archive_name> rather than the TEMP directory because
# actors are using LNK files that use relative directory traversal at arbitrary depth.
Expand All @@ -102,15 +109,63 @@ def start(self, path):
if not file_names:
raise CuckooPackageError("Empty archive")

extracted_files = set()
extracted_archives = set()
for i in range(0, recursion_depth):

packs = []
target_words = {'Archive', 'Image', 'filesystem', 'ZIP', 'RAR', 'SFX'}

for r, _, files in os.walk(root):

for file in files:
file_path = os.path.join(r, file)
if file_path == path or file_path in extracted_files:
continue

extracted_files.add(file_path)

try:
result = subprocess.run([diec_path, "-p", file_path], capture_output=True, text=True, check=True, encoding="utf-8")
file_info = result.stdout
for word in target_words:
if word in file_info:
packs.append(file_path)
break
except subprocess.CalledProcessError:
continue

if packs:
j = 0
for p in packs:
pack_name = os.path.basename(p)
output_dir = os.path.join(root, str(i), str(j), pack_name)
os.makedirs(output_dir, exist_ok=True)

try:
try_multiple_passwords = attempt_multiple_passwords(self.options, password)
extract_archive(seven_zip_path, p, output_dir, password, try_multiple_passwords)
extracted_archives.add(p)
except Exception as e:
log.warning("Extraction failed for %s: %s", p, e)

j += 1
else:
break

# Handle special characters that 7ZIP cannot
# We have the file names according to 7ZIP output (file_names)
# We have the file names that were actually extracted (files at root)
# If these values are different, replace all
files_at_root = [os.path.join(r, f).replace(f"{root}\\", "") for r, _, files in os.walk(root) for f in files]
log.warning("Extracted archives:%s", extracted_archives)
files_at_root = [
os.path.relpath(os.path.join(r, f), root)
for r, _, files in os.walk(root)
for f in files
if os.path.join(r, f) != path and os.path.join(r, f) not in extracted_archives
]
log.debug(files_at_root)
if set(file_names) != set(files_at_root):
log.debug("Replacing %s with %s", str(file_names), str(files_at_root))
file_names = files_at_root
file_names = files_at_root

upload_extracted_files(root, files_at_root)

Expand Down
6 changes: 6 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
### [05.06.2026]
* Monitor updates:
* NtCreateUserProcess hook: Dynamically patch ping commandline to thwart ping delays (e.g. Formbook/Xloader)
* Debugger: Persistent software breakpoints via softbpmode=1 (default is one-shot)
* TLS capture improvements

### [01.06.2026]
* Monitor update: Fix standalone mode broken since August

Expand Down
2 changes: 1 addition & 1 deletion tests/web/test_submission_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def test_get_lib_common_constants(self):
actual = get_lib_common_constants(platform="windows")
self.assertIsInstance(actual, dict)
self.assertEqual("runasx86", actual["OPT_RUNASX86"])
self.assertCountEqual(("file", "password"), actual["ARCHIVE_OPTIONS"])
self.assertCountEqual(("file", "password", "recursion_depth"), actual["ARCHIVE_OPTIONS"])
self.assertCountEqual(("arguments", "dllloader", "function"), actual["DLL_OPTIONS"])
self.assertIn("SystemDrive", actual["TRUSTED_PATH_TEXT"])
self.assertIn("SystemDrive", actual["MSOFFICE_TRUSTED_PATH"])
Expand Down
4 changes: 4 additions & 0 deletions web/templates/submission/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,10 @@ <h5 class="mb-0 text-white"><i class="fas fa-cogs me-2 text-primary"></i>Advance
<td class="text-end"><code>startup-time</code></td>
<td>MS since system startup</td>
</tr>
<tr>
<td class="text-end"><code>recursion_depth</code></td>
<td>Depth of archive extraction (nested archives)</td>
</tr>
</tbody>
</table>
</div>
Expand Down
Loading