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
51 changes: 51 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Git
.git
.gitignore
.github

# Docker
docker-compose.yml
Dockerfile
.dockerignore

# DB
mongodata
pgdata
*.db
*.sqlite3

# Python
__pycache__
*.py[cod]
*$py.class
*.so
.Python
*.egg
*.egg-info
dist
build
.eggs
.venv
venv
env

# IDE
.vscode
.idea
*.swp
*.swo
*~

# Logs
*.log
logs

# OS
.DS_Store
Thumbs.db

# Others
.env.local
.cache
tmp
temp
48 changes: 47 additions & 1 deletion analyzer/windows/modules/auxiliary/screenshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,29 @@
# See the file 'docs/LICENSE' for copying permission.

import logging
import os
import time
from contextlib import suppress
from io import BytesIO
from threading import Thread

try:
from PIL import Image
except ImportError:
pass

from lib.api.screenshot import Screenshot
from lib.common.abstracts import Auxiliary
from lib.common.results import NetlogFile

HAVE_CV2 = False
with suppress(ImportError):
import cv2
import numpy as np

HAVE_CV2 = True


log = logging.getLogger(__name__)

SHOT_DELAY = 1
Expand All @@ -20,13 +35,34 @@
SKIP_AREA = None


def handle_qr_codes(image_data):
"""Extract URL from QR code if present."""
if not HAVE_CV2:
return None

try:
image = Image.open(image_data)
# Convert PIL image to BGR numpy array for OpenCV
img = cv2.cvtColor(np.array(image.convert("RGB")), cv2.COLOR_RGB2BGR)
detector = cv2.QRCodeDetector()
extracted, _, _ = detector.detectAndDecode(img)
# Simple URL detection
if extracted and "://" in extracted[:10]:
return extracted
except Exception as e:
log.debug("Error in handle_qr_codes: %s", e)

return None


class Screenshots(Auxiliary, Thread):
"""Take screenshots."""

def __init__(self, options, config):
Auxiliary.__init__(self, options, config)
Thread.__init__(self)
self.enabled = config.screenshots_windows
self.screenshots_qr = getattr(config, "screenshots_qr", False)
self.do_run = self.enabled

def stop(self):
Expand Down Expand Up @@ -62,7 +98,17 @@ def run(self):
img_current.save(tmpio, format="JPEG")
tmpio.seek(0)

# now upload to host from the StringIO
if self.screenshots_qr and HAVE_CV2:
url = handle_qr_codes(tmpio)
if url:
log.info("QR code detected with URL: %s", url)
try:
# os.startfile is Windows only and usually works for URLs
os.startfile(url)
except Exception as e:
log.error("Failed to open QR URL: %s", e)
tmpio.seek(0)

nf = NetlogFile()
nf.init(f"shots/{str(img_counter).rjust(4, '0')}.jpg")
for chunk in tmpio:
Expand Down
1 change: 1 addition & 0 deletions conf/default/auxiliary.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ procmon = no
recentfiles = no
screenshots_windows = yes
screenshots_linux = yes
screenshots_qr = no
sysmon_windows = no
sysmon_linux = no
tlsdump = yes
Expand Down
36 changes: 31 additions & 5 deletions dev_utils/mongo_hooks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import itertools
import logging
from contextlib import suppress

from pymongo import UpdateOne, errors
from pymongo.errors import InvalidDocument, BulkWriteError
import bson

from dev_utils.mongodb import (
mongo_bulk_write,
Expand Down Expand Up @@ -61,13 +64,12 @@ def normalize_file(file_dict, task_id):
)
new_dict = {}
for fld in static_fields:
try:
with suppress(KeyError):
new_dict[fld] = file_dict.pop(fld)
except KeyError:
pass

new_dict["_id"] = key
file_dict[FILE_REF_KEY] = key

return UpdateOne({"_id": key}, {"$set": new_dict, "$addToSet": {TASK_IDS_KEY: task_id}}, upsert=True, hint=[("_id", 1)])


Expand All @@ -87,8 +89,32 @@ def normalize_files(report):
try:
if requests:
mongo_bulk_write(FILES_COLL, requests, ordered=False)
except errors.OperationFailure as exc:
log.error("Mongo hook 'normalize_files' failed with code %d: %s", exc.code, exc)
except (errors.OperationFailure, InvalidDocument, BulkWriteError) as exc:
log.warning("Mongo hook 'normalize_files' failed: %s. Attempting to sanitize strings and retry.", exc)
for req in requests:
# req._doc is the update document: {"$set": new_dict, ...}
# Accessing private attribute _doc to modify in place for retry
try:
if hasattr(req, "_doc") and "$set" in req._doc and "strings" in req._doc["$set"]:
strings_val = req._doc["$set"]["strings"]
# Check if strings field alone is too large (buffer safe 15MB)
if strings_val and len(bson.encode({"strings": strings_val})) > 15 * 1024 * 1024:
log.warning("Truncating oversized strings field for retry.")
if isinstance(strings_val, list):
req._doc["$set"]["strings"] = strings_val[:1000]
else:
req._doc["$set"]["strings"] = []
# If still too large, clear it
if len(bson.encode({"strings": req._doc["$set"]["strings"]})) > 15 * 1024 * 1024:
req._doc["$set"]["strings"] = []
except Exception as e:
log.error("Failed to sanitize request during retry: %s", e)

# Retry the bulk write
try:
mongo_bulk_write(FILES_COLL, requests, ordered=False)
except Exception as retry_exc:
log.error("Retry of 'normalize_files' failed: %s", retry_exc)

return report

Expand Down
8 changes: 8 additions & 0 deletions docker/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
WEB_PORT=8000
RESULT_PORT=2042
PG_PORT=5432
MONGO_PORT=27017

POSTGRES_USER=cape
POSTGRES_PASSWORD=cape
POSTGRES_DB=cape
36 changes: 36 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
FROM python:3.11-bookworm

RUN apt-get update \
&& apt-get install -y --no-install-recommends git libgraphviz-dev tcpdump libcap2-bin iproute2 libjansson-dev libmagic-dev \
&& rm -rf /var/lib/apt/lists/*

RUN useradd -ms /bin/bash cape

RUN pip install --no-cache-dir poetry

RUN poetry config virtualenvs.create false

RUN mkdir -p /etc/poetry/bin && ln -s $(which poetry) /etc/poetry/bin/poetry
RUN mkdir -p /opt && ln -s /cape /opt/CAPEv2

WORKDIR /cape

COPY pyproject.toml poetry.lock* ./

RUN poetry install --no-interaction --no-ansi --no-root

COPY . .

RUN poetry install --no-interaction --no-ansi

RUN pip install --no-cache-dir -U flare-floss
RUN bash extra/yara_installer.sh

RUN bash docker/pcap.sh

RUN bash conf/copy_configs.sh
RUN chown -R cape:cape /cape

USER cape

CMD ["bash", "docker/run.sh"]
67 changes: 67 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
services:
cape-db:
image: postgres:bookworm
hostname: cape-db
restart: unless-stopped
ports:
- "127.0.0.1:${PG_PORT:-5432}:5432"
environment:
POSTGRES_USER: ${POSTGRES_USER:-cape}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-cape}
POSTGRES_DB: ${POSTGRES_DB:-cape}
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- cape-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-cape} -d ${POSTGRES_DB:-cape}"]
interval: 5s
timeout: 5s
retries: 10
start_period: 30s

mongodb:
image: mongo:6
command: ["--bind_ip_all"]
volumes:
- cape-mongo-data:/data/db
ports:
- "127.0.0.1:${MONGO_PORT:-27017}:27017"
restart: unless-stopped
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.runCommand({ ping: 1 })"]
interval: 10s
timeout: 5s
retries: 12
start_period: 20s

cape-server:
build:
context: ../
dockerfile: docker/Dockerfile
hostname: cape-server
restart: unless-stopped
depends_on:
cape-db:
condition: service_healthy
mongodb:
condition: service_healthy
environment:
- WEB_PORT=${WEB_PORT:-8000}
- POSTGRES_USER=${POSTGRES_USER:-cape}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-cape}
- POSTGRES_DB=${POSTGRES_DB:-cape}
ports:
- "127.0.0.1:${RESULT_PORT:-2042}:2042" # result server
- "127.0.0.1:${WEB_PORT:-8000}:8000" # web ui
volumes:
- ../conf:/cape/conf
- ../custom/conf:/cape/custom/conf
- ../custom:/cape/custom
- ../storage:/cape/storage
cap_add:
- NET_ADMIN
- NET_RAW

volumes:
cape-db-data:
cape-mongo-data:
4 changes: 4 additions & 0 deletions docker/pcap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
groupadd pcap
usermod -a -G pcap cape
chgrp pcap /usr/bin/tcpdump
setcap cap_net_raw,cap_net_admin=eip /usr/bin/tcpdump
4 changes: 4 additions & 0 deletions docker/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
This is not official docker soluction!
Is community based contribution so use on your own risks!

No support here from core devs!
34 changes: 34 additions & 0 deletions docker/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash
set -e

cd /cape

# Initialize configs if mounted volume is empty
if [ ! -f "conf/cuckoo.conf" ]; then
echo "Initializing configuration files..."
bash conf/copy_configs.sh
fi

# Configure Database connection for Docker environment
mkdir -p conf/cuckoo.conf.d
DB_CONF="conf/cuckoo.conf.d/00_docker_db.conf"
if [ ! -f "$DB_CONF" ]; then
echo "Creating Docker DB configuration..."
cat > "$DB_CONF" <<EOF
[database]
connection = postgresql://${POSTGRES_USER:-cape}:${POSTGRES_PASSWORD:-cape}@cape-db:5432/${POSTGRES_DB:-cape}
EOF
fi

cd web
python manage.py migrate
cd ..

python cuckoo.py &
CUCKOO_PID=$!

cd web

: "${WEB_PORT:=8000}"

python manage.py runserver 0.0.0.0:${WEB_PORT}
Loading
Loading