netverdict 0.5.0__py3-none-any.whl

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.
netverdict/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.5.0"
@@ -0,0 +1,130 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Capture assistee netverdict (Windows) : trafic reseau + etat hote, en un coup.
4
+
5
+ .DESCRIPTION
6
+ Lance une capture pktmon (natif Windows 10+/Server 2019+, rien a installer)
7
+ et prend un snapshot de l'etat de la machine AU MILIEU de la capture :
8
+ connexions TCP avec process proprietaire, CPU, memoire, disque.
9
+ Le pcap dit ce qui passe sur le fil ; le snapshot dit QUI tenait la socket
10
+ et dans quel etat etait la machine — c'est le croisement des deux qui permet
11
+ de trancher APP vs OS.
12
+
13
+ Par defaut la capture est TRONQUEE a 128 octets/paquet : suffisant pour
14
+ l'analyse netverdict et fichiers legers. -FullPackets pour les paquets entiers.
15
+
16
+ ATTENTION : la troncature n'est PAS une garantie d'absence de secret. Elle coupe
17
+ a 128 octets DEPUIS LE DEBUT DE LA TRAME, donc tout paquet plus court est
18
+ capture EN ENTIER, payload compris. Mesure du 25/07/2026 : "PASS hunter2", un
19
+ login FTP complet et un jeton JSON court passent INTACTS. La troncature elimine
20
+ les gros transferts, pas les secrets courts — et l'authentification en clair est
21
+ precisement courte. Traiter le bundle comme une donnee sensible.
22
+
23
+ .EXAMPLE
24
+ .\capture.ps1 -DurationSec 60 -TargetIP 10.0.0.5
25
+ netverdict analyze <bundle>\capture.pcapng --snapshot <bundle>\snapshot.json
26
+ #>
27
+ [CmdletBinding()]
28
+ param(
29
+ [int]$DurationSec = 60,
30
+ [string]$OutDir = "",
31
+ [string]$TargetIP = "",
32
+ [int]$TargetPort = 0,
33
+ [switch]$FullPackets
34
+ )
35
+
36
+ $ErrorActionPreference = "Stop"
37
+
38
+ $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
39
+ ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
40
+ if (-not $isAdmin) {
41
+ Write-Error "pktmon exige une console Administrateur. Relancer en admin."
42
+ exit 2
43
+ }
44
+
45
+ if (-not $OutDir) {
46
+ $OutDir = Join-Path (Get-Location) ("netverdict-capture-" + (Get-Date -Format "yyyyMMdd-HHmmss"))
47
+ }
48
+ New-Item -ItemType Directory -Force $OutDir | Out-Null
49
+ $etl = Join-Path $OutDir "capture.etl"
50
+ $pcap = Join-Path $OutDir "capture.pcapng"
51
+ $snap = Join-Path $OutDir "snapshot.json"
52
+
53
+ # --- Filtres pktmon (reset puis cible eventuelle) ---------------------------
54
+ pktmon filter remove | Out-Null
55
+ if ($TargetIP) { pktmon filter add -i $TargetIP | Out-Null }
56
+ if ($TargetPort -gt 0) { pktmon filter add -p $TargetPort | Out-Null }
57
+
58
+ $pktSize = if ($FullPackets) { 0 } else { 128 }
59
+ Write-Host "Capture pktmon ${DurationSec}s (pkt-size=$pktSize) -> $etl"
60
+ # --comp nics : capturer au niveau des cartes reseau UNIQUEMENT. Par defaut
61
+ # pktmon capture a CHAQUE couche NDIS -> le meme paquet apparait plusieurs
62
+ # fois et fabrique de faux echantillons RTT (~0 ms) dans l'analyse
63
+ # (constate sur capture reelle). Limite connue et assumee : le trafic
64
+ # loopback pur (127.0.0.1) est invisible a pktmon de toute facon — le TCP
65
+ # Loopback Fast Path de Windows court-circuite la pile NDIS.
66
+ pktmon start --capture --comp nics --pkt-size $pktSize --file-name $etl | Out-Null
67
+
68
+ # --- Snapshot hote au milieu de la fenetre de capture -----------------------
69
+ Start-Sleep -Seconds ([math]::Max(1, [int]($DurationSec / 2)))
70
+ Write-Host "Snapshot etat hote..."
71
+
72
+ # Connexions TCP -> process. CIM plutot que Get-Counter : les noms de
73
+ # compteurs Get-Counter sont LOCALISES (piege classique des Windows FR).
74
+ $procNames = @{}
75
+ Get-Process | ForEach-Object { $procNames[$_.Id] = $_.ProcessName }
76
+ $conns = Get-NetTCPConnection -ErrorAction SilentlyContinue | ForEach-Object {
77
+ [pscustomobject]@{
78
+ local_ip = $_.LocalAddress
79
+ local_port = [int]$_.LocalPort
80
+ remote_ip = $_.RemoteAddress
81
+ remote_port = [int]$_.RemotePort
82
+ state = "$($_.State)"
83
+ pid = [int]$_.OwningProcess
84
+ process = $procNames[[int]$_.OwningProcess]
85
+ }
86
+ }
87
+
88
+ $cpu = (Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average).Average
89
+ $os = Get-CimInstance Win32_OperatingSystem
90
+ $memFreeMb = [math]::Round($os.FreePhysicalMemory / 1024, 0)
91
+ $disk = Get-CimInstance Win32_PerfFormattedData_PerfDisk_PhysicalDisk |
92
+ Where-Object Name -eq "_Total" | Select-Object -First 1
93
+ $topCpu = Get-CimInstance Win32_PerfFormattedData_PerfProc_Process |
94
+ Where-Object { $_.Name -notin @("Idle", "_Total") } |
95
+ Sort-Object PercentProcessorTime -Descending |
96
+ Select-Object -First 10 | ForEach-Object {
97
+ [pscustomobject]@{
98
+ pid = [int]$_.IDProcess
99
+ process = $_.Name
100
+ cpu_pct = [double]$_.PercentProcessorTime
101
+ }
102
+ }
103
+
104
+ $snapshot = [pscustomobject]@{
105
+ host = $env:COMPUTERNAME
106
+ os = "windows"
107
+ taken_at = (Get-Date).ToString("o")
108
+ cpu_pct = [double]$cpu
109
+ mem_free_mb = [double]$memFreeMb
110
+ disk_busy_pct = if ($disk) { [double]$disk.PercentDiskTime } else { $null }
111
+ connections = @($conns)
112
+ top_cpu = @($topCpu)
113
+ }
114
+ $snapshot | ConvertTo-Json -Depth 4 | Out-File -Encoding utf8 $snap
115
+
116
+ # --- Fin de capture et conversion -------------------------------------------
117
+ Start-Sleep -Seconds ([math]::Max(1, $DurationSec - [int]($DurationSec / 2)))
118
+ pktmon stop | Out-Null
119
+ Write-Host "Conversion etl -> pcapng..."
120
+ pktmon etl2pcap $etl -o $pcap | Out-Null
121
+ pktmon filter remove | Out-Null
122
+ Remove-Item $etl -ErrorAction SilentlyContinue
123
+
124
+ Write-Host ""
125
+ Write-Host "Bundle pret :"
126
+ Write-Host " $pcap"
127
+ Write-Host " $snap"
128
+ Write-Host ""
129
+ Write-Host "Analyse :"
130
+ Write-Host " netverdict analyze `"$pcap`" --snapshot `"$snap`""
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bash
2
+ # Capture assistee netverdict (Linux) : trafic + etat hote, en un coup.
3
+ #
4
+ # tcpdump tronque (-s 96 : de quoi lire IP+TCP+options) + snapshot au milieu de
5
+ # la fenetre : sockets avec process (ss -tnp), charge CPU, memoire, disque.
6
+ #
7
+ # ATTENTION : -s 96 n'est PAS une garantie d'absence de secret. tcpdump coupe a
8
+ # 96 octets DEPUIS LE DEBUT DE LA TRAME, donc tout paquet plus court est capture
9
+ # EN ENTIER, payload compris. Mesure du 25/07/2026 : "PASS hunter2", un login
10
+ # FTP complet et un jeton JSON court passent INTACTS. La troncature elimine les
11
+ # gros transferts, pas les secrets courts — et l'authentification en clair est
12
+ # precisement courte. Traiter le bundle comme une donnee sensible.
13
+ #
14
+ # Usage : sudo ./capture.sh [-d 60] [-o outdir] [-i host_cible] [-p port]
15
+ set -euo pipefail
16
+
17
+ DURATION=60
18
+ OUTDIR=""
19
+ TARGET_IP=""
20
+ TARGET_PORT=""
21
+
22
+ while getopts "d:o:i:p:" opt; do
23
+ case "$opt" in
24
+ d) DURATION="$OPTARG" ;;
25
+ o) OUTDIR="$OPTARG" ;;
26
+ i) TARGET_IP="$OPTARG" ;;
27
+ p) TARGET_PORT="$OPTARG" ;;
28
+ *) echo "usage: $0 [-d sec] [-o dir] [-i ip] [-p port]" >&2; exit 2 ;;
29
+ esac
30
+ done
31
+
32
+ if [ "$(id -u)" -ne 0 ]; then
33
+ echo "tcpdump exige root : relancer avec sudo." >&2
34
+ exit 2
35
+ fi
36
+ command -v tcpdump >/dev/null || { echo "tcpdump absent (apt install tcpdump)" >&2; exit 2; }
37
+ # ss etait suppose present : sur une image minimale (conteneur, Alpine) son
38
+ # absence etait avalee par un `|| true` et produisait un snapshot avec
39
+ # "connections": [] — que netverdict accepte sans broncher. Le rapport
40
+ # affichait alors un etat hote credible SANS jamais pouvoir attribuer un
41
+ # process : capacite silencieusement inerte (audit du 26/07).
42
+ command -v ss >/dev/null || { echo "ss absent (apt install iproute2) : sans lui le snapshot n'aurait aucune connexion" >&2; exit 2; }
43
+
44
+ OUTDIR="${OUTDIR:-./netverdict-capture-$(date +%Y%m%d-%H%M%S)}"
45
+ mkdir -p "$OUTDIR"
46
+ PCAP="$OUTDIR/capture.pcap"
47
+ SNAP="$OUTDIR/snapshot.json"
48
+
49
+ FILTER="tcp or icmp"
50
+ [ -n "$TARGET_IP" ] && FILTER="($FILTER) and host $TARGET_IP"
51
+ [ -n "$TARGET_PORT" ] && FILTER="($FILTER) and port $TARGET_PORT"
52
+
53
+ echo "Capture tcpdump ${DURATION}s -> $PCAP (filtre: $FILTER)"
54
+ tcpdump -i any -s 96 -w "$PCAP" $FILTER &
55
+ TCPDUMP_PID=$!
56
+ # Sans ce trap, une sortie anticipee (erreur du snapshot, Ctrl-C) laissait
57
+ # tcpdump orphelin en train d'ecrire indefiniment dans le fichier.
58
+ trap 'kill "$TCPDUMP_PID" 2>/dev/null' EXIT INT TERM
59
+
60
+ sleep "$(( DURATION / 2 > 0 ? DURATION / 2 : 1 ))"
61
+ echo "Snapshot etat hote..."
62
+
63
+ # ss -H -tnp : etat + pid/process par socket, parse en python (present partout)
64
+ ss -H -tnp > "$OUTDIR/.ss.txt" || true
65
+ python3 - "$OUTDIR/.ss.txt" "$SNAP" <<'PYEOF'
66
+ import json, os, re, sys, time
67
+
68
+ ss_file, out = sys.argv[1], sys.argv[2]
69
+ conns = []
70
+ for line in open(ss_file, encoding="utf-8", errors="replace"):
71
+ parts = line.split()
72
+ if len(parts) < 5:
73
+ continue
74
+ state, _, _, local, peer = parts[0], parts[1], parts[2], parts[3], parts[4]
75
+ m = re.search(r'pid=(\d+)', line)
76
+ proc = re.search(r'users:\(\("([^"]+)"', line)
77
+ def split_hp(s):
78
+ host, _, port = s.rpartition(":")
79
+ return host.strip("[]"), int(port) if port.isdigit() else 0
80
+ lh, lp = split_hp(local)
81
+ rh, rp = split_hp(peer)
82
+ conns.append({"local_ip": lh, "local_port": lp, "remote_ip": rh,
83
+ "remote_port": rp, "state": state,
84
+ "pid": int(m.group(1)) if m else None,
85
+ "process": proc.group(1) if proc else None})
86
+
87
+ load1 = open("/proc/loadavg").read().split()[0]
88
+ mem = {}
89
+ for l in open("/proc/meminfo"):
90
+ k, v = l.split(":", 1)
91
+ mem[k] = int(v.strip().split()[0])
92
+
93
+ snapshot = {
94
+ "host": os.uname().nodename,
95
+ "os": "linux",
96
+ "taken_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
97
+ "cpu_pct": None, # load1 ci-dessous, plus parlant sous Linux
98
+ "load1": float(load1),
99
+ "mem_free_mb": mem.get("MemAvailable", 0) // 1024,
100
+ "disk_busy_pct": None,
101
+ "connections": conns,
102
+ "top_cpu": [],
103
+ }
104
+ with open(out, "w", encoding="utf-8") as f:
105
+ json.dump(snapshot, f, indent=2)
106
+ PYEOF
107
+ rm -f "$OUTDIR/.ss.txt"
108
+
109
+ sleep "$(( DURATION - DURATION / 2 ))"
110
+ kill "$TCPDUMP_PID" 2>/dev/null || true
111
+ wait "$TCPDUMP_PID" 2>/dev/null || true
112
+
113
+ echo ""
114
+ echo "Bundle pret :"
115
+ echo " $PCAP"
116
+ echo " $SNAP"
117
+ echo ""
118
+ echo "Analyse :"
119
+ echo " netverdict analyze \"$PCAP\" --snapshot \"$SNAP\""
@@ -0,0 +1,79 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!--
3
+ Configuration Sysmon MINIMALE pour netverdict.
4
+
5
+ But : ne journaliser QUE l'evenement 3 (NetworkConnect), qui porte la
6
+ jointure process <-> connexion dont l'analyse a besoin :
7
+ SourceIp / SourcePort / DestinationIp / DestinationPort
8
+ + ProcessId + Image + Protocol + Initiated
9
+
10
+ C'est tout l'interet de Sysmon face a l'audit natif WFP (evenements
11
+ Securite 5156/5157) : le WFP journalise CHAQUE connexion autorisee dans le
12
+ journal Securite et le fait tourner en quelques heures sur un poste actif —
13
+ au moment ou l'on analyse un incident, les evenements utiles ont deja
14
+ disparu. Ici on cible, et le journal Sysmon est un canal separe, dimensionne
15
+ independamment.
16
+
17
+ IDIOME UTILISE, a ne pas "corriger" :
18
+ onmatch="include" SANS aucune regle = ne matche jamais = type d'evenement
19
+ eteint. C'est la facon documentee de desactiver un type d'evenement, plus
20
+ fiable que de compter sur ses valeurs par defaut (qui varient d'une version
21
+ a l'autre). Les 21 autres types sont donc explicitement eteints ci-dessous.
22
+
23
+ onmatch="exclude" SANS aucune regle = n'exclut rien = tout journalise.
24
+ C'est ce qu'on veut pour NetworkConnect : filtrer par port ou par process
25
+ risquerait d'ecarter precisement le flux de l'incident.
26
+
27
+ Verifie le 2026-07-25 contre le schema du binaire livre avec Windows
28
+ (C:\Windows\System32\sysmon.exe -s) : schemaversion 4.91, et les 22 noms
29
+ d'elements ci-dessous sont ceux que ce binaire declare. Un nom inconnu fait
30
+ rejeter TOUT le fichier.
31
+
32
+ Installation (terminal ADMINISTRATEUR) :
33
+ sysmon -accepteula -i capture\sysmon-netverdict.xml
34
+ Mise a jour d'une instance deja installee :
35
+ sysmon -c capture\sysmon-netverdict.xml
36
+ Desinstallation complete (driver + service + canal) :
37
+ sysmon -u force
38
+
39
+ Volume : sur un poste de bureau, compter quelques centaines a quelques
40
+ milliers d'evenements par heure. Verifier la taille du canal apres une
41
+ journee, et le reduire si besoin :
42
+ wevtutil sl Microsoft-Windows-Sysmon/Operational /ms:67108864
43
+ -->
44
+ <Sysmon schemaversion="4.91">
45
+ <!-- Aucun hachage calcule : on ne veut que la jointure reseau, et le hachage
46
+ coute du CPU a chaque process demarre pour rien ici. -->
47
+ <HashAlgorithms>*</HashAlgorithms>
48
+ <CheckRevocation>false</CheckRevocation>
49
+
50
+ <EventFiltering>
51
+ <!-- ============ LE SEUL EVENEMENT QUI NOUS INTERESSE ============ -->
52
+ <!-- exclude sans regle : tout est journalise. -->
53
+ <NetworkConnect onmatch="exclude" />
54
+
55
+ <!-- ============ TOUT LE RESTE ETEINT ============ -->
56
+ <!-- include sans regle : ne matche jamais, donc rien n'est journalise. -->
57
+ <ClipboardChange onmatch="include" />
58
+ <CreateRemoteThread onmatch="include" />
59
+ <DnsQuery onmatch="include" />
60
+ <DriverLoad onmatch="include" />
61
+ <FileBlockExecutable onmatch="include" />
62
+ <FileBlockShredding onmatch="include" />
63
+ <FileCreate onmatch="include" />
64
+ <FileCreateStreamHash onmatch="include" />
65
+ <FileCreateTime onmatch="include" />
66
+ <FileDelete onmatch="include" />
67
+ <FileDeleteDetected onmatch="include" />
68
+ <FileExecutableDetected onmatch="include" />
69
+ <ImageLoad onmatch="include" />
70
+ <PipeEvent onmatch="include" />
71
+ <ProcessAccess onmatch="include" />
72
+ <ProcessCreate onmatch="include" />
73
+ <ProcessTampering onmatch="include" />
74
+ <ProcessTerminate onmatch="include" />
75
+ <RawAccessRead onmatch="include" />
76
+ <RegistryEvent onmatch="include" />
77
+ <WmiEvent onmatch="include" />
78
+ </EventFiltering>
79
+ </Sysmon>