miércoles, 21 de enero de 2026

WINDOWS. PS IOC


 Propósito

Dada una palabra o un ejecutable, buscar un IOC (Indicador de compromiso) en un Host.

Introduces las palabras clave y el script analiza:

  • Procesos
  • Servicios
  • Tareas programadas
  • Carpetas sospechosas
  • Claves RUN / RUNONCE
  • Servicios en el registro
  • Búsqueda de binarios en AppData / Temp
  • Exportación CSV
  • Programas instalado
  • Ejecución en RAM

·         Startup del usuario y del sistema


Pasos

Para ejecutar: powershell.exe -ExecutionPolicy Bypass -File .\BuscasIOC.ps1

<#

    Detect-Strings-Extended.ps1

    Autor: Guillermo

    Objetivo:

        Script INFORMÁTICO que analiza el equipo para encontrar cualquier rastro

        de cadenas sospechosas en:

 

            • Programas instalados

            • Procesos en RAM

            • Servicios

            • Tareas programadas

            • Carpetas sospechosas

            • Entradas en RUN / RUNONCE

            • Servicios en el Registro

            • Archivos en AppData / Temp

            • Programas configurados en el inicio de Windows

 

        NO modifica nada. NO detiene nada. NO elimina nada.

#>

 

Write-Host "======================================================" -ForegroundColor Cyan

Write-Host "   ANALIZADOR DE INDICADORES - MODO INFORMATIVO"       -ForegroundColor Cyan

Write-Host "======================================================" -ForegroundColor Cyan

 

# 1) Pedir cadenas al usuario

$input = Read-Host "Introduce las cadenas a buscar (separadas por coma). Ej: updater, malware, test"

$patterns = $input.Split(",") | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }

 

if ($patterns.Count -eq 0) {

    Write-Host "No se han introducido cadenas. Saliendo..." -ForegroundColor Red

    exit

}

 

Write-Host "`nCadenas a buscar:" -ForegroundColor Yellow

$patterns | ForEach-Object { Write-Host " - $_" -ForegroundColor Green }

 

# Convertir a regex OR

$regex = ($patterns -join "|")

 

$results = New-Object System.Collections.Generic.List[object]

 

function Add-Result($Tipo,$Ruta,$Detalle) {

    $results.Add([pscustomobject]@{

        Tipo    = $Tipo

        Ruta    = $Ruta

        Detalle = $Detalle

    })

}

 

# =====================================================================

# [0] PROGRAMAS INSTALADOS (Add/Remove Programs)

# =====================================================================

Write-Host "`n========== [0] PROGRAMAS INSTALADOS ==========" -ForegroundColor Cyan

 

$installedApps = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*, `

                                  HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*, `

                                  HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* `

                    -ErrorAction SilentlyContinue

 

foreach ($app in $installedApps) {

    if ($app.DisplayName -match $regex) {

        Add-Result "Programa Instalado" $app.DisplayName $app.DisplayVersion

        Write-Host "Programa detectado: $($app.DisplayName)" -ForegroundColor Green

    }

}

 

# =====================================================================

# [1] PROCESOS

# =====================================================================

Write-Host "`n========== [1] PROCESOS ==========" -ForegroundColor Cyan

Get-Process -ErrorAction SilentlyContinue |

    Where-Object { $_.Name -match $regex } |

    ForEach-Object {

        Add-Result "Proceso" ($_.Path) ($_.Name)

        $_

    }

 

# =====================================================================

# [2] SERVICIOS

# =====================================================================

Write-Host "`n========== [2] SERVICIOS ==========" -ForegroundColor Cyan

Get-Service -ErrorAction SilentlyContinue |

    Where-Object { $_.Name -match $regex -or $_.DisplayName -match $regex } |

    ForEach-Object {

        Add-Result "Servicio" $_.Name $_.DisplayName

        $_

    }

 

# =====================================================================

# [3] TAREAS PROGRAMADAS

# =====================================================================

Write-Host "`n========== [3] TAREAS PROGRAMADAS ==========" -ForegroundColor Cyan

Get-ScheduledTask -ErrorAction SilentlyContinue |

    Where-Object { $_.TaskName -match $regex -or $_.Description -match $regex } |

    ForEach-Object {

        Add-Result "Tarea Programada" $_.TaskName $_.Description

        $_

    }

 

# =====================================================================

# [4] CARPETAS HABITUALES

# =====================================================================

Write-Host "`n========== [4] CARPETAS HABITUALES ==========" -ForegroundColor Cyan

$paths = @(

    "$env:LOCALAPPDATA",

    "$env:LOCALAPPDATA\Programs",

    "$env:APPDATA",

    "$env:TEMP",

    "$env:ProgramFiles",

    "$env:ProgramFiles(x86)"

)

 

foreach ($base in $paths) {

    if (Test-Path $base) {

        Get-ChildItem $base -Recurse -ErrorAction SilentlyContinue |

            Where-Object { $_.Name -match $regex } |

            ForEach-Object {

                Add-Result "Archivo/Carpeta" $_.FullName "Coincidencia en nombre"

                Write-Host "Encontrado: $($_.FullName)" -ForegroundColor Green

            }

    }

}

 

# =====================================================================

# [5] RUN / RUNONCE

# =====================================================================

Write-Host "`n========== [5] RUN / RUNONCE ==========" -ForegroundColor Cyan

$runKeys = @(

    "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run",

    "HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce",

    "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run",

    "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"

)

 

foreach ($rk in $runKeys) {

    if (Test-Path $rk) {

        $vals = Get-ItemProperty $rk -ErrorAction SilentlyContinue

        foreach ($prop in $vals.PSObject.Properties) {

            if ($prop.Name -notlike "PS*" -and (

                $prop.Name -match $regex -or ($prop.Value -as [string]) -match $regex

            )) {

                Add-Result "RunKey" $rk "$($prop.Name) = $($prop.Value)"

                Write-Host "[RUN] $rk → $($prop.Name) = $($prop.Value)" -ForegroundColor Green

            }

        }

    }

}

 

# =====================================================================

# [6] SERVICIOS DEL REGISTRO

# =====================================================================

Write-Host "`n========== [6] SERVICIOS DEL REGISTRO ==========" -ForegroundColor Cyan

Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Services" -ErrorAction SilentlyContinue |

    Where-Object { $_.Name -match $regex } |

    ForEach-Object {

        Add-Result "RegistroServicio" $_.Name "Coincidencia en nombre de clave"

        $_.Name

    }

 

# =====================================================================

# [7] INICIO DE WINDOWS (Startup)

# =====================================================================

Write-Host "`n========== [7] PROGRAMAS EN EL INICIO ==========" -ForegroundColor Cyan

 

$startupPaths = @(

    "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup",

    "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\Startup"

)

 

foreach ($sp in $startupPaths) {

    if (Test-Path $sp) {

        Get-ChildItem $sp -ErrorAction SilentlyContinue |

            Where-Object { $_.Name -match $regex } |

            ForEach-Object {

                Add-Result "Inicio Windows" $_.FullName "Coincidencia en acceso directo"

                Write-Host "En inicio: $($_.FullName)" -ForegroundColor Yellow

            }

    }

}

 

# =====================================================================

# [8] BINARIOS EN APPDATA / TEMP

# =====================================================================

Write-Host "`n========== [8] BINARIOS APPDATA / TEMP ==========" -ForegroundColor Cyan

$binroots = @("$env:LOCALAPPDATA","$env:APPDATA","$env:TEMP")

 

foreach ($br in $binroots) {

    if (Test-Path $br) {

        Get-ChildItem $br -Recurse -ErrorAction SilentlyContinue |

            Where-Object { $_.Name -match $regex } |

            ForEach-Object {

                Add-Result "Binario" $_.FullName "Coincidencia en nombre"

                Write-Host "Binario detectado: $($_.FullName)" -ForegroundColor Green

            }

    }

}

 

# =====================================================================

# RESUMEN + EXPORTACIÓN

# =====================================================================

 

Write-Host "`n==============================================" -ForegroundColor Cyan

Write-Host "                 RESUMEN" -ForegroundColor Cyan

Write-Host "==============================================" -ForegroundColor Cyan

 

$results | Format-Table -AutoSize

 

# Nombre con fecha y hora

$timestamp = (Get-Date).ToString("yyyy-MM-dd_HH-mm-ss")

$output = "C:\Temp\Deteccion-Resultado_$timestamp.csv"

 

New-Item -ItemType Directory -Path (Split-Path $output) -Force | Out-Null

$results | Export-Csv -Path $output -NoTypeInformation -Encoding UTF8

 

Write-Host "`nArchivo exportado: $output" -ForegroundColor Yellow

Write-Host "`n--- ANÁLISIS FINALIZADO ---" -ForegroundColor Cyan

 







WINDOWS. Security. LAPS

 


    Propósito

    El objetivo este post es explicar un poco como implantar una configuración de Microsoft LAPS.
    Microsoft LAPS (Local Administrator Password Solution) es una herramienta de Microsoft diseñada para gestionar automáticamente las contraseñas de las cuentas de administrador local en equipos Windows dentro de un dominio.
    Esto solo son algunas de las muchas opciones que hay para gestionar una implantación de este tipo.
    Usa la información oficial de Microsoft para hacer este proceso. Este post úsalo de apoyo y consulta por si tienes alguna duda o problema.
    Antes de ponerlo en un entorno de producción valídalo en un entorno de pruebas.
    Aplicar una configuración de LAPS sin conocimiento y todas las garantías de éxito podría tener un impacto en las infraestructuras.
    Pasos

    Para que funcione LAPS hay que tener en cuenta varios fases

    [01] Preparar el AD
    [02] Asignar configurar las OU
    [03] Script para consultar la OU autorizadas por LAPS
    [04] Check cuantos computers han entrado en LAPS
    [05] Laps Diagnosticos
    [06] Como comprobar si un PC tiene LAPS
    [07] GPO
    [08] Notas

[01] Preparar el AD

Debemos tener la plantilla preparada para la gestión del LAPS:

Copy from:

C:\Windows\PolicyDefinitions\LAPS.admx

C:\Windows\PolicyDefinitions\<language>\LAPS.adml

 

Copy to:

\\<domain>\SYSVOL\<domain>\Policies\PolicyDefinitions\

\\<domain>\SYSVOL\<domain>\Policies\PolicyDefinitions\<language>\

Verifica que el esquema de AD tiene los atributos del nuevo LAPS

Ejecuta en tu DC:

COMANDO: Get-ADObject -SearchBase "CN=Schema,CN=Configuration,DC=Midomino,DC=com" -LDAPFilter "(lDAPDisplayName=msLAPS-EncryptedPassword)"


COMANDO: Get-ADObject -SearchBase (Get-ADRootDSE).schemaNamingContext -LDAPFilter "(|(name=*laps*)(name=*admPwd*))" | Select-Object name

Si no lo tiene hay que prepararlo 

Es recomendable que el usuario que lo vaya a configurar pertenezca a los grupos:

·         Scheme Administrator /      Organization Administrator       

     Para que la configuración no de problemas se debe hacer desde un Controlador de Dominio que tenga el módulo de LAPS instalado.

ç     Empezamos. NO debes ejecutarlo si:

  • El esquema ya fue actualizado previamente (solo se hace una vez por bosque).

  • Tu dominio ya tiene los atributos de LAPS nuevo (Windows Server 2019/2022 + actualizaciones ya los incluyen).

  • Estás usando LAPS Legacy (el antiguo) y no piensas migrar.

     Esta configuración podría cambiar en función a la evolución que de Microsoft a este servicio. Mucho cuidado en configurar el LAPS sin haber tenido en cuenta los puntos anteriores.

      Preparamos el AD

       C:\> Update-LapsADSchema


Debemos crear 2 nuevos grupos de usuarios, uno que solo podrá leer las contraseñas y otro para gestionar el LAPS

[] Permiso de lectura

COMMAND: PS C:\> Set-LapsADReadPasswordPermission -Identity "OU=LAPS,DC=midominio,DC=com" -AllowedPrincipals @("laps\LapsPasswordReadersGroup")

[] Permitir acceso a contraseñas cifradas (Password Encryption Allowed Principals)

Para visualizar contraseñas cuando LAPS está configurado con cifrado, los usuarios deben pertenecer a un grupo autorizado.

COMMAND: Set-LapsADPasswordEncryptionAllowedPrincipals -Identity "OU=LAPS,DC=midominio,DC=com" -AllowedPrincipals "Domain Admins","Grupo_Laps_Management"

Members of the Domain Admins group already have password query permission by default.

[] Consultar permisos extendidos (Query Extended Rights Permissions)

Este comando permite verificar qué grupos o usuarios tienen permisos avanzados sobre la OU, incluyendo lectura de contraseñas y escritura de atributos gestionados por LAPS.

COMMAND: Find-LapsADExtendedRights -Identity "OU=LAPS,DC=midominio,DC=com"



Command: PS C:\> Import-Module LAPS


Hay que autorizar a un grupo del AD de usuarios para poder leer las contraseñas del LAPS


[02] Asignar configurar las OU

En cada OU que queramos que se le aplique la GPO hay que autorizarla por comandos, con   Set-LapsADComputerSelfPermission -Identity

Por ejemplo: 

Set-LapsADComputerSelfPermission -Identity OU=Bcn,OU=Spain,OU=EquiposCoorp,DC=salvadorescoda,DC=com


[03] Script para consultar la OU autorizadas por LAPS

Se recorre el AD y mira todas las OU, saca las que con el comando anterior han sido autorizadas

Import-Module ActiveDirectory

 

# GUID del atributo específico

$lapsEncryptedGuid = [guid]"f3531ec6-6330-4f8e-8d39-7a671fbac605"

 

# Obtener todas las OUs del dominio

$OUs = Get-ADOrganizationalUnit -Filter *

 

$ousWithWritePermission = foreach ($OU in $OUs) {

    # Leer ACL de la OU usando ADSI

    $entry = [ADSI]"LDAP://$($OU.DistinguishedName)"

    $acl = $entry.ObjectSecurity

 

    # Filtrar ACEs con WriteProperty sobre el GUID específico

    $writeAce = $acl.Access | Where-Object {

        ($_.ActiveDirectoryRights -band [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty) -and

        $_.ObjectType -eq $lapsEncryptedGuid

    }

 

    if ($writeAce) {

        foreach ($ace in $writeAce) {

            [PSCustomObject]@{

                OU        = $OU.DistinguishedName

                Trustee   = $ace.IdentityReference

                Inherited = $ace.IsInherited

            }

        }

    }

}

 

# Mostrar lista única de OUs con permisos WRITE

if ($ousWithWritePermission) {

    $ousWithWritePermission |

        Sort-Object OU -Unique |

        Format-Table -AutoSize

} else {

    Write-Output "No se encontraron OUs con permisos WRITE sobre ms-LAPS-Encrypted-Password-Attributes."

}






Otra opcion

Comando: Get-ADObject -SearchBase (Get-ADRootDSE).schemaNamingContext -LDAPFilter "(name=*LAPS*)"




[04] Check cuantos computers han entrado en LAPS

Se verifica los PCs que tiene algo dentro de la contraseña de LAPS

 Write-Host "Ejecutando v3.1..."

Write-Host "Hay que asegurarse que las OUs donde hay equipos LAPS este autorizadas con: Set-LapsADComputerSelfPermission y que recorre todas las Sub-OUS, este proceso puede durar varios minutos. "

# --- Configuración ---

$ou = "OU=Equipos,DC=midominio,DC=com"

# Fecha y hora para el nombre del archivo

$timestamp = Get-Date -Format "yyyyMMdd_HHmm"

$csvPath = "C:\scripts\Computer_LAPS_with_assigned_passwd_$timestamp.csv"

 # --- Obtener equipos de la OU ---

$computers = Get-ADComputer -SearchBase $ou -Filter * -Properties Description, MemberOf, DistinguishedName

 # --- Lista de resultados ---

$resultados = @()

 foreach ($computer in $computers) {

    try {

        # Intenta obtener la contraseña con el nuevo esquema de LAPS

        $laps = Get-LapsADPassword -Identity $computer.Name -ErrorAction Stop

         if ($laps) {

            # Obtener OU desde el DN

            $computerOU = ($computer.DistinguishedName -split ",",2)[1]

             # Obtener grupos del AD

            $groups = $computer.MemberOf | ForEach-Object {

                ($_ -split ",")[0] -replace "^CN="

            }

             $resultados += [PSCustomObject]@{

                ComputerName = $computer.Name

                Description  = $computer.Description

                OU           = $computerOU

                ADGroups     = ($groups -join ";")

                PasswordSet  = $true

                Expiration   = $laps.ExpirationTimestamp

            }

        }

    }

    catch {

        # No se añade a la lista

    }

}

 # --- Exportar a CSV ---

$resultados | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8

 Write-Host "Archivo CSV generado correctamente: $csvPath"

Write-Host "Total de filas exportadas: $($resultados.Count)"

 





[05] Laps Diagnosticos

COMANDO: Get-LapsDiagnostics

Get-LapsDiagnostics: all data for this run was written to the following zip file:

 C:\Users\R\AppData\Local\Temp\LapsDiagnostics\LapsDiagnostics_DC_2025122312_155158.zip



[06] Como comprobar si un PC tiene LAPS

COMANDO: Get-ADComputer PC001 -Properties msLAPS-PasswordExpirationTime, msLAPS-EncryptedPassword


Si esos atributos tienen valores, LAPS está aplicado y funcionando.

Si aparecen vacíos o null, el equipo no ha aplicado la GPO o no ha podido escribir en AD.


[07] GPO









[08] Notas

Para acceder a las estaciones de trabajo con LAPS, solo los administradores locales deben tener acceso. Los administradores de dominio lo deben tener prohibido. 

No debería haber un usuario con contraseña común en más de un PC.

Sería recomendable renombrar por GPO el administrador local.

Sería recomendable, hacer una exportación cada 15 días de las contraseñas, en un lugar seguro, con contraseña y cifrado. Por si hay algún problema poder consultarla.

Solo los usuarios que den soporte deberían poder consultar la contraseña, siempre elevándose con un usuario privilegiado especial.

Se debería auditar todos los accesos que consulten las contraseñas de LAPS.



by GoN | Published: Jan 2026 | Last Updated: