Last active
May 6, 2025 12:52
-
-
Save pondahai/e3d314f6e74454ae5de3932ed1aee616 to your computer and use it in GitHub Desktop.
from MAC addr search IP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import subprocess | |
import re | |
import sys | |
import ipaddress | |
import netifaces | |
from concurrent.futures import ThreadPoolExecutor | |
from datetime import datetime | |
from typing import List, Tuple | |
def get_all_networks() -> List[Tuple[str, str]]: | |
"""獲取所有非回環的 IPv4 網路介面及其網段""" | |
networks = [] | |
try: | |
for iface in netifaces.interfaces(): | |
addrs = netifaces.ifaddresses(iface) | |
if netifaces.AF_INET not in addrs: | |
continue | |
for addr_info in addrs[netifaces.AF_INET]: | |
ip = addr_info.get('addr', '') | |
netmask = addr_info.get('netmask', '') | |
if not ip or ip.startswith('127.'): | |
continue | |
try: | |
network = ipaddress.IPv4Network(f"{ip}/{netmask}", strict=False) | |
networks.append((iface, str(network))) | |
except ValueError as e: | |
print(f"介面 {iface} 網段計算錯誤: {e}", file=sys.stderr) | |
except Exception as e: | |
print(f"獲取網路介面失敗: {e}", file=sys.stderr) | |
return networks | |
def select_network(networks: List[Tuple[str, str]]) -> str: | |
"""讓使用者選擇要掃描的網段""" | |
if not networks: | |
print("警告: 無法自動檢測網段,使用預設值 192.168.1.0/24", file=sys.stderr) | |
return '192.168.1.0/24' | |
if len(networks) == 1: | |
return networks[0][1] | |
print("\n檢測到多個網路介面:") | |
for i, (iface, network) in enumerate(networks, 1): | |
print(f"{i}. 介面 {iface} - 網段 {network}") | |
print(f"{len(networks)+1}. 掃描所有以上網段") | |
print(f"{len(networks)+2}. 手動輸入網段") | |
while True: | |
try: | |
choice = input("請選擇要掃描的網段 (編號): ").strip() | |
if not choice: | |
return networks[0][1] | |
choice = int(choice) | |
if 1 <= choice <= len(networks): | |
return networks[choice-1][1] | |
elif choice == len(networks)+1: | |
return 'multiple' | |
elif choice == len(networks)+2: | |
manual_input = input("請手動輸入要掃描的網段 (如 192.168.1.0/24): ").strip() | |
try: | |
# 驗證輸入的網段是否有效 | |
ipaddress.IPv4Network(manual_input) | |
return manual_input | |
except ValueError: | |
print("無效的網段格式,請重新輸入", file=sys.stderr) | |
continue | |
except (ValueError, IndexError): | |
print("無效輸入,請重新選擇", file=sys.stderr) | |
def normalize_mac(mac_input: str) -> str: | |
"""將不同格式的 MAC 地址轉換為統一格式""" | |
mac_clean = re.sub(r'[^a-fA-F0-9]', '', mac_input.lower()) | |
return mac_clean | |
def ping_host(ip: str) -> str: | |
"""Ping 單個主機""" | |
param = '-n' if sys.platform.lower() == 'win32' else '-c' | |
timeout = '-w' if sys.platform.lower() == 'win32' else '-W' | |
try: | |
subprocess.run( | |
['ping', param, '1', timeout, '500', str(ip)], | |
check=True, | |
stdout=subprocess.DEVNULL, | |
stderr=subprocess.DEVNULL | |
) | |
return str(ip) | |
except: | |
return None | |
def scan_network(network_cidr: str) -> List[str]: | |
"""主動掃描整個網段""" | |
print(f"\n正在掃描網段 {network_cidr}...") | |
try: | |
network = ipaddress.ip_network(network_cidr) | |
except ValueError as e: | |
print(f"無效的網段: {e}", file=sys.stderr) | |
return [] | |
live_hosts = [] | |
with ThreadPoolExecutor(max_workers=50) as executor: | |
futures = [executor.submit(ping_host, str(host)) for host in network.hosts()] | |
for future in futures: | |
result = future.result() | |
if result: | |
live_hosts.append(result) | |
print(f"掃描完成,找到 {len(live_hosts)} 個活動主機") | |
return live_hosts | |
def get_arp_table(use_cache: bool = True) -> List[Tuple[str, str]]: | |
"""獲取 ARP 表,可選使用快取""" | |
if use_cache and hasattr(get_arp_table, 'cache') and \ | |
(datetime.now() - get_arp_table.last_updated).seconds < 300: | |
return get_arp_table.cache | |
try: | |
result = subprocess.run(['arp', '-a'], capture_output=True, text=True) | |
arp_output = result.stdout | |
except Exception as e: | |
print(f"執行 arp 命令時出錯: {e}", file=sys.stderr) | |
return [] | |
arp_entries = [] | |
for line in arp_output.split('\n'): | |
line = line.strip() | |
if not line: | |
continue | |
# Windows 格式 | |
win_match = re.match(r'^([\d.]+)\s+([0-9a-fA-F-]+)\s+', line) | |
if win_match: | |
ip = win_match.group(1) | |
mac = normalize_mac(win_match.group(2)) | |
arp_entries.append((ip, mac)) | |
continue | |
# Unix 格式 | |
unix_match = re.match(r'.*\(([\d.]+)\)\s+at\s+([0-9a-fA-F:]+)\s+', line) | |
if unix_match: | |
ip = unix_match.group(1) | |
mac = normalize_mac(unix_match.group(2)) | |
arp_entries.append((ip, mac)) | |
# 更新快取 | |
get_arp_table.cache = arp_entries | |
get_arp_table.last_updated = datetime.now() | |
return arp_entries | |
def search_mac( | |
mac_part: str, | |
networks: List[str], | |
scan_type: str = 'auto' | |
) -> List[Tuple[str, str]]: | |
"""搜尋 MAC 地址""" | |
mac_part = normalize_mac(mac_part) | |
matches = [] | |
arp_table = [] | |
# 決定掃描方式 | |
if scan_type == 'arp' or (scan_type == 'auto' and len(mac_part) >= 6): | |
arp_table = get_arp_table() | |
matches.extend([(ip, mac) for ip, mac in arp_table if mac_part in mac]) | |
if (not matches or scan_type == 'full') and networks: | |
# 完整掃描模式 | |
if networks == ['multiple']: | |
# 特殊情況:掃描所有網段 | |
all_networks = get_all_networks() | |
networks = [net[1] for net in all_networks] if all_networks else ['192.168.1.0/24'] | |
for network in networks: | |
live_ips = scan_network(network) | |
arp_table = get_arp_table(use_cache=False) # 強制更新 ARP 表 | |
matches.extend([(ip, mac) for ip, mac in arp_table if mac_part in mac]) | |
# 去重 | |
unique_matches = [] | |
seen = set() | |
for ip, mac in matches: | |
if (ip, mac) not in seen: | |
seen.add((ip, mac)) | |
unique_matches.append((ip, mac)) | |
return unique_matches | |
def get_vendor(mac_prefix: str) -> str: | |
"""獲取 MAC 地址廠商資訊""" | |
if not mac_prefix or len(mac_prefix) < 6: | |
return "Unknown" | |
if not hasattr(get_vendor, 'cache'): | |
get_vendor.cache = {} | |
if mac_prefix in get_vendor.cache: | |
return get_vendor.cache[mac_prefix] | |
try: | |
import requests | |
url = f"https://api.macvendors.com/{mac_prefix[:6]}" | |
response = requests.get(url, timeout=2) | |
vendor = response.text if response.status_code == 200 else "Unknown" | |
get_vendor.cache[mac_prefix] = vendor | |
return vendor | |
except: | |
return "Unknown" | |
def main(): | |
print("MAC 地址搜尋工具 (多網段增強版)") | |
print("=" * 50) | |
# 獲取所有網路介面資訊 | |
networks_info = get_all_networks() | |
selected_network = select_network(networks_info) | |
# 處理網段選擇 | |
networks_to_scan = [] | |
if selected_network == 'multiple': | |
networks_to_scan = ['multiple'] | |
print("\n將掃描所有檢測到的網段") | |
else: | |
networks_to_scan = [selected_network] | |
print(f"\n選擇掃描網段: {selected_network}") | |
# 獲取輸入 | |
if len(sys.argv) < 2: | |
print("\n請輸入要搜尋的 MAC 地址(完整或部分)") | |
print("支援格式範例: b3e0, b3:e0, b3-e0, B3-E0-12-34") | |
print("掃描模式選項:") | |
print(" fast - 僅檢查 ARP 快取 (預設)") | |
print(" full - 完整掃描整個網段") | |
while True: | |
mac_input = input("\n輸入 MAC 地址 (或 q 退出): ").strip() | |
if mac_input.lower() == 'q': | |
return | |
if mac_input: | |
break | |
print("錯誤: 輸入不能為空", file=sys.stderr) | |
scan_type = input("選擇掃描模式 [fast/full] (預設 fast): ").strip().lower() | |
scan_type = scan_type if scan_type in ('fast', 'full') else 'fast' | |
else: | |
mac_input = sys.argv[1] | |
scan_type = 'fast' if len(sys.argv) < 3 else sys.argv[2].lower() | |
print(f"\n搜尋模式: {'完整掃描' if scan_type == 'full' else '快速掃描'}") | |
print(f"搜尋目標: {mac_input}") | |
# 執行搜尋 | |
matches = search_mac(mac_input, networks_to_scan, scan_type) | |
# 顯示結果 | |
if matches: | |
print("\n找到匹配的裝置:") | |
print("-" * 60) | |
print(f"{'IP地址':<15} | {'MAC地址':<17} | {'廠商資訊'}") | |
print("-" * 60) | |
for ip, mac in matches: | |
formatted_mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)]) | |
vendor = get_vendor(mac[:6]) if len(mac) >= 6 else "Unknown" | |
print(f"{ip:<15} | {formatted_mac:<17} | {vendor}") | |
else: | |
print(f"\n沒有找到 MAC 地址包含 '{mac_input}' 的裝置") | |
if __name__ == "__main__": | |
# 檢查必要依賴 | |
try: | |
import netifaces | |
except ImportError: | |
print("錯誤: 需要 netifaces 庫,請先執行: pip install netifaces", file=sys.stderr) | |
sys.exit(1) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment