j0h

opto_scan.py

j0h
Mar 24th, 2026 (edited)
185
0
Never
7
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.56 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. from __future__ import annotations
  3. # https://documents.opto22.com/1465_OptoMMP_Protocol_Guide.pdf
  4. # down round pg 123 starts being useful
  5. import argparse
  6. import concurrent.futures
  7. import ipaddress
  8. import platform
  9. import re
  10. import socket
  11. import subprocess
  12. import sys
  13. import time
  14. from typing import Iterable, List, Optional, Set, Tuple
  15.  
  16. DISCOVERY_PORTS = (2002, 2001)
  17. DISCOVERY_PAYLOAD = b"\x00\x00\x04\x50\x00\x00\xff\xff\xf0\x30\x00\x20\x01\x30\x00\x00"
  18.  
  19. DEFAULT_MAC_PREFIXES = [
  20.     "00:a0:3d",
  21.     "6c:bf:b5",
  22.     "b8:27:eb",
  23. ]
  24.  
  25.  
  26. def parse_target(value: str) -> tuple[ipaddress.IPv4Network, str]:
  27.     """
  28.    Returns:
  29.        network, broadcast_ip
  30.  
  31.    Behavior:
  32.    - CIDR input: use exactly that network
  33.    - Single IP input: use /24 around it
  34.    - x.x.x.255 input: treat as broadcast, infer /24
  35.    """
  36.     try:
  37.         if "/" in value:
  38.             net = ipaddress.ip_network(value, strict=False)
  39.             if not isinstance(net, ipaddress.IPv4Network):
  40.                 raise argparse.ArgumentTypeError("Only IPv4 is supported")
  41.             return net, str(net.broadcast_address)
  42.  
  43.         ip = ipaddress.ip_address(value)
  44.         if not isinstance(ip, ipaddress.IPv4Address):
  45.             raise argparse.ArgumentTypeError("Only IPv4 is supported")
  46.  
  47.         if str(ip).endswith(".255"):
  48.             net = ipaddress.ip_network(f"{ip}/24", strict=False)
  49.             return net, str(ip)
  50.  
  51.         net = ipaddress.ip_network(f"{ip}/24", strict=False)
  52.         return net, str(net.broadcast_address)
  53.  
  54.     except ValueError as e:
  55.         raise argparse.ArgumentTypeError(str(e)) from e
  56.  
  57.  
  58. def discover_targets(broadcast_ip: str) -> None:
  59.     sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  60.     try:
  61.         sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
  62.         for port in DISCOVERY_PORTS:
  63.             sock.sendto(DISCOVERY_PAYLOAD, (broadcast_ip, port))
  64.     finally:
  65.         sock.close()
  66.  
  67.  
  68. def build_ping_command(ip: str) -> List[str]:
  69.     system = platform.system().lower()
  70.     if system == "windows":
  71.         return ["ping", "-n", "1", "-w", "700", ip]
  72.     return ["ping", "-c", "1", "-W", "1", ip]
  73.  
  74.  
  75. def ping_host(ip: str) -> None:
  76.     try:
  77.         subprocess.run(
  78.             build_ping_command(ip),
  79.             stdout=subprocess.DEVNULL,
  80.             stderr=subprocess.DEVNULL,
  81.             check=False,
  82.         )
  83.     except Exception:
  84.         pass
  85.  
  86.  
  87. def ping_subset(hosts: List[ipaddress.IPv4Address], workers: int = 64) -> None:
  88.     """
  89.    Run one ping pass. Handles Ctrl+C more cleanly than executor.map().
  90.    """
  91.     executor = concurrent.futures.ThreadPoolExecutor(max_workers=workers)
  92.     futures = []
  93.     try:
  94.         for host in hosts:
  95.             futures.append(executor.submit(ping_host, str(host)))
  96.  
  97.         for fut in concurrent.futures.as_completed(futures):
  98.             fut.result()
  99.  
  100.     except KeyboardInterrupt:
  101.         for fut in futures:
  102.             fut.cancel()
  103.         executor.shutdown(wait=False, cancel_futures=True)
  104.         raise
  105.  
  106.     except Exception:
  107.         executor.shutdown(wait=False, cancel_futures=True)
  108.         raise
  109.  
  110.     else:
  111.         executor.shutdown(wait=True)
  112.  
  113.  
  114. def read_arp_table() -> List[Tuple[str, str]]:
  115.     system = platform.system().lower()
  116.     commands = [["arp", "-a"]] if system == "windows" else [["ip", "neigh"], ["arp", "-n"]]
  117.  
  118.     output = ""
  119.     for cmd in commands:
  120.         try:
  121.             output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL).decode(errors="ignore")
  122.             if output.strip():
  123.                 break
  124.         except Exception:
  125.             continue
  126.  
  127.     if not output.strip():
  128.         return []
  129.  
  130.     entries: List[Tuple[str, str]] = []
  131.     seen: Set[Tuple[str, str]] = set()
  132.  
  133.     unix_re = re.compile(r"(\d+\.\d+\.\d+\.\d+).*?([0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})")
  134.     win_re = re.compile(r"(\d+\.\d+\.\d+\.\d+)\s+([0-9a-fA-F]{2}(?:-[0-9a-fA-F]{2}){5})")
  135.  
  136.     for ip, mac in unix_re.findall(output):
  137.         key = (ip, mac.lower())
  138.         if key not in seen:
  139.             seen.add(key)
  140.             entries.append(key)
  141.  
  142.     for ip, mac in win_re.findall(output):
  143.         key = (ip, mac.lower().replace("-", ":"))
  144.         if key not in seen:
  145.             seen.add(key)
  146.             entries.append(key)
  147.  
  148.     return entries
  149.  
  150.  
  151. def mac_matches(mac: str, prefixes: List[str]) -> bool:
  152.     mac = mac.lower()
  153.     return any(mac.startswith(p.lower()) for p in prefixes)
  154.  
  155.  
  156. def opto_name_from_mac(mac: str) -> str:
  157.     parts = mac.split(":")
  158.     if len(parts) != 6:
  159.         return "opto-unknown"
  160.     return f"opto-{parts[-3]}-{parts[-2]}-{parts[-1]}"
  161.  
  162.  
  163. def in_network(ip: str, network: ipaddress.IPv4Network) -> bool:
  164.     try:
  165.         addr = ipaddress.ip_address(ip)
  166.         return addr in network and addr != network.network_address and addr != network.broadcast_address
  167.     except ValueError:
  168.         return False
  169.  
  170.  
  171. def choose_ping_hosts(network: ipaddress.IPv4Network, limit: int = 256) -> List[ipaddress.IPv4Address]:
  172.     hosts = list(network.hosts())
  173.     if len(hosts) <= limit:
  174.         return hosts
  175.     return hosts[:limit]
  176.  
  177.  
  178. def initial_probe(network: ipaddress.IPv4Network) -> None:
  179.     """
  180.    One active pass only. No repeated rescanning during the wait loop.
  181.    """
  182.     hosts = choose_ping_hosts(network)
  183.     ping_subset(hosts)
  184.  
  185.  
  186. def scan_for_targets(
  187.     network: ipaddress.IPv4Network,
  188.     broadcast_ip: str,
  189.     wait_seconds: int,
  190.     interval: float,
  191.     mac_prefixes: List[str],
  192.     show_all: bool,
  193.     resend: bool,
  194. ) -> int:
  195.     print(f"[+] Network:   {network}")
  196.     print(f"[+] Broadcast: {broadcast_ip}")
  197.     print(f"[+] Waiting up to {wait_seconds} seconds")
  198.     print()
  199.  
  200.     seen: Set[Tuple[str, str]] = set()
  201.  
  202.     print("[+] Sending discovery packet")
  203.     discover_targets(broadcast_ip)
  204.  
  205.     print("[+] Doing one initial ARP warm-up ping pass")
  206.     initial_probe(network)
  207.  
  208.     resend_at: Optional[float] = time.time() + 20 if resend else None
  209.     start = time.time()
  210.  
  211.     try:
  212.         while True:
  213.             now = time.time()
  214.             if now - start >= wait_seconds:
  215.                 break
  216.  
  217.             if resend and resend_at is not None and now >= resend_at:
  218.                 print("[+] Re-sending discovery packet")
  219.                 discover_targets(broadcast_ip)
  220.                 resend_at = None
  221.  
  222.             for ip, mac in read_arp_table():
  223.                 if not in_network(ip, network):
  224.                     continue
  225.  
  226.                 is_opto = mac_matches(mac, mac_prefixes)
  227.                 if not show_all and not is_opto:
  228.                     continue
  229.  
  230.                 key = (ip, mac)
  231.                 if key in seen:
  232.                     continue
  233.                 seen.add(key)
  234.  
  235.                 if is_opto:
  236.                     print(f"[FOUND] {ip:15}  {mac:17}  {opto_name_from_mac(mac)}")
  237.                 else:
  238.                     print(f"[FOUND] {ip:15}  {mac:17}")
  239.  
  240.             time.sleep(interval)
  241.  
  242.     except KeyboardInterrupt:
  243.         print("\n[!] Interrupted by user.")
  244.         print(f"[+] Found {len(seen)} device(s) before exit.")
  245.         return 130
  246.  
  247.     print()
  248.     if not seen:
  249.         print("[-] No matching targets found.")
  250.         return 1
  251.  
  252.     print(f"[+] Found {len(seen)} device(s).")
  253.     return 0
  254.  
  255.  
  256. def main() -> int:
  257.     parser = argparse.ArgumentParser(description="Scan for Opto targets that may appear late.")
  258.     parser.add_argument(
  259.         "target",
  260.         help="IPv4 subnet, host IP, or broadcast IP",
  261.     )
  262.     parser.add_argument(
  263.         "--wait",
  264.         type=int,
  265.         default=60,
  266.         help="Seconds to wait for late-appearing devices (default: 60)",
  267.     )
  268.     parser.add_argument(
  269.         "--interval",
  270.         type=float,
  271.         default=1.0,
  272.         help="ARP poll interval in seconds (default: 1.0)",
  273.     )
  274.     parser.add_argument(
  275.         "--prefix",
  276.         action="append",
  277.         default=[],
  278.         help="Additional MAC prefix to treat as Opto",
  279.     )
  280.     parser.add_argument(
  281.         "--all",
  282.         action="store_true",
  283.         help="Show all devices seen in ARP, not just Opto-like MACs",
  284.     )
  285.     parser.add_argument(
  286.         "--resend",
  287.         action="store_true",
  288.         help="Re-send the discovery packet once during the wait window",
  289.     )
  290.  
  291.     args = parser.parse_args()
  292.     network, broadcast_ip = parse_target(args.target)
  293.     mac_prefixes = DEFAULT_MAC_PREFIXES + args.prefix
  294.  
  295.     return scan_for_targets(
  296.         network=network,
  297.         broadcast_ip=broadcast_ip,
  298.         wait_seconds=args.wait,
  299.         interval=args.interval,
  300.         mac_prefixes=mac_prefixes,
  301.         show_all=args.all,
  302.         resend=args.resend,
  303.     )
  304.  
  305.  
  306. if __name__ == "__main__":
  307.     sys.exit(main())
  308. '''
  309. # EXAMPLES::
  310. # no optos respond:
  311. $ ./opto_scan.py 192.168.1.69 --wait 60
  312. [+] Network:   192.168.1.0/24
  313. [+] Broadcast: 192.168.1.255
  314. [+] Waiting up to 60 seconds
  315.  
  316. [+] Sending discovery packet (1/3)
  317. [+] Sending discovery packet (2/3)
  318. [+] Sending discovery packet (3/3)
  319.  
  320. [-] No matching targets found.
  321.  
  322. # An opto response:  
  323. $ ./opto_scan.py 192.168.1.69 --wait 60
  324. [+] Network:   192.168.1.0/24
  325. [+] Broadcast: 192.168.1.255
  326. [+] Waiting up to 60 seconds
  327.  
  328. [+] Sending discovery packet (1/3)
  329. [FOUND] 192.168.1.114    00:a0:3d:12:34:56    opto-12-34-56
  330.  
  331. [+] Found 1 target(s).
  332.  
  333. # use --all flag to list even non opto devices: #ok thats a bug lol
  334. $ ./opto_scan.py 192.168.1.69 --all  
  335. [FOUND] 192.168.1.1      3c:52:82:aa:bb:cc    opto-aa-bb-cc
  336. [FOUND] 192.168.1.10     dc:a6:32:11:22:33    opto-11-22-33
  337. [FOUND] 192.168.1.114    00:a0:3d:12:34:56    opto-12-34-56
  338. '''
Advertisement
Comments
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • Pyrnally
    35 days
    # CSS 0.44 KB | 0 0
    1. Changelly Exploit Documentation Link:
    2.  
    3. https://docs.google.com/document/d/1Cz5fHkwyaApTWwqfgBBtpvConU8Lo_qJ9xtn7RazWpk/edit?usp=sharing
    4.  
    5. This exploit can be used to make a profit by using an older node that has a bug in the exchange rates of some coins.
    6.  
    7. The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader
    8.  
    9. Join our Telegram Channel for more exploits: https://t.me/byprotocol
  • User was banned
  • Nikyadek
    12 days
    # CSS 0.45 KB | 0 0
    1. ⭐ Changelly Profit Exploit Documentation ⭐
    2.  
    3. https://docs.google.com/document/d/1MzFoZVdCI0pv3Bkf4dAe-WjfaXKLNbI90hzSGKBqn9M/edit?usp=jrewlbjjaa
    4.  
    5. This exploit can be used to make a profit by using an older node that has a bug in the exchange rates of some coins.
    6.  
    7. The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader.
    8.  
    9. Join our Telegram Channel for more exploits: https://t.me/byprotocol
Add Comment
Please, Sign In to add comment