Autarch Will Control The Internet
This commit is contained in:
321
modules/encmod_sources/floppy_dick.py
Normal file
321
modules/encmod_sources/floppy_dick.py
Normal file
@@ -0,0 +1,321 @@
|
||||
"""
|
||||
Floppy_Dick — AUTARCH Encrypted Module
|
||||
Operator: darkHal Security Group / Setec Security Labs
|
||||
|
||||
Automated credential fuzzer and authentication tester for legacy
|
||||
and deprecated protocol stacks. Targets: FTP, SMB, Telnet, SMTP,
|
||||
POP3, IMAP, SNMP v1/v2c, and RDP legacy endpoints. Generates
|
||||
detailed vulnerability reports suitable for remediation guidance.
|
||||
|
||||
For authorized penetration testing ONLY.
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import json
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Iterator, Optional
|
||||
|
||||
MODULE_NAME = "Floppy_Dick"
|
||||
MODULE_VERSION = "1.0"
|
||||
MODULE_AUTHOR = "darkHal Security Group"
|
||||
MODULE_TAGS = ["brute-force", "auth", "legacy", "pentest", "fuzz"]
|
||||
|
||||
_stop_flag = threading.Event()
|
||||
_output_lines = []
|
||||
|
||||
|
||||
def _emit(msg: str, level: str = "info") -> None:
|
||||
ts = datetime.now(timezone.utc).strftime('%H:%M:%S')
|
||||
line = f"[{ts}][{level.upper()}] {msg}"
|
||||
_output_lines.append(line)
|
||||
print(line)
|
||||
|
||||
|
||||
# ── Credential generators ─────────────────────────────────────────────────────
|
||||
|
||||
DEFAULT_USERS = [
|
||||
'admin', 'administrator', 'root', 'user', 'guest', 'test',
|
||||
'ftp', 'anonymous', 'backup', 'operator', 'service',
|
||||
]
|
||||
|
||||
DEFAULT_PASSWORDS = [
|
||||
'', 'admin', 'password', 'password123', '123456', 'admin123',
|
||||
'root', 'toor', 'pass', 'letmein', 'welcome', 'changeme',
|
||||
'default', 'cisco', 'alpine',
|
||||
]
|
||||
|
||||
|
||||
def wordlist_generator(path: Path) -> Iterator[str]:
|
||||
"""Yield lines from a wordlist file."""
|
||||
with open(path, 'r', encoding='utf-8', errors='replace') as f:
|
||||
for line in f:
|
||||
yield line.rstrip('\n')
|
||||
|
||||
|
||||
def credential_pairs(users: list[str], passwords: list[str]) -> Iterator[tuple[str, str]]:
|
||||
"""Yield all (user, password) combinations."""
|
||||
for u in users:
|
||||
for p in passwords:
|
||||
yield u, p
|
||||
|
||||
|
||||
# ── Protocol testers ──────────────────────────────────────────────────────────
|
||||
|
||||
def test_ftp(host: str, port: int, user: str, password: str, timeout: float = 5.0) -> dict:
|
||||
"""Test FTP credentials."""
|
||||
result = {'host': host, 'port': port, 'proto': 'FTP', 'user': user, 'success': False}
|
||||
try:
|
||||
import ftplib
|
||||
ftp = ftplib.FTP()
|
||||
ftp.connect(host, port, timeout=timeout)
|
||||
ftp.login(user, password)
|
||||
result['success'] = True
|
||||
result['banner'] = ftp.getwelcome()
|
||||
ftp.quit()
|
||||
except ftplib.error_perm as exc:
|
||||
result['error'] = str(exc)
|
||||
except Exception as exc:
|
||||
result['error'] = str(exc)
|
||||
return result
|
||||
|
||||
|
||||
def test_smtp(host: str, port: int, user: str, password: str, timeout: float = 5.0) -> dict:
|
||||
"""Test SMTP AUTH credentials."""
|
||||
result = {'host': host, 'port': port, 'proto': 'SMTP', 'user': user, 'success': False}
|
||||
try:
|
||||
import smtplib
|
||||
smtp = smtplib.SMTP(host, port, timeout=timeout)
|
||||
smtp.ehlo()
|
||||
if port == 587:
|
||||
smtp.starttls()
|
||||
smtp.login(user, password)
|
||||
result['success'] = True
|
||||
smtp.quit()
|
||||
except smtplib.SMTPAuthenticationError as exc:
|
||||
result['error'] = 'bad credentials'
|
||||
except Exception as exc:
|
||||
result['error'] = str(exc)
|
||||
return result
|
||||
|
||||
|
||||
def test_telnet(host: str, port: int, user: str, password: str, timeout: float = 5.0) -> dict:
|
||||
"""Test Telnet authentication."""
|
||||
result = {'host': host, 'port': port, 'proto': 'Telnet', 'user': user, 'success': False}
|
||||
try:
|
||||
import telnetlib
|
||||
tn = telnetlib.Telnet(host, port, timeout=timeout)
|
||||
tn.read_until(b'login: ', timeout)
|
||||
tn.write(user.encode('ascii') + b'\n')
|
||||
tn.read_until(b'Password: ', timeout)
|
||||
tn.write(password.encode('ascii') + b'\n')
|
||||
response = tn.read_until(b'$', timeout)
|
||||
if b'incorrect' not in response.lower() and b'failed' not in response.lower():
|
||||
result['success'] = True
|
||||
result['banner'] = response.decode('utf-8', errors='replace')[:128]
|
||||
tn.close()
|
||||
except Exception as exc:
|
||||
result['error'] = str(exc)
|
||||
return result
|
||||
|
||||
|
||||
def test_snmp(host: str, community: str = 'public', version: str = '2c', timeout: float = 3.0) -> dict:
|
||||
"""Test SNMP community string (v1/v2c)."""
|
||||
result = {'host': host, 'proto': 'SNMP', 'community': community, 'success': False}
|
||||
try:
|
||||
from pysnmp.hlapi import getCmd, SnmpEngine, CommunityData, UdpTransportTarget, ContextData, ObjectType, ObjectIdentity
|
||||
errorIndication, errorStatus, errorIndex, varBinds = next(
|
||||
getCmd(SnmpEngine(),
|
||||
CommunityData(community, mpModel=0 if version == '1' else 1),
|
||||
UdpTransportTarget((host, 161), timeout=timeout),
|
||||
ContextData(),
|
||||
ObjectType(ObjectIdentity('SNMPv2-MIB', 'sysDescr', 0)))
|
||||
)
|
||||
if not errorIndication and not errorStatus:
|
||||
result['success'] = True
|
||||
result['sysDescr'] = str(varBinds[0])
|
||||
else:
|
||||
result['error'] = str(errorIndication or errorStatus)
|
||||
except ImportError:
|
||||
result['error'] = 'pysnmp not installed'
|
||||
except Exception as exc:
|
||||
result['error'] = str(exc)
|
||||
return result
|
||||
|
||||
|
||||
def test_generic_banner(host: str, port: int, timeout: float = 3.0) -> dict:
|
||||
"""Grab a service banner from any TCP port."""
|
||||
result = {'host': host, 'port': port, 'proto': 'TCP', 'banner': ''}
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(timeout)
|
||||
s.connect((host, port))
|
||||
banner = s.recv(1024)
|
||||
result['banner'] = banner.decode('utf-8', errors='replace').strip()[:256]
|
||||
result['open'] = True
|
||||
s.close()
|
||||
except Exception as exc:
|
||||
result['open'] = False
|
||||
result['error'] = str(exc)
|
||||
return result
|
||||
|
||||
|
||||
# ── Port scanner ──────────────────────────────────────────────────────────────
|
||||
|
||||
LEGACY_PORTS = {
|
||||
21: 'FTP',
|
||||
23: 'Telnet',
|
||||
25: 'SMTP',
|
||||
110: 'POP3',
|
||||
143: 'IMAP',
|
||||
161: 'SNMP',
|
||||
445: 'SMB',
|
||||
587: 'SMTP-Submission',
|
||||
3389: 'RDP',
|
||||
}
|
||||
|
||||
|
||||
def scan_ports(host: str, ports: Optional[list[int]] = None, timeout: float = 1.0) -> dict:
|
||||
"""Scan ports and return which are open."""
|
||||
if ports is None:
|
||||
ports = list(LEGACY_PORTS.keys())
|
||||
open_ports = {}
|
||||
for port in ports:
|
||||
banner = test_generic_banner(host, port, timeout)
|
||||
if banner.get('open'):
|
||||
proto = LEGACY_PORTS.get(port, 'unknown')
|
||||
open_ports[port] = {
|
||||
'proto': proto,
|
||||
'banner': banner.get('banner', ''),
|
||||
}
|
||||
return {'host': host, 'open_ports': open_ports}
|
||||
|
||||
|
||||
# ── Main fuzzing engine ───────────────────────────────────────────────────────
|
||||
|
||||
def fuzz_host(
|
||||
host: str,
|
||||
port: int,
|
||||
proto: str,
|
||||
users: list[str],
|
||||
passwords: list[str],
|
||||
delay: float = 0.1,
|
||||
output_cb=None,
|
||||
) -> list[dict]:
|
||||
"""Run credential fuzzing against a single host:port for a given protocol."""
|
||||
found = []
|
||||
testers = {
|
||||
'FTP': test_ftp,
|
||||
'SMTP': test_smtp,
|
||||
'SMTP-Submission': test_smtp,
|
||||
'Telnet': test_telnet,
|
||||
}
|
||||
tester = testers.get(proto)
|
||||
if not tester:
|
||||
return [{'error': f'No tester implemented for {proto}'}]
|
||||
|
||||
for user, password in credential_pairs(users, passwords):
|
||||
if _stop_flag.is_set():
|
||||
break
|
||||
r = tester(host, port, user, password)
|
||||
if r.get('success'):
|
||||
msg = f"[FOUND] {proto} {host}:{port} -> {user}:{password}"
|
||||
_emit(msg, 'warn')
|
||||
if output_cb:
|
||||
output_cb({'line': msg, 'found': True, 'user': user, 'password': password})
|
||||
found.append(r)
|
||||
time.sleep(delay)
|
||||
|
||||
return found
|
||||
|
||||
|
||||
# ── Main run entry point ──────────────────────────────────────────────────────
|
||||
|
||||
def run(params: dict, output_cb=None) -> dict:
|
||||
"""
|
||||
Main execution entry point.
|
||||
|
||||
params:
|
||||
targets — list of hosts to test
|
||||
ports — list of ports to probe (default: LEGACY_PORTS)
|
||||
users — list of usernames (default: DEFAULT_USERS)
|
||||
passwords — list of passwords (default: DEFAULT_PASSWORDS)
|
||||
user_wordlist — path to user wordlist file
|
||||
pass_wordlist — path to password wordlist file
|
||||
delay — delay between attempts in seconds (default 0.1)
|
||||
snmp_communities — list of SNMP community strings to test
|
||||
threads — number of parallel threads (default 1)
|
||||
"""
|
||||
_stop_flag.clear()
|
||||
_output_lines.clear()
|
||||
|
||||
def emit(msg, level='info'):
|
||||
_emit(msg, level)
|
||||
if output_cb:
|
||||
output_cb({'line': f"[{level.upper()}] {msg}"})
|
||||
|
||||
emit(f"=== {MODULE_NAME} v{MODULE_VERSION} ===")
|
||||
emit("Authorized penetration testing only. All attempts logged.")
|
||||
|
||||
targets = params.get('targets', [])
|
||||
ports = params.get('ports', None)
|
||||
delay = float(params.get('delay', 0.1))
|
||||
users = params.get('users', DEFAULT_USERS)[:]
|
||||
passwords = params.get('passwords', DEFAULT_PASSWORDS)[:]
|
||||
|
||||
# Load wordlists if provided
|
||||
uw = params.get('user_wordlist', '')
|
||||
pw = params.get('pass_wordlist', '')
|
||||
if uw and Path(uw).exists():
|
||||
users = list(wordlist_generator(Path(uw)))
|
||||
emit(f"Loaded {len(users)} users from wordlist")
|
||||
if pw and Path(pw).exists():
|
||||
passwords = list(wordlist_generator(Path(pw)))
|
||||
emit(f"Loaded {len(passwords)} passwords from wordlist")
|
||||
|
||||
snmp_communities = params.get('snmp_communities', ['public', 'private', 'community'])
|
||||
|
||||
all_results = []
|
||||
|
||||
for host in targets:
|
||||
if _stop_flag.is_set():
|
||||
break
|
||||
emit(f"Scanning {host}...")
|
||||
scan = scan_ports(host, ports)
|
||||
emit(f" Open ports: {list(scan['open_ports'].keys())}")
|
||||
|
||||
host_result = {'host': host, 'open_ports': scan['open_ports'], 'findings': []}
|
||||
|
||||
for port, info in scan['open_ports'].items():
|
||||
if _stop_flag.is_set():
|
||||
break
|
||||
proto = info['proto']
|
||||
emit(f" Fuzzing {proto} on port {port}...")
|
||||
|
||||
if proto == 'SNMP':
|
||||
for comm in snmp_communities:
|
||||
r = test_snmp(host, comm)
|
||||
if r.get('success'):
|
||||
emit(f"[FOUND] SNMP community: {comm}", 'warn')
|
||||
host_result['findings'].append(r)
|
||||
else:
|
||||
found = fuzz_host(host, port, proto, users, passwords, delay, output_cb)
|
||||
host_result['findings'].extend(found)
|
||||
|
||||
all_results.append(host_result)
|
||||
|
||||
emit(f"Fuzzing complete. {sum(len(r['findings']) for r in all_results)} finding(s).")
|
||||
|
||||
return {
|
||||
'module': MODULE_NAME,
|
||||
'targets': len(targets),
|
||||
'results': all_results,
|
||||
'output': _output_lines[:],
|
||||
}
|
||||
|
||||
|
||||
def stop():
|
||||
_stop_flag.set()
|
||||
Reference in New Issue
Block a user