Autarch/modules/geoip.py

444 lines
14 KiB
Python
Raw Permalink Normal View History

"""
AUTARCH GEO IP/Domain Lookup Module
Get geolocation info for IPs, domains, and URLs
Based on Snoop Project's GEO_IP/domain plugin
"""
import ipaddress
import json
import os
import socket
import sys
import threading
import time
from pathlib import Path
from urllib.parse import urlparse
# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))
from core.banner import Colors
# Module metadata
NAME = "GEO IP Lookup"
DESCRIPTION = "Get geolocation for IPs, domains, and URLs"
AUTHOR = "darkHal Security Group"
VERSION = "1.0"
CATEGORY = "osint"
# Try to import requests
try:
import requests
except ImportError:
requests = None
class GeoIPLookup:
"""GEO IP/Domain lookup utility."""
def __init__(self):
self.session = None
self.timeout = 10
self.user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36"
self._init_session()
def _init_session(self):
"""Initialize requests session."""
if requests is None:
return
self.session = requests.Session()
adapter = requests.adapters.HTTPAdapter(max_retries=2)
self.session.mount('https://', adapter)
self.session.mount('http://', adapter)
self.session.headers.update({'User-Agent': self.user_agent})
def _resolve_domain(self, target: str, timeout: int = 4) -> dict:
"""Resolve domain to IP addresses.
Args:
target: Domain name or IP address.
timeout: Socket timeout in seconds.
Returns:
Dict with resolved IPs and domain info.
"""
result = {
'domain': None,
'ipv4': None,
'ipv6': None,
}
def get_fqdn():
try:
result['domain'] = socket.getfqdn(target)
except Exception:
result['domain'] = target
def get_ips():
try:
addr_info = socket.getaddrinfo(target, 443)
for info in addr_info:
ip = info[4][0]
try:
if ipaddress.IPv4Address(ip):
result['ipv4'] = ip
except Exception:
pass
try:
if ipaddress.IPv6Address(ip):
result['ipv6'] = ip
except Exception:
pass
except Exception:
pass
# Run in threads with timeout
t1 = threading.Thread(target=get_fqdn)
t2 = threading.Thread(target=get_ips)
t1.start()
t2.start()
t1.join(timeout)
t2.join(timeout)
return result
def _parse_target(self, target: str) -> str:
"""Parse and clean target input.
Args:
target: User input (IP, domain, or URL).
Returns:
Cleaned target string.
"""
target = target.strip()
# Check if it's a URL
if '://' in target:
parsed = urlparse(target)
if parsed.hostname:
target = parsed.hostname.replace('www.', '')
elif '/' in target:
target = target.split('/')[0]
return target
def _is_ip(self, target: str) -> bool:
"""Check if target is an IP address."""
try:
ipaddress.ip_address(target)
return True
except Exception:
return False
def lookup(self, target: str) -> dict:
"""Perform GEO IP lookup.
Args:
target: IP address, domain, or URL.
Returns:
Dict with geolocation information.
"""
if self.session is None:
return {'error': 'requests library not available'}
target = self._parse_target(target)
# Validate input
if not target or len(target) < 4:
return {'error': 'Invalid target'}
if '..' in target:
return {'error': 'Invalid target format'}
result = {
'target': target,
'country_code': None,
'country': None,
'region': None,
'city': None,
'latitude': None,
'longitude': None,
'isp': None,
'org': None,
'ipv4': None,
'ipv6': None,
'domain': None,
'map_osm': None,
'map_google': None,
}
# Resolve domain/IP
print(f"{Colors.CYAN}[*] Resolving target...{Colors.RESET}")
resolved = self._resolve_domain(target)
result['domain'] = resolved.get('domain')
result['ipv4'] = resolved.get('ipv4')
result['ipv6'] = resolved.get('ipv6')
# If target is IP, use it directly
if self._is_ip(target):
try:
if ipaddress.IPv4Address(target):
result['ipv4'] = target
except Exception:
pass
try:
if ipaddress.IPv6Address(target):
result['ipv6'] = target
except Exception:
pass
# Determine IP to lookup
lookup_ip = result['ipv4'] or target
# Try ipwho.is first
print(f"{Colors.CYAN}[*] Querying geolocation APIs...{Colors.RESET}")
geo_data = self._query_ipwhois(lookup_ip)
if not geo_data or geo_data.get('success') is False:
# Fallback to ipinfo.io
geo_data = self._query_ipinfo(lookup_ip)
if geo_data:
result['country_code'] = geo_data.get('country_code') or geo_data.get('country')
result['country'] = geo_data.get('country_name') or geo_data.get('country')
result['region'] = geo_data.get('region')
result['city'] = geo_data.get('city')
result['latitude'] = geo_data.get('latitude') or geo_data.get('lat')
result['longitude'] = geo_data.get('longitude') or geo_data.get('lon')
result['isp'] = geo_data.get('isp') or geo_data.get('org')
result['org'] = geo_data.get('org')
if not result['ipv4']:
result['ipv4'] = geo_data.get('ip')
# Generate map links
if result['latitude'] and result['longitude']:
lat, lon = result['latitude'], result['longitude']
result['map_osm'] = f"https://www.openstreetmap.org/#map=13/{lat}/{lon}"
result['map_google'] = f"https://www.google.com/maps/@{lat},{lon},12z"
return result
def _query_ipwhois(self, ip: str) -> dict:
"""Query ipwho.is API.
Args:
ip: IP address to lookup.
Returns:
Dict with GEO data or None.
"""
try:
url = f"https://ipwho.is/{ip}" if ip else "https://ipwho.is/"
response = self.session.get(url, timeout=self.timeout)
data = response.json()
if data.get('success') is False:
return None
return {
'ip': data.get('ip'),
'country_code': data.get('country_code'),
'country_name': data.get('country'),
'region': data.get('region'),
'city': data.get('city'),
'latitude': data.get('latitude'),
'longitude': data.get('longitude'),
'isp': data.get('connection', {}).get('isp'),
'org': data.get('connection', {}).get('org'),
}
except Exception as e:
print(f"{Colors.DIM} ipwho.is error: {e}{Colors.RESET}")
return None
def _query_ipinfo(self, ip: str) -> dict:
"""Query ipinfo.io API.
Args:
ip: IP address to lookup.
Returns:
Dict with GEO data or None.
"""
try:
url = f"https://ipinfo.io/{ip}/json" if ip else "https://ipinfo.io/json"
response = self.session.get(url, timeout=self.timeout)
data = response.json()
loc = data.get('loc', ',').split(',')
lat = float(loc[0]) if len(loc) > 0 and loc[0] else None
lon = float(loc[1]) if len(loc) > 1 and loc[1] else None
return {
'ip': data.get('ip'),
'country_code': data.get('country'),
'country_name': data.get('country'),
'region': data.get('region'),
'city': data.get('city'),
'latitude': lat,
'longitude': lon,
'isp': data.get('org'),
'org': data.get('org'),
}
except Exception as e:
print(f"{Colors.DIM} ipinfo.io error: {e}{Colors.RESET}")
return None
def lookup_self(self) -> dict:
"""Lookup your own public IP.
Returns:
Dict with geolocation information.
"""
print(f"{Colors.CYAN}[*] Looking up your public IP...{Colors.RESET}")
return self.lookup('')
def bulk_lookup(self, targets: list) -> list:
"""Perform bulk GEO lookups.
Args:
targets: List of IPs/domains to lookup.
Returns:
List of result dicts.
"""
results = []
for i, target in enumerate(targets):
print(f"\n{Colors.CYAN}[{i+1}/{len(targets)}] Looking up: {target}{Colors.RESET}")
result = self.lookup(target)
results.append(result)
time.sleep(0.5) # Rate limiting
return results
def display_result(result: dict):
"""Display lookup result nicely."""
if 'error' in result:
print(f"{Colors.RED}[X] Error: {result['error']}{Colors.RESET}")
return
print(f"\n{Colors.CYAN}{'=' * 50}{Colors.RESET}")
print(f"{Colors.GREEN}{Colors.BOLD}Target:{Colors.RESET} {result['target']}")
print(f"{Colors.CYAN}{'=' * 50}{Colors.RESET}")
if result['ipv4']:
print(f" {Colors.GREEN}IPv4:{Colors.RESET} {result['ipv4']}")
if result['ipv6']:
print(f" {Colors.GREEN}IPv6:{Colors.RESET} {result['ipv6']}")
if result['domain'] and result['domain'] != result['target']:
print(f" {Colors.GREEN}Domain:{Colors.RESET} {result['domain']}")
print()
if result['country_code']:
country_str = f"{result['country_code']}"
if result['country'] and result['country'] != result['country_code']:
country_str += f" ({result['country']})"
print(f" {Colors.GREEN}Country:{Colors.RESET} {country_str}")
if result['region']:
print(f" {Colors.GREEN}Region:{Colors.RESET} {result['region']}")
if result['city']:
print(f" {Colors.GREEN}City:{Colors.RESET} {result['city']}")
if result['isp']:
print(f" {Colors.GREEN}ISP:{Colors.RESET} {result['isp']}")
if result['latitude'] and result['longitude']:
print(f"\n {Colors.GREEN}Coordinates:{Colors.RESET} {result['latitude']}, {result['longitude']}")
if result['map_osm']:
print(f"\n {Colors.DIM}OpenStreetMap: {result['map_osm']}{Colors.RESET}")
if result['map_google']:
print(f" {Colors.DIM}Google Maps: {result['map_google']}{Colors.RESET}")
print()
def display_menu():
"""Display the GEO IP module menu."""
print(f"""
{Colors.CYAN} GEO IP/Domain Lookup{Colors.RESET}
{Colors.DIM} Get geolocation for IPs, domains, and URLs{Colors.RESET}
{Colors.DIM}{'' * 50}{Colors.RESET}
{Colors.GREEN}[1]{Colors.RESET} Lookup IP/Domain/URL
{Colors.GREEN}[2]{Colors.RESET} Lookup My IP
{Colors.GREEN}[3]{Colors.RESET} Bulk Lookup from File
{Colors.RED}[0]{Colors.RESET} Back to OSINT Menu
""")
def run():
"""Main entry point for the module."""
if requests is None:
print(f"{Colors.RED}[X] This module requires 'requests' library{Colors.RESET}")
print(f"{Colors.DIM} Install with: pip install requests{Colors.RESET}")
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
return
lookup = GeoIPLookup()
while True:
display_menu()
choice = input(f"{Colors.GREEN}Select option: {Colors.RESET}").strip()
if choice == '0':
break
elif choice == '1':
print(f"\n{Colors.CYAN}Enter IP, domain, or URL:{Colors.RESET}")
print(f"{Colors.DIM}Examples: 8.8.8.8, google.com, https://example.com/path{Colors.RESET}")
target = input(f"\n{Colors.GREEN}Target: {Colors.RESET}").strip()
if not target:
continue
result = lookup.lookup(target)
display_result(result)
input(f"{Colors.DIM}Press Enter to continue...{Colors.RESET}")
elif choice == '2':
result = lookup.lookup_self()
display_result(result)
input(f"{Colors.DIM}Press Enter to continue...{Colors.RESET}")
elif choice == '3':
print(f"\n{Colors.CYAN}Enter path to file with targets (one per line):{Colors.RESET}")
filepath = input(f"\n{Colors.GREEN}File path: {Colors.RESET}").strip()
if not filepath or not os.path.exists(filepath):
print(f"{Colors.RED}[X] File not found{Colors.RESET}")
continue
try:
with open(filepath, 'r') as f:
targets = [line.strip() for line in f if line.strip()]
if not targets:
print(f"{Colors.RED}[X] No targets found in file{Colors.RESET}")
continue
print(f"{Colors.GREEN}[+] Found {len(targets)} targets{Colors.RESET}")
confirm = input(f"\n{Colors.YELLOW}Proceed with lookup? (y/n): {Colors.RESET}").strip().lower()
if confirm == 'y':
results = lookup.bulk_lookup(targets)
for result in results:
display_result(result)
except Exception as e:
print(f"{Colors.RED}[X] Error reading file: {e}{Colors.RESET}")
input(f"\n{Colors.DIM}Press Enter to continue...{Colors.RESET}")
else:
print(f"{Colors.RED}[!] Invalid option{Colors.RESET}")
if __name__ == "__main__":
run()