diff --git a/ttm_aggregate.py b/ttm_aggregate.py new file mode 100644 index 0000000..e353b67 --- /dev/null +++ b/ttm_aggregate.py @@ -0,0 +1,83 @@ + +import sys +import re +from pathlib import Path +from collections import defaultdict + +CATEGORIES = { + "fetch": ["_URLs.txt"], + "subdomains": ["_subdomain.txt"], + "backups": ["_backups.txt"], + "archives": ["_archive.txt"], + "parameters": ["_parameters.txt"], + "jwt": ["_jwt.txt"], + "xss": ["_xss.txt"], + "sqli": ["_sqli.txt"], + "lfi": ["_lfi.txt"], + "redirect": ["_redirect.txt"], + "jira": ["_jira.txt"], + "wp": ["_wp.txt"], + "fuzz": ["_fuzz.txt"], +} + +DOMAIN_RE = re.compile(r"^(?=.{1,253}$)(?!-)([a-z0-9-]{1,63}\.)+[a-z]{2,63}$") + + +def is_under_apex(domain: str, apex: str) -> bool: + return domain == apex or domain.endswith("." + apex) + + +def main(): + if len(sys.argv) != 2: + print("Usage: python ttm_aggregate.py ") + sys.exit(1) + + apex = sys.argv[1].lower().strip() + content_dir = Path("content") + output_dir = Path("output") / apex + + if not content_dir.exists(): + print("[!] content/ directory not found") + sys.exit(1) + + output_dir.mkdir(parents=True, exist_ok=True) + + buckets = defaultdict(set) + + for subdir in content_dir.iterdir(): + if not subdir.is_dir(): + continue + + domain = subdir.name.lower() + + if not DOMAIN_RE.match(domain): + continue + + if not is_under_apex(domain, apex): + continue + + for file in subdir.glob("*.txt"): + for category, suffixes in CATEGORIES.items(): + if any(file.name.endswith(sfx) for sfx in suffixes): + for line in file.read_text(errors="ignore").splitlines(): + line = line.strip() + if line: + buckets[category].add(f"[{domain}] {line}") + + + for category, values in buckets.items(): + if not values: + continue + + out_file = output_dir / f"{category}_all.txt" + with out_file.open("w", encoding="utf-8") as f: + for entry in sorted(values): + f.write(entry + "\n") + + print(f"[+] Written {out_file} ({len(values)} lines)") + + print(f"\n[✓] Aggregation complete → {output_dir}") + +if __name__ == "__main__": + main() + diff --git a/ttm_wrapper.py b/ttm_wrapper.py new file mode 100644 index 0000000..06b3074 --- /dev/null +++ b/ttm_wrapper.py @@ -0,0 +1,81 @@ +import subprocess +import sys +import re +from pathlib import Path + + +PIPELINE = [ + ["--fetch"], + ["--backups"], + ["--listings"], + ["--parameters"], + ["--jwt"], + ["--attack", "xss"], + ["--attack", "sqli"], + ["--attack", "lfi"], + ["--attack", "redirect"], + ["--attack", "jira"], + ["--attack", "wp"], + ["--attack", "fuzz"], + ["--subdomains"], +] + +DOMAIN_RE = re.compile(r"^(?=.{1,253}$)(?!-)([a-z0-9-]{1,63}\.)+[a-z]{2,63}$") + + +def normalize(domain: str) -> str: + domain = domain.strip().lower() + domain = re.sub(r"^https?://", "", domain) + return domain.split("/", 1)[0].rstrip(".") + +def load_subdomains(path: Path, apex: str): + results = set() + for line in path.read_text(errors="ignore").splitlines(): + d = normalize(line) + if DOMAIN_RE.match(d) and (d == apex or d.endswith("." + apex)): + results.add(d) + return sorted(results) + + +def main(): + if len(sys.argv) != 3: + print("Usage: python ttm_wrapper.py ") + sys.exit(1) + + apex = normalize(sys.argv[1]) + sub_file = Path(sys.argv[2]) + + base_dir = Path(__file__).resolve().parent + ttm_script = base_dir / "thetimemachine.py" + + if not ttm_script.exists(): + print("[!] thetimemachine.py not found in this directory") + sys.exit(1) + + targets = load_subdomains(sub_file, apex) + + print(f"[+] Apex : {apex}") + print(f"[+] Targets : {len(targets)}") + print(f"[+] TTM Dir : {base_dir}") + + for i, target in enumerate(targets, 1): + print(f"\n==============================") + print(f"[{i}/{len(targets)}] TARGET → {target}") + print(f"==============================") + + for step in PIPELINE: + cmd = ["python", "thetimemachine.py", target] + step + print("\n[CMD]", " ".join(cmd)) + print("-" * 60) + + subprocess.run( + cmd, + cwd=base_dir + ) + + print("\n[✓] All scans completed") + print("[i] Check ./content/ for raw results") + +if __name__ == "__main__": + main() +