Skip to content

Commit

Permalink
Merge branch 'kevoreilly:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
dsecuma authored Sep 30, 2024
2 parents ed5a8b4 + 66e5b08 commit 3244cd2
Show file tree
Hide file tree
Showing 49 changed files with 3,617 additions and 2,720 deletions.
30 changes: 28 additions & 2 deletions agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,20 @@
import multiprocessing
import os
import platform
import random
import shlex
import shutil
import socket
import socketserver
import stat
import string
import subprocess
import sys
import tempfile
import time
import traceback
from io import StringIO
from threading import Lock
from typing import Iterable
from zipfile import ZipFile

Expand All @@ -41,7 +44,7 @@
if sys.maxsize > 2**32 and sys.platform == "win32":
sys.exit("You should install python3 x86! not x64")

AGENT_VERSION = "0.17"
AGENT_VERSION = "0.18"
AGENT_FEATURES = [
"execpy",
"execute",
Expand All @@ -54,6 +57,7 @@

if sys.platform == "win32":
AGENT_FEATURES.append("mutex")
AGENT_FEATURES.append("browser_extension")
MUTEX_TIMEOUT_MS = 500
from ctypes import WinError, windll

Expand Down Expand Up @@ -89,6 +93,8 @@ def _missing_(cls, value):
return None


AGENT_BROWSER_EXT_PATH = ""
AGENT_BROWSER_LOCK = Lock()
ANALYZER_FOLDER = ""
agent_mutexes = {}
"""Holds handles of mutexes held by the agent."""
Expand Down Expand Up @@ -196,7 +202,7 @@ def handle(self, obj):
if "client_ip" in state and request.client_ip != state["client_ip"]:
if request.client_ip != "127.0.0.1":
return
if obj.path != "/status" or request.method != "POST":
if obj.path not in ["/status", "/browser_extension"] or request.method != "POST":
return

for route, fn in self.routes[obj.command]:
Expand Down Expand Up @@ -753,6 +759,26 @@ def do_execpy():
return json_exception(f"Error executing Python command: {ex}")


@app.route("/browser_extension", methods=["POST"])
def do_browser_ext():
global AGENT_BROWSER_EXT_PATH
AGENT_BROWSER_LOCK.acquire()
if not AGENT_BROWSER_EXT_PATH:
try:
ext_tmpdir = tempfile.mkdtemp(prefix="tmp")
except Exception:
AGENT_BROWSER_LOCK.release()
return json_exception("Error creating temporary directory")
ext_filepath = "bext_" + "".join(random.choice(string.ascii_letters) for _ in range(11)) + ".json"
AGENT_BROWSER_EXT_PATH = os.path.join(ext_tmpdir, ext_filepath)
network_data = request.form.get("networkData")
if network_data:
with open(AGENT_BROWSER_EXT_PATH, "w") as ext_fd:
ext_fd.write(network_data)
AGENT_BROWSER_LOCK.release()
return json_success("OK")


@app.route("/pinning")
def do_pinning():
if "client_ip" in state:
Expand Down
31 changes: 10 additions & 21 deletions analyzer/windows/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,45 +496,34 @@ def run(self):
# Initialize Auxiliary modules
Auxiliary()
prefix = f"{auxiliary.__name__}."

# disable_screens = True
# if self.options.get("disable_screens") == "0":
# disable_screens = False
windows_modules = ("human", "screenshots", "sysmon")

for _, name, _ in pkgutil.iter_modules(auxiliary.__path__, prefix):
try:
log.debug('Importing auxiliary module "%s"...', name)
__import__(name, globals(), locals(), ["dummy"])
# log.debug('Imported auxiliary module "%s"', name)
mod_name = name.split(".")[-1]
if mod_name in windows_modules:
mod_name += "_windows"
# if hasattr(self.config, mod_name) and getattr(self.config, mod_name, False):
# log.debug('Imported auxiliary module "%s"', name)
except ImportError as e:
log.warning('Unable to import the auxiliary module "%s": %s', name, e)

# Walk through the available auxiliary modules.
aux_modules = []

for module in sorted(Auxiliary.__subclasses__(), key=lambda x: x.start_priority, reverse=True):
# Try to start the auxiliary module.
# if module.__name__ == "Screenshots" and disable_screens:
# continue
try:
aux = module(self.options, self.config)
log.debug('Initialized auxiliary module "%s"', module.__name__)
aux_modules.append(aux)

# The following commented out code causes the monitor to not upload logs.
# If the auxiliary module is not enabled, we shouldn't start it
# if hasattr(aux, "enabled") and not getattr(aux, "enabled", False):
# log.debug('Auxiliary module "%s" is disabled.', module.__name__)
# # We continue so that the module is not added to AUX_ENABLED
# continue
# else:
log.debug('Trying to start auxiliary module "%s"...', module.__name__)
log.debug('Trying to start auxiliary module "%s"...', module.__module__)
aux.start()
except (NotImplementedError, AttributeError) as e:
log.warning("Auxiliary module %s was not implemented: %s", module.__name__, e)
except Exception as e:
log.warning("Cannot execute auxiliary module %s: %s", module.__name__, e)
log.warning("Cannot execute auxiliary module %s: %s", module.__module__, e)
else:
log.debug("Started auxiliary module %s", module.__name__)
log.debug("Started auxiliary module %s", module.__module__)
AUX_ENABLED.append(aux)

"""
Expand Down
12 changes: 12 additions & 0 deletions analyzer/windows/data/yara/SlowLoader.yar
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
rule SlowLoader
{
meta:
author = "kevoreilly"
description = "SlowLoader detonation aide for slow cpus (thread race)"
cape_options = "break-on-return=CreateProcessA,action0=sleep:1000,count=0"
packed = "f6eeb73ffb3e6d6cc48f74344cb590614db7e3116ba00a52aefd7dff468a60a5"
strings:
$code = {0F B6 44 07 08 0F B6 54 1F 08 03 C2 25 FF 00 00 80 79 07 48 0D 00 FF FF FF 40 89 45 ?? 6A 00}
condition:
any of them
}
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.
79 changes: 79 additions & 0 deletions analyzer/windows/modules/auxiliary/browsermonitor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright (C) 2024 [email protected]
# This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
# See the file 'docs/LICENSE' for copying permission.
import logging
import os
import subprocess
import tempfile
import time
from threading import Thread

from lib.common.abstracts import Auxiliary
from lib.common.results import upload_to_host

log = logging.getLogger(__name__)


class Browsermonitor(Auxiliary, Thread):
"""Monitors Browser Extension request logs."""

def __init__(self, options=None, config=None):
if options is None:
options = {}
Auxiliary.__init__(self, options, config)
Thread.__init__(self)
self.do_run = False
self.enabled = config.browsermonitor
self.startupinfo = subprocess.STARTUPINFO()
self.startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
self.browser_logfile = ""
self.last_modification = 0.0
self._is_first_save = True

def _find_browser_extension(self):
temp_dir = tempfile.gettempdir()
while not self.browser_logfile and self.do_run:
temp_dir_list = os.listdir(temp_dir)
for directory in temp_dir_list:
# TOR Browser saves directly to %temp%
if directory.startswith("bext_") and directory.endswith(".json"):
log.debug(f"Found extension logs: {self.browser_logfile}")
self.browser_logfile = os.path.join(temp_dir, directory)
break
tmp_directory_path = os.path.join(temp_dir, directory)
if not os.path.isdir(tmp_directory_path):
continue
if not directory.startswith("tmp"):
continue
tmp_dir_files = os.listdir(tmp_directory_path)
for file in tmp_dir_files:
if file.startswith("bext_") and file.endswith(".json"):
self.browser_logfile = os.path.join(temp_dir, directory, file)
log.debug(f"Found extension logs: {self.browser_logfile}")
break
time.sleep(1)

def _collect_browser_logs(self):
if not self._is_first_save and self.last_modification != os.path.getmtime(self.browser_logfile):
return
self.last_modification = os.path.getmtime(self.browser_logfile)
upload_to_host(self.browser_logfile, "browser/requests.log")
self._is_first_save = False

def run(self):
self.do_run = True
if self.enabled:
self._find_browser_extension()
self.last_modification = os.path.getmtime(self.browser_logfile)
while self.do_run:
self._collect_browser_logs()
time.sleep(1)
return True
return False

def stop(self):
if self.enabled:
self.do_run = False
if self.browser_logfile:
self._collect_browser_logs()
return True
25 changes: 25 additions & 0 deletions analyzer/windows/modules/packages/chromium_ext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright (C) 2024 [email protected]
# This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
# See the file 'docs/LICENSE' for copying permission.

import time
import webbrowser

from lib.common.abstracts import Package


class ChromiumExt(Package):
"""Chromium extension analysis package."""

PATHS = [
("LOCALAPPDATA", "Chromium", "chrome.exe"),
]
summary = "Opens the URL in Chromium with loaded extension."
description = """Runs Chromium preloaded with a custom extensios."""

def start(self, url):
webbrowser.register("chromium", None, webbrowser.BackgroundBrowser(self.get_path("chrome.exe")))
chromium = webbrowser.get("chromium")
chromium.open("about:blank")
time.sleep(10)
return chromium.open(url)
24 changes: 24 additions & 0 deletions analyzer/windows/modules/packages/firefox_ext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright (C) 2024 [email protected]
# This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
# See the file 'docs/LICENSE' for copying permission.
import time
import webbrowser

from lib.common.abstracts import Package


class Firefox_Ext(Package):
"""Firefox analysis package (with extension)."""

PATHS = [
("ProgramFiles", "Mozilla Firefox", "firefox.exe"),
]
summary = "Opens the URL in firefox."
description = """Spawns firefox.exe and opens the supplied URL."""

def start(self, url):
webbrowser.register("firefox", None, webbrowser.BackgroundBrowser(self.get_path("firefox.exe")))
firefox = webbrowser.get("firefox")
firefox.open("about:blank")
time.sleep(7) # Rough estimate, change based on your setup times.
return firefox.open(url)
25 changes: 25 additions & 0 deletions analyzer/windows/modules/packages/tor_browser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright (C) 2024 [email protected]
# This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
# See the file 'docs/LICENSE' for copying permission.
import time
import webbrowser

from lib.common.abstracts import Package


class TorBrowserExt(Package):
"""TOR analysis package (with extension)."""

PATHS = [
("LOCALAPPDATA", "Tor Browser", "Browser", "firefox.exe"),
]
summary = "Opens the URL in firefox."
description = """Spawns TOR's firefox.exe and opens the supplied URL."""

def start(self, url):
webbrowser.register("firefox", None, webbrowser.BackgroundBrowser(self.get_path("firefox.exe")))
firefox = webbrowser.get("firefox")
time.sleep(15) # Rough estimate, change based on your setup times.
firefox.open(url)
time.sleep(15) # Prevent analysis from finishing too early.
return
8 changes: 8 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
### [26.09.2024] Browser monitoring
* [Browser extension details](https://github.com/kevoreilly/CAPEv2/tree/master/extra/browser_extension/README.md). For code details see [PR](https://github.com/kevoreilly/CAPEv2/pull/2330)


### [23.09.2024]
* Monitor update: Fix size bug with unpacking embedded PEs
* .NET loader 'SlowLoader' detonation shim for slower cpus (race condition)

### [18.09.2024]
* Monitor updates:
* Add disassembled instruction to exception output
Expand Down
2 changes: 2 additions & 0 deletions conf/default/auxiliary.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ filecollector = yes
windows_static_route = no
tracee_linux = no
sslkeylogfile = no
# Requires setting up browser extension, check extra/browser_extension
browsermonitor = no

[AzSniffer]
# Enable or disable the use of Azure Network Watcher packet capture feature, disable standard sniffer if this is in use to not create concurrent .pcap files
Expand Down
7 changes: 7 additions & 0 deletions conf/default/az.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,17 @@ monitor_rate = 300
# normal instances
spot_instances = false

quota_name = MyQuota
# This value is used to set the number of machines that will be reserved outside the scale set (deducted from the quota).
quota_machine_exclusion = 5

# This boolean value is used to indicate if we want to wait for each VM to have its agent running before we
# start pulling tasks off of the stack
wait_for_agent_before_starting = true

# This integer value is used to determine how many times a VMSS that does not initialize properly can retry
init_retries = 2

# These are the value(s) of the DNS server(s) that you want the scale sets to use. (E.g. 1.1.1.1,8.8.8.8)
# NOTE: NO SPACES
dns_server_ips = <dns_server_ip>
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 @@ -80,6 +80,8 @@ platform = linux

[debug]
enabled = yes
# Amount of text (bytes)
buffer = 8192

[detections]
enabled = yes
Expand Down
3 changes: 3 additions & 0 deletions conf/default/reporting.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,6 @@ enabled = no
# Community
[malheur]
enabled = no

[browserext]
enabled = no
14 changes: 7 additions & 7 deletions data/yara/CAPE/Quickbind.yar
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ rule Quickbind
description = "Quickbind"
cape_type = "Quickbind Payload"
strings:
$anti_appdirs = {E8 [4] 83 F8 0? 7? ?? E8}
$anti_procs_ram = {E8 [4] 83 F8 0? 7? ?? E8 [4] 3D (FF 0E | 00 0F) 00 00}
$anti_ram = {E8 [4] 3D (FF 1F | 00 20 | 00 17) 00 00}
$mutex_1 = {FF [1-5] 3D B7 00 00 00 74 [7-10] 25 89 00 00 00}
$mutex_2 = {FF 15 [4] 4? 89 C? 4? 85 C? 74 ?? FF 15 [4] 3D B7 00 00 00}
$mutex_3 = {FF 15 [4] 4? 89 44 24 ?? 4? 83 7C 24 ?? 00 74 ?? FF 15 [4] 3D B7 00 00 00}
$sleep = {B9 64 00 00 00 [0-7] FF}
$anti_appdirs = {E8 [4] 83 F8 0? 7? ?? E8}
$anti_procs_ram = {E8 [4] 83 F8 0? 7? ?? E8 [4] 3D (FF 0E | 00 0F | FF 16) 00 00}
$anti_ram = {E8 [4] 3D (FF 1F | 00 20 | 00 17 | FF 0E | FF 16 | FF 2F) 00 00}
$mutex_1 = {FF [1-5] 3D B7 00 00 00 74 [7-10] 25 89 00 00 00}
$mutex_2 = {FF 15 [4] 4? 89 C? 4? 85 C? 74 ?? FF 15 [4] 3D B7 00 00 00}
$mutex_3 = {FF 15 [4] 4? 89 44 24 ?? 4? 83 7C 24 ?? 00 74 ?? FF 15 [4] 3D B7 00 00 00}
$sleep = {B9 64 00 00 00 [0-7] FF}
condition:
all of ($anti_*) and 1 of ($mutex_*) and $sleep
}
Loading

0 comments on commit 3244cd2

Please sign in to comment.