#Requires -Version 5.1 <# .SYNOPSIS Vector 1 - Genera el juego de canarios sinteticos y su manifiesto. .DESCRIPTION Crea, por cada equipo del alcance, N ficheros de contenido SINTETICO y marcado. El marcador aparece a la vez en el NOMBRE y en la PRIMERA LINEA del contenido: Nomina_2026_Q1__CANARY-RT2026-02-01.csv CANARY-RT2026-02|ID=CN-RT-2026-02-LAB-PC-01-01|HOST=...|LOC=...|TS=...|UUID=... Esto es deliberado, no un descuido: - el interlock del cliente detecta el marcador leyendo 512 bytes, - DLP/FIM pueden alertar por contenido y por nombre, - un ZIP que los contenga tambien es detectado (el marcador viaja en el nombre de la entrada del archivo, dentro de la cabecera local del ZIP), - queda demostrado que el dato exfiltrado era sintetico. No escribe nada fuera de staging_root. .EXAMPLE .\New-Canaries.ps1 -ScopePath ..\scope\scope.vector1.json #> [CmdletBinding()] param( [string]$ScopePath = (Join-Path (Split-Path -Parent $PSScriptRoot) 'scope\scope.vector1.json'), [string[]]$HostName = @(), [switch]$Force ) . (Join-Path (Split-Path -Parent $PSScriptRoot) 'lib\Common.ps1') . (Join-Path (Split-Path -Parent $PSScriptRoot) 'lib\Canary.ps1') $cfg = Get-RTCanaryConfig -Path $ScopePath $logRoot = New-RTDirectory -Path (Expand-RTPath -Path $cfg.log_root) $logPath = Join-Path $logRoot ("canaries-$($cfg.exercise_id).log") $stagingRoot = New-RTDirectory -Path (Expand-RTPath -Path $cfg.staging_root) $manifestRoot = New-RTDirectory -Path (Expand-RTPath -Path $cfg.manifest_root) $manifestPath = Join-Path $manifestRoot ("canaries-$($cfg.exercise_id).jsonl") Write-RTLog -LogPath $logPath -Message "================================================================" Write-RTLog -LogPath $logPath -Message "Vector 1 - Generacion de canarios. exercise_id=$($cfg.exercise_id)" Write-RTLog -LogPath $logPath -Message "Marcador: $($cfg.canary.marker) Staging: $stagingRoot" if ((Test-Path -LiteralPath $manifestPath) -and (-not $Force)) { throw "Ya existe el manifiesto $manifestPath. Usa -Force para regenerar el juego de canarios." } if ($Force -and (Test-Path -LiteralPath $manifestPath)) { Remove-Item -LiteralPath $manifestPath -Force Write-RTLog -LogPath $logPath -Level WARN -Message "Manifiesto anterior eliminado (-Force)." } # 2026-10-07: si no hay lista de equipos, los canarios se generan para ESTE equipo. # Sin esto, con el alcance sin hosts, no se generaba ningun canario. $hosts = if ($HostName.Count -gt 0) { $HostName } elseif (@($cfg.authorized_hosts).Count -gt 0) { @($cfg.authorized_hosts) } else { @($env:COMPUTERNAME) } $names = @($cfg.canary.names) $sizes = @($cfg.canary.sizes_kb) $totalBytes = [long]0 $totalFiles = 0 Add-RTManifestEntry -ManifestPath $manifestPath -Entry ([ordered]@{ type = 'canary_header' exercise_id = $cfg.exercise_id marker = $cfg.canary.marker hosts = @($hosts) per_host = [int]$cfg.canary.per_host created_utc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') host_user = "$env:COMPUTERNAME\$env:USERNAME" }) foreach ($h in $hosts) { $hostDir = New-RTDirectory -Path (Join-Path $stagingRoot $h) $filesForZip = @() for ($seq = 1; $seq -le [int]$cfg.canary.per_host; $seq++) { # Con sigilo el nombre NO lleva el marcador: un fichero que dice CANARY en el # nombre se descubre solo, y mas si va a estar plantado dias en un equipo real. # El marcador sigue viajando DENTRO del fichero, que es lo que el interlock # comprueba de verdad: lee los primeros 512 bytes, no el nombre. if ([bool]$cfg.canary.stealth) { $short = '{0:D2}' -f $seq } else { $short = '{0}-{1:D2}' -f $cfg.canary.marker, $seq } $tpl = $names[($seq - 1) % $names.Count] $fileName = $tpl -f $short $sizeKb = [int]$sizes[($seq - 1) % $sizes.Count] $canaryId = New-RTCanaryId -ExerciseId $cfg.exercise_id -HostName $h -Seq $seq $content = New-RTCanaryContent -Marker $cfg.canary.marker -CanaryId $canaryId ` -SizeBytes ($sizeKb * 1024) -HostName $h -LocationLabel $fileName $filePath = Join-Path $hostDir $fileName Set-Content -LiteralPath $filePath -Value $content -Encoding UTF8 -NoNewline $item = Get-Item -LiteralPath $filePath $sha = Get-RTFileSha256 -Path $filePath Add-RTManifestEntry -ManifestPath $manifestPath -Entry ([ordered]@{ type = 'canary' canary_id = $canaryId host = $h logical_name = $fileName staging_path = $filePath size = $item.Length size_kb = $sizeKb sha256 = $sha marker = $cfg.canary.marker is_archive = $false created_utc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }) $filesForZip += $filePath $totalBytes += $item.Length $totalFiles++ } if ([bool]$cfg.canary.archive_one -and $filesForZip.Count -gt 0) { $zipName = if ([bool]$cfg.canary.stealth) { 'Archivo_Confidencial.zip' } else { 'Archivo_Confidencial__{0}.zip' -f $cfg.canary.marker } $zipPath = Join-Path $hostDir $zipName if (Test-Path -LiteralPath $zipPath) { Remove-Item -LiteralPath $zipPath -Force } Compress-Archive -Path $filesForZip -DestinationPath $zipPath -Force $zipItem = Get-Item -LiteralPath $zipPath $zipSha = Get-RTFileSha256 -Path $zipPath # El interlock debe aceptar tambien el ZIP: el marcador viaja en el nombre # de la entrada, dentro de la cabecera local del archivo. $zipOk = Test-RTCanaryFile -Path $zipPath -Marker $cfg.canary.marker Add-RTManifestEntry -ManifestPath $manifestPath -Entry ([ordered]@{ type = 'canary' canary_id = 'CN-{0}-{1}-ZIP' -f $cfg.exercise_id, $h host = $h logical_name = $zipName staging_path = $zipPath size = $zipItem.Length sha256 = $zipSha marker = $cfg.canary.marker is_archive = $true marker_ok = $zipOk created_utc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') }) $totalBytes += $zipItem.Length $totalFiles++ if (-not $zipOk) { Write-RTLog -LogPath $logPath -Level WARN -Message "El ZIP '$zipName' NO supera la comprobacion de marcador; revisar antes de usarlo." } } Write-RTLog -LogPath $logPath -Message "Host $h : $([int]$cfg.canary.per_host) canarios + archivo, en $hostDir" } Write-RTLog -LogPath $logPath -Message "TOTAL: $totalFiles ficheros, $totalBytes bytes." Write-RTLog -LogPath $logPath -Message "Manifiesto: $manifestPath" Write-RTLog -LogPath $logPath -Message "================================================================" Write-Host '' Write-Host ("Canarios generados: {0} ficheros, {1:N0} bytes" -f $totalFiles, $totalBytes) -ForegroundColor Cyan Write-Host "Manifiesto : $manifestPath" Write-Host "Staging : $stagingRoot" Write-Host "Siguiente : .\Invoke-CanaryPlant.ps1 -ScopePath '$ScopePath' -Execute" -ForegroundColor Yellow