milenium 0.4.2__tar.gz → 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {milenium-0.4.2 → milenium-0.5.1}/PKG-INFO +2 -1
- milenium-0.5.1/README.md +2 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/cli.py +67 -0
- milenium-0.5.1/milenium/modules/aggregator.py +41 -0
- milenium-0.5.1/milenium/modules/email_validator.py +22 -0
- milenium-0.5.1/milenium/modules/exif_extractor.py +22 -0
- milenium-0.5.1/milenium/modules/github_search.py +29 -0
- milenium-0.5.1/milenium/modules/gravatar.py +13 -0
- milenium-0.5.1/milenium/modules/instagram_osint.py +25 -0
- milenium-0.5.1/milenium/modules/ip_geo_advanced.py +16 -0
- milenium-0.5.1/milenium/modules/password_pattern.py +32 -0
- milenium-0.5.1/milenium/modules/pgp_lookup.py +10 -0
- milenium-0.5.1/milenium/modules/reverse_whois.py +24 -0
- milenium-0.5.1/milenium/modules/tg_parser.py +20 -0
- milenium-0.5.1/milenium/modules/twitter_osint.py +13 -0
- milenium-0.5.1/milenium/modules/vk_osint.py +23 -0
- milenium-0.5.1/milenium/modules/wayback.py +18 -0
- milenium-0.5.1/milenium/modules/wifi_geolocation.py +27 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/tui.py +65 -4
- {milenium-0.4.2 → milenium-0.5.1}/milenium.egg-info/PKG-INFO +2 -1
- {milenium-0.4.2 → milenium-0.5.1}/milenium.egg-info/SOURCES.txt +15 -1
- {milenium-0.4.2 → milenium-0.5.1}/pyproject.toml +1 -1
- milenium-0.4.2/README.md +0 -1
- milenium-0.4.2/milenium/modules/aggregator.py +0 -26
- {milenium-0.4.2 → milenium-0.5.1}/milenium/__init__.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/__init__.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/breach.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/breach_local.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/dehashed.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/dns_history.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/domain.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/dorker.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/email.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/hibp.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/ip.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/ip_reputation.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/leakcheck.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/logger.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/permutator.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/phone.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/pwned_passwords.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/report.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/shodan_lite.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/social.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/telegram_osint.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium/modules/username.py +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium.egg-info/dependency_links.txt +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium.egg-info/entry_points.txt +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium.egg-info/requires.txt +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/milenium.egg-info/top_level.txt +0 -0
- {milenium-0.4.2 → milenium-0.5.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: milenium
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: OSINT / dox / deanon toolkit
|
|
5
5
|
Requires-Python: >=3.9
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -13,3 +13,4 @@ Requires-Dist: click
|
|
|
13
13
|
Requires-Dist: python-whois
|
|
14
14
|
|
|
15
15
|
# milenium
|
|
16
|
+
# exiftool
|
milenium-0.5.1/README.md
ADDED
|
@@ -84,6 +84,73 @@ def user(nick, permutations, dorks):
|
|
|
84
84
|
console.print(f"[bold cyan]Лог:[/bold cyan] {logger.path}")
|
|
85
85
|
logger.close()
|
|
86
86
|
|
|
87
|
+
@main.command()
|
|
88
|
+
@click.argument("email")
|
|
89
|
+
def email_check(email):
|
|
90
|
+
"""SMTP-валидация email + Gravatar + PGP + GitHub"""
|
|
91
|
+
logger = _init_logger()
|
|
92
|
+
from milenium.modules import email_validator, gravatar, pgp_lookup, github_search
|
|
93
|
+
res = {
|
|
94
|
+
"smtp": email_validator.check(email),
|
|
95
|
+
"gravatar": gravatar.check(email),
|
|
96
|
+
"pgp": pgp_lookup.check(email),
|
|
97
|
+
"github": github_search.check_email(email),
|
|
98
|
+
}
|
|
99
|
+
for k, v in res.items():
|
|
100
|
+
console.print(f"[bold]{k}:[/bold] {v}")
|
|
101
|
+
logger.close()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@main.command()
|
|
105
|
+
@click.argument("image_path")
|
|
106
|
+
def exif(image_path):
|
|
107
|
+
"""Извлечь EXIF/GPS из фото"""
|
|
108
|
+
logger = _init_logger()
|
|
109
|
+
from milenium.modules import exif_extractor
|
|
110
|
+
res = exif_extractor.check(image_path)
|
|
111
|
+
for k, v in res.items():
|
|
112
|
+
console.print(f"[bold]{k}:[/bold] {v}")
|
|
113
|
+
logger.close()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@main.command()
|
|
117
|
+
@click.argument("domain")
|
|
118
|
+
def whois_rev(domain):
|
|
119
|
+
"""Reverse WHOIS по домену"""
|
|
120
|
+
logger = _init_logger()
|
|
121
|
+
from milenium.modules import reverse_whois
|
|
122
|
+
res = reverse_whois.check(domain)
|
|
123
|
+
for d in res.get("domains", []):
|
|
124
|
+
console.print(d)
|
|
125
|
+
logger.close()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@main.command()
|
|
129
|
+
@click.option("--name", default=None)
|
|
130
|
+
@click.option("--surname", default=None)
|
|
131
|
+
@click.option("--year", default=None)
|
|
132
|
+
@click.option("--nick", default=None)
|
|
133
|
+
def pwgen(name, surname, year, nick):
|
|
134
|
+
"""Генератор вероятных паролей по данным жертвы"""
|
|
135
|
+
logger = _init_logger()
|
|
136
|
+
from milenium.modules import password_pattern
|
|
137
|
+
res = password_pattern.generate(name=name, surname=surname, birth_year=year, nick=nick)
|
|
138
|
+
for p in res["passwords"][:50]:
|
|
139
|
+
console.print(p)
|
|
140
|
+
console.print(f"\n[bold]Всего:[/bold] {res['count']}")
|
|
141
|
+
logger.close()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@main.command()
|
|
145
|
+
@click.argument("image_path")
|
|
146
|
+
def exif_cmd(image_path):
|
|
147
|
+
"""EXIF из фото"""
|
|
148
|
+
logger = _init_logger()
|
|
149
|
+
from milenium.modules import exif_extractor
|
|
150
|
+
res = exif_extractor.check(image_path)
|
|
151
|
+
for k, v in res.items():
|
|
152
|
+
console.print(f"[bold]{k}:[/bold] {v}")
|
|
153
|
+
logger.close()
|
|
87
154
|
|
|
88
155
|
@main.command()
|
|
89
156
|
@click.argument("mail")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from milenium.modules import (
|
|
2
|
+
pwned_passwords, breach_local, hibp, dehashed, leakcheck,
|
|
3
|
+
email_validator, gravatar, pgp_lookup, github_search,
|
|
4
|
+
wayback, reverse_whois, password_pattern,
|
|
5
|
+
ip_geo_advanced, tg_parser,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
def run(email=None, username=None, phone=None, password=None, db_path=None, domain=None):
|
|
9
|
+
out = {}
|
|
10
|
+
|
|
11
|
+
if password:
|
|
12
|
+
out["pwned_passwords"] = pwned_passwords.check_password(password)
|
|
13
|
+
|
|
14
|
+
if email:
|
|
15
|
+
out["hibp"] = hibp.check_email(email)
|
|
16
|
+
out["dehashed_email"] = dehashed.search(f'email:"{email}"')
|
|
17
|
+
out["leakcheck_email"] = leakcheck.search(email)
|
|
18
|
+
out["gravatar"] = gravatar.check(email)
|
|
19
|
+
out["pgp"] = pgp_lookup.check(email)
|
|
20
|
+
out["github_email"] = github_search.check_email(email)
|
|
21
|
+
out["email_valid"] = email_validator.check(email)
|
|
22
|
+
|
|
23
|
+
if username:
|
|
24
|
+
out["dehashed_username"] = dehashed.search(f'username:"{username}"')
|
|
25
|
+
out["leakcheck_username"] = leakcheck.search(username)
|
|
26
|
+
out["github_user"] = github_search.check_username(username)
|
|
27
|
+
out["telegram"] = tg_parser.channel_info(username)
|
|
28
|
+
|
|
29
|
+
if phone:
|
|
30
|
+
out["leakcheck_phone"] = leakcheck.search(phone)
|
|
31
|
+
|
|
32
|
+
if domain:
|
|
33
|
+
out["wayback"] = wayback.check(f"http://{domain}")
|
|
34
|
+
out["reverse_whois"] = reverse_whois.check(domain)
|
|
35
|
+
|
|
36
|
+
if db_path:
|
|
37
|
+
q = email or username or phone
|
|
38
|
+
if q:
|
|
39
|
+
out["breach_local"] = breach_local.search(q, db_path)
|
|
40
|
+
|
|
41
|
+
return out
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import smtplib
|
|
2
|
+
import dns.resolver
|
|
3
|
+
|
|
4
|
+
def check(email):
|
|
5
|
+
"""Проверка существования email через SMTP RCPT TO (без отправки письма)."""
|
|
6
|
+
domain = email.split("@")[1]
|
|
7
|
+
try:
|
|
8
|
+
mx_records = dns.resolver.resolve(domain, "MX")
|
|
9
|
+
mx_host = str(sorted(mx_records, key=lambda r: r.preference)[0].exchange).rstrip(".")
|
|
10
|
+
except Exception as e:
|
|
11
|
+
return {"error": f"MX lookup failed: {e}"}
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
server = smtplib.SMTP(timeout=10)
|
|
15
|
+
server.connect(mx_host, 25)
|
|
16
|
+
server.helo("milenium.local")
|
|
17
|
+
server.mail("check@milenium.local")
|
|
18
|
+
code, _ = server.rcpt(email)
|
|
19
|
+
server.quit()
|
|
20
|
+
return {"exists": code in (250, 251), "code": code, "mx": mx_host}
|
|
21
|
+
except Exception as e:
|
|
22
|
+
return {"error": str(e), "mx": mx_host}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
def check(image_path):
|
|
5
|
+
"""Требует exiftool в PATH. Установка: apt install libimage-exiftool-perl / choco install exiftool."""
|
|
6
|
+
if not os.path.exists(image_path):
|
|
7
|
+
return {"error": "file not found"}
|
|
8
|
+
try:
|
|
9
|
+
r = subprocess.run(
|
|
10
|
+
["exiftool", "-j", image_path],
|
|
11
|
+
capture_output=True, text=True, timeout=20
|
|
12
|
+
)
|
|
13
|
+
if r.returncode != 0:
|
|
14
|
+
return {"error": r.stderr.strip() or "exiftool failed"}
|
|
15
|
+
import json
|
|
16
|
+
data = json.loads(r.stdout)[0]
|
|
17
|
+
keys = ["GPSLatitude", "GPSLongitude", "Model", "Make", "DateTimeOriginal", "Software"]
|
|
18
|
+
return {k: data.get(k) for k in keys if data.get(k)}
|
|
19
|
+
except FileNotFoundError:
|
|
20
|
+
return {"error": "exiftool not installed"}
|
|
21
|
+
except Exception as e:
|
|
22
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
def check_email(email):
|
|
4
|
+
try:
|
|
5
|
+
r = requests.get(
|
|
6
|
+
f"https://api.github.com/search/users?q={email}+in:email",
|
|
7
|
+
headers={"Accept": "application/vnd.github+json"},
|
|
8
|
+
timeout=10,
|
|
9
|
+
)
|
|
10
|
+
if r.status_code == 200:
|
|
11
|
+
items = r.json().get("items", [])
|
|
12
|
+
return {"users": [{"login": u["login"], "url": u["html_url"]} for u in items]}
|
|
13
|
+
return {"error": f"HTTP {r.status_code}"}
|
|
14
|
+
except Exception as e:
|
|
15
|
+
return {"error": str(e)}
|
|
16
|
+
|
|
17
|
+
def check_username(username):
|
|
18
|
+
try:
|
|
19
|
+
r = requests.get(
|
|
20
|
+
f"https://api.github.com/search/users?q={username}+in:login",
|
|
21
|
+
headers={"Accept": "application/vnd.github+json"},
|
|
22
|
+
timeout=10,
|
|
23
|
+
)
|
|
24
|
+
if r.status_code == 200:
|
|
25
|
+
items = r.json().get("items", [])
|
|
26
|
+
return {"users": [{"login": u["login"], "url": u["html_url"]} for u in items]}
|
|
27
|
+
return {"error": f"HTTP {r.status_code}"}
|
|
28
|
+
except Exception as e:
|
|
29
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
def check(email):
|
|
5
|
+
h = hashlib.md5(email.strip().lower().encode()).hexdigest()
|
|
6
|
+
url = f"https://www.gravatar.com/avatar/{h}?d=404"
|
|
7
|
+
try:
|
|
8
|
+
r = requests.get(url, timeout=10)
|
|
9
|
+
if r.status_code == 200:
|
|
10
|
+
return {"exists": True, "url": f"https://www.gravatar.com/{h}"}
|
|
11
|
+
return {"exists": False}
|
|
12
|
+
except Exception as e:
|
|
13
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
def check(username):
|
|
5
|
+
"""Парсит публичный профиль Instagram без API (может ломаться при защите)."""
|
|
6
|
+
headers = {"User-Agent": "Mozilla/5.0"}
|
|
7
|
+
try:
|
|
8
|
+
r = requests.get(f"https://www.instagram.com/{username}/", headers=headers, timeout=15)
|
|
9
|
+
if r.status_code != 200:
|
|
10
|
+
return {"error": f"HTTP {r.status_code}"}
|
|
11
|
+
m = re.search(r'"edge_followed_by":\{"count":(\d+)\}', r.text)
|
|
12
|
+
followers = m.group(1) if m else None
|
|
13
|
+
m2 = re.search(r'"edge_follow":\{"count":(\d+)\}', r.text)
|
|
14
|
+
following = m2.group(1) if m2 else None
|
|
15
|
+
m3 = re.search(r'"biography":"(.*?)"', r.text)
|
|
16
|
+
bio = m3.group(1) if m3 else None
|
|
17
|
+
return {
|
|
18
|
+
"exists": True,
|
|
19
|
+
"followers": followers,
|
|
20
|
+
"following": following,
|
|
21
|
+
"bio": bio,
|
|
22
|
+
"url": f"https://instagram.com/{username}",
|
|
23
|
+
}
|
|
24
|
+
except Exception as e:
|
|
25
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
def check(ip):
|
|
4
|
+
out = {}
|
|
5
|
+
try:
|
|
6
|
+
r = requests.get(f"https://ipapi.co/{ip}/json/", timeout=10).json()
|
|
7
|
+
out["ipapi"] = {k: r.get(k) for k in ["city", "region", "country_name", "org", "asn", "timezone"]}
|
|
8
|
+
except Exception as e:
|
|
9
|
+
out["ipapi_error"] = str(e)
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
r = requests.get(f"https://ipwho.is/{ip}", timeout=10).json()
|
|
13
|
+
out["ipwhois"] = {k: r.get(k) for k in ["city", "region", "country", "connection"]}
|
|
14
|
+
except Exception as e:
|
|
15
|
+
out["ipwhois_error"] = str(e)
|
|
16
|
+
return out
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
def generate(name=None, surname=None, birth_year=None, nick=None, limit=500):
|
|
5
|
+
"""Генерит вероятные пароли по данным жертвы."""
|
|
6
|
+
base = []
|
|
7
|
+
if name: base.append(name.lower())
|
|
8
|
+
if surname: base.append(surname.lower())
|
|
9
|
+
if nick: base.append(nick.lower())
|
|
10
|
+
if name and surname:
|
|
11
|
+
base.append(f"{name.lower()}{surname.lower()}")
|
|
12
|
+
base.append(f"{name.lower()}.{surname.lower()}")
|
|
13
|
+
base.append(f"{name.lower()}_{surname.lower()}")
|
|
14
|
+
|
|
15
|
+
suffixes = ["", "123", "1234", "12345", "2024", "2025", "!", "@", "#", "_"]
|
|
16
|
+
if birth_year:
|
|
17
|
+
suffixes += [str(birth_year), str(birth_year)[-2:], f"{birth_year}!"]
|
|
18
|
+
|
|
19
|
+
out = set()
|
|
20
|
+
for b in base:
|
|
21
|
+
for s in suffixes:
|
|
22
|
+
out.add(b + s)
|
|
23
|
+
out.add(b.capitalize() + s)
|
|
24
|
+
out.add(b.upper() + s)
|
|
25
|
+
|
|
26
|
+
# leet
|
|
27
|
+
leet_map = {"a": "4", "e": "3", "i": "1", "o": "0", "s": "5"}
|
|
28
|
+
for b in list(out)[:100]:
|
|
29
|
+
leet = "".join(leet_map.get(c, c) for c in b)
|
|
30
|
+
out.add(leet)
|
|
31
|
+
|
|
32
|
+
return {"passwords": list(out)[:limit], "count": len(out)}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
def check(email):
|
|
4
|
+
try:
|
|
5
|
+
r = requests.get(f"https://keys.openpgp.org/vks/v1/by-email/{email}", timeout=10)
|
|
6
|
+
if r.status_code == 200:
|
|
7
|
+
return {"found": True, "key": r.text[:500]}
|
|
8
|
+
return {"found": False}
|
|
9
|
+
except Exception as e:
|
|
10
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from bs4 import BeautifulSoup
|
|
3
|
+
|
|
4
|
+
def check(query):
|
|
5
|
+
"""Парсит viewdns.info — бесплатно, без API-ключа."""
|
|
6
|
+
try:
|
|
7
|
+
r = requests.get(
|
|
8
|
+
"https://viewdns.info/reversewhois/",
|
|
9
|
+
params={"q": query},
|
|
10
|
+
headers={"User-Agent": "Mozilla/5.0"},
|
|
11
|
+
timeout=20,
|
|
12
|
+
)
|
|
13
|
+
soup = BeautifulSoup(r.text, "html.parser")
|
|
14
|
+
table = soup.find("table", {"border": "1"})
|
|
15
|
+
if not table:
|
|
16
|
+
return {"domains": []}
|
|
17
|
+
domains = []
|
|
18
|
+
for row in table.find_all("tr")[1:]:
|
|
19
|
+
cols = row.find_all("td")
|
|
20
|
+
if cols:
|
|
21
|
+
domains.append(cols[0].text.strip())
|
|
22
|
+
return {"domains": domains[:100]}
|
|
23
|
+
except Exception as e:
|
|
24
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from bs4 import BeautifulSoup
|
|
3
|
+
|
|
4
|
+
def channel_info(username):
|
|
5
|
+
try:
|
|
6
|
+
r = requests.get(f"https://t.me/{username}", timeout=10)
|
|
7
|
+
if r.status_code != 200:
|
|
8
|
+
return {"error": f"HTTP {r.status_code}"}
|
|
9
|
+
soup = BeautifulSoup(r.text, "html.parser")
|
|
10
|
+
title = soup.find("meta", property="og:title")
|
|
11
|
+
desc = soup.find("meta", property="og:description")
|
|
12
|
+
members = soup.find("div", class_="tgme_page_extra")
|
|
13
|
+
return {
|
|
14
|
+
"title": title["content"] if title else None,
|
|
15
|
+
"description": desc["content"] if desc else None,
|
|
16
|
+
"extra": members.text.strip() if members else None,
|
|
17
|
+
"url": f"https://t.me/{username}",
|
|
18
|
+
}
|
|
19
|
+
except Exception as e:
|
|
20
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
def check(username):
|
|
4
|
+
"""Через nitter-зеркало (может не работать, зеркала меняются)."""
|
|
5
|
+
mirrors = ["https://nitter.net", "https://nitter.privacydev.net"]
|
|
6
|
+
for base in mirrors:
|
|
7
|
+
try:
|
|
8
|
+
r = requests.get(f"{base}/{username}", timeout=10)
|
|
9
|
+
if r.status_code == 200 and "user-profile" in r.text:
|
|
10
|
+
return {"exists": True, "url": f"https://twitter.com/{username}", "mirror": base}
|
|
11
|
+
except Exception:
|
|
12
|
+
continue
|
|
13
|
+
return {"exists": False}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
def check(user_id, token=""):
|
|
4
|
+
"""VK API: нужен сервисный токен. Без него вернёт ошибку."""
|
|
5
|
+
if not token:
|
|
6
|
+
return {"error": "no VK token"}
|
|
7
|
+
try:
|
|
8
|
+
r = requests.get(
|
|
9
|
+
"https://api.vk.com/method/users.get",
|
|
10
|
+
params={
|
|
11
|
+
"user_ids": user_id,
|
|
12
|
+
"fields": "followers_count,country,city,photo_max,bdate",
|
|
13
|
+
"access_token": token,
|
|
14
|
+
"v": "5.199",
|
|
15
|
+
},
|
|
16
|
+
timeout=15,
|
|
17
|
+
)
|
|
18
|
+
data = r.json()
|
|
19
|
+
if "response" in data:
|
|
20
|
+
return data["response"][0]
|
|
21
|
+
return {"error": data.get("error", {}).get("error_msg")}
|
|
22
|
+
except Exception as e:
|
|
23
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
def check(url):
|
|
4
|
+
try:
|
|
5
|
+
r = requests.get(
|
|
6
|
+
"http://archive.org/wayback/available",
|
|
7
|
+
params={"url": url},
|
|
8
|
+
timeout=15,
|
|
9
|
+
)
|
|
10
|
+
if r.status_code == 200:
|
|
11
|
+
data = r.json()
|
|
12
|
+
snap = data.get("archived_snapshots", {}).get("closest")
|
|
13
|
+
if snap:
|
|
14
|
+
return {"available": True, "snapshot": snap["url"], "timestamp": snap["timestamp"]}
|
|
15
|
+
return {"available": False}
|
|
16
|
+
return {"error": f"HTTP {r.status_code}"}
|
|
17
|
+
except Exception as e:
|
|
18
|
+
return {"error": str(e)}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
def check(bssid):
|
|
4
|
+
"""Wi-Fi геолокация через wigle.net (нужен API-токен)."""
|
|
5
|
+
try:
|
|
6
|
+
r = requests.get(
|
|
7
|
+
"https://api.wigle.net/api/v2/network/search",
|
|
8
|
+
params={"netid": bssid},
|
|
9
|
+
headers={"Authorization": "Basic YOUR_BASE64_TOKEN"},
|
|
10
|
+
timeout=15,
|
|
11
|
+
)
|
|
12
|
+
if r.status_code == 200:
|
|
13
|
+
data = r.json()
|
|
14
|
+
results = data.get("results", [])
|
|
15
|
+
if results:
|
|
16
|
+
first = results[0]
|
|
17
|
+
return {
|
|
18
|
+
"lat": first.get("trilat"),
|
|
19
|
+
"lon": first.get("trilong"),
|
|
20
|
+
"ssid": first.get("ssid"),
|
|
21
|
+
"country": first.get("country"),
|
|
22
|
+
"city": first.get("city"),
|
|
23
|
+
}
|
|
24
|
+
return {"found": False}
|
|
25
|
+
return {"error": f"HTTP {r.status_code}"}
|
|
26
|
+
except Exception as e:
|
|
27
|
+
return {"error": str(e)}
|
|
@@ -53,6 +53,39 @@ SESSION = {
|
|
|
53
53
|
"db_loaded": False,
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
OUTPUT_LOG = [] # буфер вывода последних строк
|
|
57
|
+
|
|
58
|
+
def push_output(text):
|
|
59
|
+
ts = datetime.now().strftime("%H:%M:%S")
|
|
60
|
+
OUTPUT_LOG.append(f"[red]{ts}[/red] [white]{text}[/white]")
|
|
61
|
+
if len(OUTPUT_LOG) > 20:
|
|
62
|
+
OUTPUT_LOG.pop(0)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def clear_output():
|
|
66
|
+
OUTPUT_LOG.clear()
|
|
67
|
+
|
|
68
|
+
def render_output():
|
|
69
|
+
lines = []
|
|
70
|
+
lines.append("[bold red]=== ВЫВОД КОМАНДЫ ===[/bold red]")
|
|
71
|
+
if OUTPUT_LOG:
|
|
72
|
+
lines.extend(OUTPUT_LOG[-10:])
|
|
73
|
+
else:
|
|
74
|
+
lines.append("[white]Пока ничего. Введи команду ниже.[/white]")
|
|
75
|
+
return Panel(
|
|
76
|
+
"\n".join(lines),
|
|
77
|
+
title="[bold red]ПРОЦЕСС[/bold red]",
|
|
78
|
+
border_style="red",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def render_input():
|
|
83
|
+
return Panel(
|
|
84
|
+
"[bold red]milenium[/bold red] [white]@[/white] [red]VexxDev200[/red] [white]~[/white] [bold white]введи команду ниже[/bold white]",
|
|
85
|
+
title="[bold red]ВВОД[/bold red]",
|
|
86
|
+
border_style="red",
|
|
87
|
+
)
|
|
88
|
+
|
|
56
89
|
|
|
57
90
|
def log_result(text):
|
|
58
91
|
ts = datetime.now().strftime("%H:%M:%S")
|
|
@@ -65,7 +98,9 @@ def build_layout():
|
|
|
65
98
|
layout = Layout()
|
|
66
99
|
layout.split_column(
|
|
67
100
|
Layout(name="header", size=10),
|
|
68
|
-
Layout(name="body"),
|
|
101
|
+
Layout(name="body", ratio=2),
|
|
102
|
+
Layout(name="output", size=12),
|
|
103
|
+
Layout(name="input", size=3),
|
|
69
104
|
Layout(name="footer", size=3),
|
|
70
105
|
)
|
|
71
106
|
layout["body"].split_row(
|
|
@@ -76,6 +111,14 @@ def build_layout():
|
|
|
76
111
|
return layout
|
|
77
112
|
|
|
78
113
|
|
|
114
|
+
def render_input():
|
|
115
|
+
return Panel(
|
|
116
|
+
"[bold red]milenium[/bold red] [white]@[/white] [red]VexxDev200[/red] [white]~[/white] [bold white]введи команду ниже[/bold white]",
|
|
117
|
+
title="[bold red]ВВОД[/bold red]",
|
|
118
|
+
border_style="red",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
79
122
|
def render_header():
|
|
80
123
|
logo = Text.from_markup(ASCII_LOGO)
|
|
81
124
|
banner = Text.from_markup(BANNER)
|
|
@@ -160,30 +203,48 @@ def draw():
|
|
|
160
203
|
layout["left"].update(render_left())
|
|
161
204
|
layout["center"].update(render_center())
|
|
162
205
|
layout["right"].update(render_right())
|
|
206
|
+
layout["output"].update(render_output())
|
|
207
|
+
layout["input"].update(render_input())
|
|
163
208
|
layout["footer"].update(render_footer())
|
|
164
209
|
return layout
|
|
165
210
|
|
|
166
211
|
|
|
167
212
|
def interactive():
|
|
168
213
|
console.clear()
|
|
169
|
-
|
|
214
|
+
push_output("сессия запущена")
|
|
215
|
+
with Live(draw(), refresh_per_second=4) as live:
|
|
170
216
|
while True:
|
|
171
217
|
try:
|
|
218
|
+
live.stop()
|
|
172
219
|
cmd = Prompt.ask("[bold red]milenium[/bold red] [white]@[/white] [red]VexxDev200[/red] [white]~[/white]")
|
|
220
|
+
live.start()
|
|
221
|
+
|
|
173
222
|
if cmd.strip().lower() in ("exit", "quit", "q"):
|
|
174
223
|
break
|
|
175
224
|
if not cmd.strip():
|
|
225
|
+
live.update(draw())
|
|
176
226
|
continue
|
|
227
|
+
|
|
177
228
|
SESSION["queries"] += 1
|
|
229
|
+
push_output(f"выполняю: {cmd}")
|
|
230
|
+
|
|
178
231
|
import shlex
|
|
232
|
+
import io
|
|
233
|
+
import contextlib
|
|
179
234
|
from milenium.cli import main
|
|
235
|
+
|
|
180
236
|
args = shlex.split(cmd)
|
|
237
|
+
buf = io.StringIO()
|
|
181
238
|
try:
|
|
182
|
-
|
|
239
|
+
with contextlib.redirect_stdout(buf):
|
|
240
|
+
main(args, standalone_mode=False)
|
|
241
|
+
for line in buf.getvalue().splitlines():
|
|
242
|
+
if line.strip():
|
|
243
|
+
push_output(line)
|
|
183
244
|
except SystemExit:
|
|
184
245
|
pass
|
|
185
246
|
except Exception as e:
|
|
186
|
-
|
|
247
|
+
push_output(f"[red]ошибка: {e}[/red]")
|
|
187
248
|
live.update(draw())
|
|
188
249
|
except KeyboardInterrupt:
|
|
189
250
|
break
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: milenium
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: OSINT / dox / deanon toolkit
|
|
5
5
|
Requires-Python: >=3.9
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -13,3 +13,4 @@ Requires-Dist: click
|
|
|
13
13
|
Requires-Dist: python-whois
|
|
14
14
|
|
|
15
15
|
# milenium
|
|
16
|
+
# exiftool
|
|
@@ -18,16 +18,30 @@ milenium/modules/dns_history.py
|
|
|
18
18
|
milenium/modules/domain.py
|
|
19
19
|
milenium/modules/dorker.py
|
|
20
20
|
milenium/modules/email.py
|
|
21
|
+
milenium/modules/email_validator.py
|
|
22
|
+
milenium/modules/exif_extractor.py
|
|
23
|
+
milenium/modules/github_search.py
|
|
24
|
+
milenium/modules/gravatar.py
|
|
21
25
|
milenium/modules/hibp.py
|
|
26
|
+
milenium/modules/instagram_osint.py
|
|
22
27
|
milenium/modules/ip.py
|
|
28
|
+
milenium/modules/ip_geo_advanced.py
|
|
23
29
|
milenium/modules/ip_reputation.py
|
|
24
30
|
milenium/modules/leakcheck.py
|
|
25
31
|
milenium/modules/logger.py
|
|
32
|
+
milenium/modules/password_pattern.py
|
|
26
33
|
milenium/modules/permutator.py
|
|
34
|
+
milenium/modules/pgp_lookup.py
|
|
27
35
|
milenium/modules/phone.py
|
|
28
36
|
milenium/modules/pwned_passwords.py
|
|
29
37
|
milenium/modules/report.py
|
|
38
|
+
milenium/modules/reverse_whois.py
|
|
30
39
|
milenium/modules/shodan_lite.py
|
|
31
40
|
milenium/modules/social.py
|
|
32
41
|
milenium/modules/telegram_osint.py
|
|
33
|
-
milenium/modules/
|
|
42
|
+
milenium/modules/tg_parser.py
|
|
43
|
+
milenium/modules/twitter_osint.py
|
|
44
|
+
milenium/modules/username.py
|
|
45
|
+
milenium/modules/vk_osint.py
|
|
46
|
+
milenium/modules/wayback.py
|
|
47
|
+
milenium/modules/wifi_geolocation.py
|
milenium-0.4.2/README.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# milenium
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from milenium.modules import pwned_passwords, breach_local, hibp, dehashed, leakcheck
|
|
2
|
-
|
|
3
|
-
def run(email=None, username=None, phone=None, password=None, db_path=None):
|
|
4
|
-
out = {}
|
|
5
|
-
|
|
6
|
-
if password:
|
|
7
|
-
out["pwned_passwords"] = pwned_passwords.check_password(password)
|
|
8
|
-
|
|
9
|
-
if email:
|
|
10
|
-
out["hibp"] = hibp.check_email(email)
|
|
11
|
-
out["dehashed_email"] = dehashed.search(f'email:"{email}"')
|
|
12
|
-
out["leakcheck_email"] = leakcheck.search(email)
|
|
13
|
-
|
|
14
|
-
if username:
|
|
15
|
-
out["dehashed_username"] = dehashed.search(f'username:"{username}"')
|
|
16
|
-
out["leakcheck_username"] = leakcheck.search(username)
|
|
17
|
-
|
|
18
|
-
if phone:
|
|
19
|
-
out["leakcheck_phone"] = leakcheck.search(phone)
|
|
20
|
-
|
|
21
|
-
if db_path:
|
|
22
|
-
q = email or username or phone
|
|
23
|
-
if q:
|
|
24
|
-
out["breach_local"] = breach_local.search(q, db_path)
|
|
25
|
-
|
|
26
|
-
return out
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|