La brecha de la ingeniería de detección
La mayoría de las reglas de detección de entrada provienen de uno de dos lugares: feeds de proveedores o informes de brechas. Pero hay una tercera forma que está sorprendentemente subutilizada: leer el código fuente del atacante y crear detecciones directamente a partir de él.
¿El desafío? Lo que ves en el código rara vez coincide con lo que encontrarás en los registros. Los nombres de las variables cambian. Las funciones se traducen a eventos de Windows. Patrones de ataque que parecen obvios en Python se convierten en cadenas oscuras de ID de eventos. Esta brecha entre el código y los registros es donde fallan la mayoría de los esfuerzos de detección.
Esta publicación recorre un enfoque sistemático para cerrar esa brecha, usando las herramientas de reconocimiento LDAP de Impacket como caso de prueba.
El pilar pasado por alto
Active Directory se apoya en tres pilares: DNS, autenticación y LDAP. De los tres, LDAP es probablemente el más pasado por alto, lo cual es lamentable porque también es el más revelador. Todo sistema unido al dominio depende de LDAP para buscar usuarios, grupos y permisos. Es el motor de consultas que hace funcionar a Active Directory.
Los atacantes descubrieron esto hace mucho tiempo. El conjunto de herramientas Impacket, usado tanto por adversarios como por equipos rojos, bombardea los endpoints LDAP para mapear entornos. La buena noticia es que estas consultas dejan rastros. La mala noticia es que la mayoría de los defensores los pasan por alto porque están mirando en el lugar equivocado o buscando lo que no corresponde.
El detalle clave
Aquí está el detalle clave: lo que ves en el código fuente no es lo que aparece en los registros. Impacket envía consultas como (userAccountControl:1.2.840.113556.1.4.803:=16777216), pero los controladores de dominio las registran como (userAccountControl&16777216). Mismo comportamiento, formato diferente. El OID 1.2.840.113556.1.4.803 se reemplaza con el operador bit a bit &. He visto muchas reglas de detección que se ven perfectas en el papel pero que nunca se activan porque buscan OID en registros que solo contienen operaciones bit a bit.
Me tomó más tiempo del que quisiera admitir darme cuenta de esto. Estas transformaciones aparecen en el ID de evento 1644, que encontrarás en:
Applications and Services Logs -> Directory Service
Nota: El evento 1644 no se registra de forma predeterminada. Para habilitarlo en tus controladores de dominio:
- Abre el Editor del Registro y navega a: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics
- Configura el valor "15 Field Engineering" en 5
Consulta la documentación de Microsoft para más detalles. Del lado del cliente, el ID de evento 30 se encuentra en:
Applications and Services Logs -> Microsoft-Windows-LDAP-Client/Debug
Nota: En el Visor de eventos, primero haz clic en View -> Show Analytic and Debug Logs. Luego navega a Applications and Services Logs -> Microsoft-Windows-LDAP-Client, haz clic derecho en Debug y selecciona Enable Log. Como alternativa, puedes usar el rastreo ETW (incontables otras publicaciones y herramientas cubren esto). O puedes usar LDAPMon de Jonathan Johnson, que es mi herramienta preferida, complementaria a los registros de eventos. Ten en cuenta que esto registra en la máquina cliente, no en el controlador de dominio, así que necesitarás habilitarlo en los endpoints que quieras monitorear.
Ambas perspectivas importan. El servidor muestra lo que el controlador de dominio registró, mientras que el cliente muestra quién hizo la consulta, desde dónde y con qué proceso.
Por qué el reconocimiento LDAP funciona tan bien
Las consultas LDAP son perfectas para el reconocimiento porque son:
- Legítimas por diseño: todo sistema unido al dominio usa LDAP constantemente
- Ricas en información: una sola consulta puede volcar usuarios, grupos y permisos
- Demasiado críticas para deshabilitar: restringir LDAP rompe Active Directory, así que los defensores no pueden bloquearlo
- Fáciles de pasar por alto: la mayoría de los equipos vigilan los registros de autenticación, no las consultas de directorio
Esto convierte a LDAP en el vector de ataque ideal. Parece tráfico normal mientras les da a los atacantes todo lo que necesitan para mapear tu entorno.
El problema de la traducción
El desafío no es solo que los registros y el código no coincidan, sino que la mayoría de la gente no se da cuenta de que no coinciden. He revisado docenas de reglas de detección LDAP que buscan cadenas de OID en el evento 1644, lo cual nunca funcionará porque los controladores de dominio traducen esos OID a operaciones bit a bit antes de registrarlos.
Sin tener en cuenta esta traducción, las reglas que validan sintácticamente nunca se activarán. La brecha entre el código fuente y los registros de eventos no es un detalle trivial: es un problema fundamental que los ingenieros de detección deben resolver.
Cómo construyo reglas de detección a partir del código fuente
Desarrollé este enfoque después de quemarme demasiadas veces con reglas que se veían perfectas pero que nunca se activaban. Este es el proceso que realmente funciona:
-
Paso 1: Análisis del código fuente - Observa exactamente cómo las herramientas construyen consultas
Comienza con el código fuente real de Impacket, específicamente findDelegation.py y herramientas de consulta LDAP similares. Verás exactamente cómo construyen sus filtros LDAP, qué atributos solicitan y qué patrones se mantienen constantes entre distintas consultas.
-
Paso 2: Emulación en laboratorio - Genera telemetría real
Ejecuta las herramientas reales en un entorno controlado. No adivines cómo se verían los registros: genera telemetría real. Aquí es donde descubrirás esas transformaciones de OID a operaciones bit a bit que mencioné antes.
-
Paso 3: Análisis de registros - Descubre las transformaciones de OID
Aquí es donde la mayoría se atasca. Debes revisar el Event ID 1644 y buscar las consultas traducidas. Por ejemplo, el filtro de findDelegation.py contiene (userAccountControl:1.2.840.113556.1.4.803:=16777216), pero el Event 1644 lo registra como (userAccountControl & 16777216). El OID :1.2.840.113556.1.4.803:= se convierte en &. Esa transformación ocurre aquí, y si no la detectas, tus reglas no funcionarán.
-
Paso 4: Desarrollo de Sigma - Escribe reglas basadas en registros reales
Ahora escribe tus reglas de Sigma basándote en lo que realmente encontraste en los registros, no en lo que el código sugería que habría. Las reglas que buscan cadenas de OID nunca se activarán.
-
Paso 5: Validación y ajuste - Prueba y reduce los falsos positivos
La regla debe activarse con una repetición de comportamiento conocido en el laboratorio y permanecer en silencio durante un día hábil de tráfico normal. Si es demasiado ruidosa, probablemente capturaste comportamiento LDAP legítimo.
-
Paso 6: Mejora de la atribución - Correlaciona el Event 1644 con el Event 30
Combina los registros del servidor (Event 1644) con los registros del cliente (Event 30) para una atribución completa (nota: el Event 30 solo funciona con herramientas que usan wldap32.dll). El servidor muestra qué pasó, el cliente muestra quién lo hizo.
- RESULTADO: Reglas de detección listas para producción
Figura 1: Mi proceso de desarrollo de detecciones. El Paso 3 (Análisis de registros) está resaltado como la etapa crítica donde se descubre la transformación de OID a operación bit a bit, lo que cambia fundamentalmente cómo deben escribirse las reglas.
Este método basa las detecciones en la realidad observable. Cada paso se construye sobre el anterior, de modo que pasas de la teoría a la evidencia y luego a la cobertura con una cadena de custodia clara.
Por qué funciona este proceso
- Enfoque tradicional: Leer la documentación, escribir reglas y esperar que funcionen
- Enfoque sistemático: Analizar el código, generar registros, estudiar las transformaciones, construir reglas y validar exhaustivamente
El enfoque sistemático saca a la luz las diferencias sutiles entre lo que envían las herramientas y lo que registran los controladores de dominio. Esas diferencias suelen ser la razón por la que detecciones prometedoras fallan en producción.
La brecha de validación
La mayor parte de la ingeniería de detección se detiene en la creación de reglas. Las lecciones difíciles aparecen en la validación:
- Desajustes de formato: OID en el código frente a operaciones bit a bit en los registros
- Sincronización y correlación: Cuándo se activan los eventos y cómo unirlos
- Falsos positivos: Herramientas administrativas legítimas o escaneos de inventario que se ven similares
- Oportunidades de atribución: Formas de vincular eventos anónimos del servidor con identidades, endpoints y procesos específicos
Cómo generar confianza en la detección
Al final de este proceso, deberías tener más que una regla de Sigma. Deberías tener:
- Cobertura verificada: Prueba de que la regla se activa con el comportamiento previsto
- Límites conocidos: Claridad sobre qué detecta y qué se le escapa
- Contexto para el analista: Notas breves sobre por qué se activa y qué revisar a continuación
- Guía de ajuste: Palancas concretas para umbrales, filtros y correlaciones
Así es como el desarrollo de detecciones pasa de ser conjeturas a ser ingeniería.
Paso 1: Leer realmente el código fuente (y hacer algunas búsquedas básicas en Google)
Esto puede sonar obvio, pero necesitas ver lo que estas herramientas realmente hacen, no lo que crees que hacen. Cada script de Impacket construye filtros LDAP explícitamente, y si quieres detectarlos, necesitas entender los patrones.
A veces la forma más fácil de encontrar estos patrones es simplemente buscarlos en Google. No es broma: búsquedas básicas como "sAMAccountName,pwdLastSet,mail,lastLogon" o "msDS-PrincipalName,objectSid" te mostrarán exactamente dónde aparecen estas combinaciones de atributos en la práctica. Issues de GitHub, investigaciones de seguridad, incluso PDF aleatorios de cursos de pruebas de penetración: todo está ahí si lo buscas.
Figura 2: Resultados de búsqueda en Google de "samaccountname,pwdlastset,mail,lastlogon" que muestran referencias a GetADUsers.py en issues de GitHub y materiales de capacitación en seguridad. La combinación distintiva de atributos aparece en múltiples fuentes, lo que demuestra cómo búsquedas básicas en Google pueden revelar firmas de herramientas.
Empecemos con GetADUsers.py porque tiene una particularidad extraña que me llamó la atención de inmediato:
Figura 3: Fragmento de código del filtro de búsqueda LDAP de GetADUsers.py
La primera vez que vi esa solicitud del atributo email pensé: "¿qué onda, por qué una herramienta de reconocimiento pide direcciones de correo?". Resulta que es útil para construir listas de objetivos para phishing, pero también es una firma bastante distintiva. ¿Qué herramienta administrativa legítima solicita cuentas de usuario junto con sus direcciones de correo, la fecha del último cambio de contraseña y el último inicio de sesión? Casi ninguna.
Ahora observa secretsdump.py: cuando se le proporciona un filtro LDAP (-ldapfilter), se vuelve completamente minimalista:
Figura 4: Fragmento de código del filtro de búsqueda LDAP de secretsdump.py
Eso es todo. Solo dos atributos: msDS-PrincipalName y objectSid. Pero, ¿por qué esos dos juntos? Eso me pareció una señal de alerta.
- msDS-PrincipalName = nombres principales de autenticación/Kerberos entre bosques
- objectSid = identificadores de seguridad únicos
- Juntos = "dame nombres de autenticación e ID de seguridad de todos los usuarios"
Cuando solicitas ambos juntos, básicamente estás pidiendo exactamente lo que necesitas para apuntar a cuentas en ataques DCSync. Ninguna herramienta de administración legítima necesita esta combinación específica.
Figura 5: Resultados de Google para "msDS-PrincipalName,objectSid" que revelan su conexión con secretsdump.py y las operaciones DCSync. Esta combinación mínima de dos atributos es tan específica que buscarla expone de inmediato su intención maliciosa.
Estos dos ejemplos te muestran el espectro: GetADUsers.py solicita atributos de aspecto normal, pero en una combinación sospechosa, mientras que secretsdump.py solicita atributos que casi nunca usan las herramientas legítimas.
Otras herramientas tienen sus propios patrones. GetNPUsers.py hace AS-REP roasting:
Figura 6: Fragmento de código del filtro de búsqueda LDAP de GetNPUsers.py
GetUserSPNs.py busca el infame patrón servicePrincipalName=* :
Figura 7: Fragmento de código (1 de 2) del filtro de búsqueda LDAP de GetUserSPNs.py
Figura 8: Fragmento de código (2 de 2) del filtro de búsqueda LDAP de GetUserSPNs.py
Eso es preparación para Kerberoasting, justo ahí.
GetADComputers.py solicita información del sistema operativo:
Figura 9: Fragmento de código del filtro de búsqueda LDAP de GetADComputers.py
Esos atributos operatingSystem y operatingSystemVersion son señales claras de enumeración de equipos.
Y findDelegation.py se vuelve más complejo con atributos específicos de delegación:
Figura 10: Fragmento de código del filtro de búsqueda LDAP de findDelegation.py
Figura 11: Resultados de búsqueda para los patrones de atributos de findDelegation.py que muestran "sAMAccountName, pwdLastSet, userAccountControl, objectCategory, msDS-AllowedToActOnBehalfOfOtherIdentity" en los issues de GitHub de Impacket. Estos atributos específicos enfocados en delegación hacen que el propósito de la herramienta sea evidente de inmediato.
La cuestión es que estas combinaciones de atributos son tan específicas que básicamente son firmas. ¿Y la mejor parte? Están por todo internet si sabes cómo buscarlas. A veces el enfoque más simple, simplemente buscar en Google la lista exacta de atributos, te dice más sobre el comportamiento de una herramienta que horas de análisis de código.
Paso 2 - Ejecutarlo en el laboratorio
No puedes simplemente leer el código y asumir que sabes cómo se verán los registros. Tienes que ejecutar realmente las herramientas y ver qué pasa. Esto es lo que obtengo al ejecutar findDelegation.py:
Figura 12: Salida de terminal de findDelegation.py
El registro del evento 1644 que se genera se ve así:
Figura 13: Evento 1644 correspondiente a la actividad de findDelegation.py registrada en el DC
¿Notaste algo? Los OID desaparecieron por completo. Fueron reemplazados por operaciones bit a bit con &. Esta es la clave: lo que ves en el código fuente no es lo que obtienes en los registros.
Paso 3 - Por qué tus reglas no se activan
Aquí es donde la mayoría de los ingenieros de detección se tropiezan, y, honestamente, a mí me tomó demasiado tiempo entenderlo. Lo que aparece en los registros no se parece en nada a lo que está en el código fuente, y si no lo sabes, tus reglas nunca se activarán.
Mira esto lado a lado:
Lo que envía la herramienta: (userAccountControl:1.2.840.113556.1.4.803:=524288)
Lo que registra el evento 1644: (userAccountControl&524288)
Misma operación lógica, sintaxis totalmente distinta. Esta es la razón: el OID 1.2.840.113556.1.4.803 es la forma en que LDAP dice "haz una operación AND bit a bit". Active Directory entiende esto y lo traduce a la operación bit a bit real antes de registrarlo en el evento 1644.
Entonces, si escribes una regla Sigma como esta, nunca coincidirá:
selection:
winlog.event_data.LDAPFilter|contains: '1.2.840.113556.1.4.803'
Obtendrás cero coincidencias porque los OID no aparecen en los registros del evento 1644. En su lugar, necesitas:
selection:
winlog.event_data.LDAPFilter|contains: 'userAccountControl&524288'
Una vez pasé tres días depurando una regla antes de darme cuenta de esto. No seas como yo.
Paso 3.5 - Análisis técnico profundo: cómo funciona realmente la transformación de OID
El corazón del problema: cómo Active Directory transforma los filtros OID
Esta es la parte que me rompió la cabeza durante días. Seguía escribiendo reglas de detección que deberían haber funcionado, validaba la sintaxis, las probaba en mi SIEM y no obtenía absolutamente nada. Los registros estaban ahí, los eventos se disparaban, pero mis reglas dirigidas a 1.2.840.113556.1.4.803 nunca coincidían. No fue hasta que empecé a revisar lo que realmente contenía el Evento 1644 que entendí la desconexión fundamental.
El recorrido de una consulta LDAP: del OID al operador bitwise
Déjame mostrarte exactamente qué sucede cuando Impacket envía una consulta LDAP a un controlador de dominio. Esto no se trata de registro de eventos, sino de cómo el servicio LDAP de Active Directory procesa realmente las consultas a nivel de protocolo.
Paso 1: La herramienta elabora la consulta
Cuando findDelegation.py quiere encontrar cuentas con permisos de delegación, construye este filtro:
searchFilter = "(&(|(userAccountControl:1.2.840.113556.1.4.803:=16777216)(userAccountControl:1.2.840.113556.1.4.803:=" \
"524288)(msDS-AllowedToDelegateTo=*)(msDS-AllowedToActOnBehalfOfOtherIdentity=*)..."
#(truncated for brevity)
Esto es sintaxis pura del protocolo LDAP. El 1.2.840.113556.1.4.803 es el OID (identificador de objeto) registrado por Microsoft, que significa "realizar una operación de coincidencia AND bitwise". Piensa en los OID como una forma estandarizada para que distintas implementaciones de LDAP entiendan operaciones complejas sin codificar sintaxis específica de proveedor.
La herramienta empaqueta esto en un paquete LDAP SearchRequest y lo envía por el puerto 389 (o 636 para LDAPS) al controlador de dominio.
Paso 2: El controlador de dominio recibe la consulta sin procesar
El servicio LDAP del DC, que se ejecuta como parte de lsass.exe, recibe este SearchRequest. En este punto, el OID todavía está completamente intacto. Si hicieras una captura de paquetes con Wireshark, verías la cadena exacta del OID en el paquete LDAP.
Para herramientas que usan la biblioteca LDAP nativa de Windows (wldap32.dll), aquí es donde el Evento 30 capturaría la consulta si tuvieras habilitado el registro del lado del cliente. El Evento 30 te muestra lo que realmente se envió por la red, por lo que todavía contiene el formato del OID.
Paso 3: La traducción crítica dentro del servicio LDAP
Aquí es donde se pone interesante. El servicio LDAP del controlador de dominio no solo pasa las consultas directamente a la base de datos. Hay un motor de análisis de filtros dentro de la implementación LDAP de Active Directory que debe interpretar y validar el filtro LDAP antes de poder ejecutarlo.
Cuando el analizador sintáctico encuentra userAccountControl:1.2.840.113556.1.4.803:=524288, pasa por este proceso:
- Reconocimiento del OID: El analizador sintáctico ve la sintaxis :1.2.840.113556.1.4.803: y la reconoce como una regla de coincidencia extendida
- Búsqueda estática: Consulta la tabla de mapeo predefinida de Microsoft para entender qué significa este OID
- Traducción de sintaxis: Convierte la sintaxis basada en el OID a la operación nativa equivalente
- Validación: Se asegura de que el atributo de destino (userAccountControl) admita la operación solicitada
La tabla de mapeo estática
Microsoft tiene codificada de forma fija una tabla de búsqueda en su implementación LDAP que asigna OID específicos a operaciones. Esto no es dinámico ni calculado: es un conjunto fijo de asignaciones.
El proceso de transformación mecánica
Vamos a rastrear exactamente qué sucede con el filtro de findDelegation.py para mostrar qué tan mecánico es este proceso:
Para cada aparición del OID:
- Original: userAccountControl:1.2.840.113556.1.4.803:=16777216
- Reconocer el patrón attribute:OID:=value
- Buscar 1.2.840.113556.1.4.803 en la tabla estática
- La tabla devuelve el operador AND bitwise (&)
- Resultado: userAccountControl&16777216
Transformación completa de findDelegation.py:
Antes (lo que envía Impacket):
(&(|(userAccountControl:1.2.840.113556.1.4.803:=16777216)
(userAccountControl:1.2.840.113556.1.4.803:=524288)
(msDS-AllowedToDelegateTo=*)
(msDS-AllowedToActOnBehalfOfOtherIdentity=*))
(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
Después (lo que registra el Evento 1644):
( & ( | (userAccountControl&16777216) (userAccountControl&524288)
(msDS-AllowedToDelegateTo=*)
(msDS-AllowedToActOnBehalfOfOtherIdentity=*) ) ( !
(userAccountControl&2) ) )
Observa cómo:
- La regla de coincidencia LDAP userAccountControl:1.2.840.113556.1.4.803:= se convierte en userAccountControl& (el OID para AND bitwise se reemplaza con el operador &)
- Los nombres de atributos en el Evento 1644 usan el formato canónico del esquema (por ejemplo, userAccountControl)
- Todos los valores numéricos de bandera (16777216, 524288, 2) permanecen exactamente iguales en formato decimal
- Los atributos sin reglas de coincidencia (como msDS-AllowedToDelegateTo=*) no cambian en absoluto
- La transformación es puramente sintáctica: convierte la sintaxis de regla de coincidencia extendida de LDAP a la notación interna de consulta de Windows para fines de registro
Esta transformación es determinista y ocurre cada vez. Lo más probable es que la tabla de mapeo estática esté compilada dentro de los binarios del servicio LDAP de Active Directory, lo que explica por qué el comportamiento es consistente en todos los entornos de AD y no se puede modificar mediante configuración.
¿Por qué ocurre esta transformación?
Microsoft no documenta exactamente por qué ni dónde ocurre esta transformación. Lo que sabemos por las pruebas:
- El paquete LDAP en la red contiene la sintaxis del OID
- El Evento 1644 registra la sintaxis bitwise
- La transformación ocurre de manera consistente en todas las versiones de AD que he probado
Mi mejor suposición es que el procesamiento interno de consultas de AD convierte la sintaxis del OID a nivel de protocolo en un formato nativo antes de la ejecución, y el Evento 1644 captura el filtro después de la conversión. Pero eso es especulación. Lo que importa para la detección es saber que la transformación ocurre.
Figura 14: Una vista conceptual de las capas de procesamiento de consultas LDAP de Active Directory.
Dónde se definen las reglas de coincidencia
Los OID de las reglas de coincidencia no se almacenan como objetos configurables en la base de datos de Active Directory. En cambio, se definen en las especificaciones de protocolo de Microsoft y se implementan directamente en el código del servicio LDAP.
Puedes encontrar las definiciones oficiales en dos fuentes clave de Microsoft:
- Especificación MS-ADTS (sección 3.1.1.3.4.4): documenta el comportamiento a nivel de protocolo de las reglas de coincidencia LDAP
- Documentación de sintaxis de filtros de búsqueda de Microsoft: ofrece los detalles oficiales de implementación y ejemplos de sintaxis
La documentación de Microsoft define estos OID de reglas de coincidencia y sus operadores correspondientes:
|
OID |
Nombre de la capacidad |
Operador |
|
1.2.840.113556.1.4.803 |
LDAP_MATCHING_RULE_BIT_AND |
& |
|
1.2.840.113556.1.4.804 |
LDAP_MATCHING_RULE_BIT_OR |
| |
|
1.2.840.113556.1.4.1941 |
LDAP_MATCHING_RULE_TRANSITIVE_EVAL |
(procesamiento especial en cadena) |
|
1.2.840.113556.1.4.2253 |
LDAP_MATCHING_RULE_DN_WITH_DATA |
(vinculación binaria de DN) |
Estas reglas de coincidencia parecen estar implementadas en el servicio LDAP de AD. No las encontré expuestas como objetos de esquema configurables.
Esto es lo que importa para la detección: cuando el evento 1644 registra una consulta que usa estos OID, la sintaxis se transforma. El OID 1.2.840.113556.1.4.803 aparece como & en el filtro registrado. En mis pruebas en varios entornos de AD, este comportamiento se ha mantenido constante.
Nota rápida: la mayoría de las herramientas usan .803 (AND), pero .804 (OR) también funciona y se transforma en |. Tus reglas de detección deben manejar ambos operadores. Veremos esto en la Parte 2.
La documentación de Microsoft confirma que estos OID siguen la sintaxis estándar de extensibleMatch de LDAP. Herramientas como Impacket usan este mismo estándar al construir consultas. La transformación ocurre en algún punto entre la capa del protocolo LDAP y el registro de eventos. Microsoft no documenta exactamente dónde, pero para fines de detección, lo que importa es saber que ocurre.
Esto explicaría por qué:
- no pude encontrar las reglas de coincidencia como objetos de esquema consultables
- el comportamiento de transformación parece constante en los entornos de AD
- las reglas que buscan cadenas de OID en el evento 1644 muy probablemente no coincidirán
Impactos en la ingeniería de detección
Entender esta transformación importa porque explica por qué fallan tantos intentos de detección LDAP. Cuando empecé a escribir reglas para la detección de Impacket, cometí el mismo error que comete todo el mundo: asumí que los registros contendrían lo que las herramientas enviaban.
En la práctica, es más complicado:
- Nivel de protocolo: sintaxis OID (lo que envían las herramientas)
- Nivel de ejecución: sintaxis bitwise (lo que procesa AD)
- Nivel de registro de eventos: sintaxis posterior a la transformación (lo que registra el evento 1644)
Las reglas de detección deben apuntar al nivel de ejecución, no al nivel de protocolo. Por eso buscar 1.2.840.113556.1.4.803 en el evento 1644 siempre devolverá cero resultados.
Implicaciones prácticas
Esta comprensión técnica tiene varias implicaciones prácticas para la ingeniería de detección:
Detección independiente de la herramienta: dado que la transformación es determinista, cualquier herramienta que use 1.2.840.113556.1.4.803 producirá la misma firma bitwise en el evento 1644. Tus reglas detectarán Impacket, scripts personalizados de PowerShell y cualquier otra herramienta que use este OID.
Reglas a prueba de futuro: Microsoft no puede cambiar esta transformación sin romper la arquitectura fundamental de Active Directory. Tus reglas basadas en bitwise seguirán funcionando incluso si las herramientas evolucionan.
Atribución mediante correlación: para las herramientas que usan wldap32.dll (como Rubeus y el módulo AD de PowerShell), el evento 30 captura la sintaxis OID, mientras que el evento 1644 captura la sintaxis bitwise. Correlacionar ambos demuestra el uso específico de la herramienta. El evento 30 demuestra que la herramienta envió consultas basadas en OID, y el evento 1644 demuestra que AD las ejecutó. Ten en cuenta que Impacket y otras herramientas con implementaciones LDAP personalizadas no generarán el evento 30.
Estrategia de validación: puedes poner a prueba tu comprensión ejecutando consultas con distintas herramientas y observando el patrón de transformación constante. El módulo de Active Directory de PowerShell, ldapsearch y herramientas personalizadas deberían producir las mismas firmas bitwise al usar los mismos OID.
Validación: cómo comprobar la transformación
Si quieres ver esta transformación en acción, así puedes validarla tú mismo:
- Habilita el registro del evento 1644 en un controlador de dominio de prueba
- Ejecuta cualquier herramienta LDAP que use filtros basados en OID (Impacket, módulo AD de PowerShell, scripts personalizados)
- Captura el tráfico de red con Wireshark para ver el OID en el paquete LDAP
- Revisa el evento 1644 para ver la operación bitwise en el registro
- Compara ambos para confirmar la transformación
Esta validación práctica fue lo que me convenció de que la transformación era real y predecible, y no una rareza del registro o un caso aislado.
La transformación de OID a bitwise no es un error, es una característica. Así es como Active Directory equilibra el cumplimiento de estándares con los requisitos de rendimiento. Entenderla es la clave para escribir reglas de detección LDAP que realmente funcionen en entornos de producción.
Paso 4: cómo lograr una atribución correcta
El evento 1644 te dice qué pasó en el controlador de dominio, pero no te dice mucho sobre quién lo hizo o cómo. Ahí es donde el evento 30 resulta útil, siempre que hayas habilitado el registro del cliente LDAP. Ten en cuenta que el evento 30 se registra en la máquina de origen que hace la consulta, no en el DC. Esto requiere habilitar el registro de depuración del cliente LDAP (Microsoft-Windows-LDAP-Client/Debug), que está deshabilitado de forma predeterminada y puede generar un volumen importante.
Advertencia importante: el evento 30 solo lo generan herramientas que usan la biblioteca nativa de cliente LDAP de Windows (wldap32.dll). Esto incluye Rubeus, el módulo AD de PowerShell y la mayoría de las herramientas nativas de Windows. Impacket usa su propia implementación LDAP y no generará el evento 30, por lo que para detectar Impacket dependes únicamente del evento 1644.
El evento 1644 te da el qué, el cuándo y el dónde: qué filtro se ejecutó, cuándo ocurrió y cuál fue la base de búsqueda. Pero le falta el quién y el cómo: qué herramienta hizo la consulta, qué proceso, desde qué sistema específico.
Para las herramientas que usan wldap32.dll, el evento 30 captura la consulta exactamente como la envió la herramienta. Aquí tienes un ejemplo de Rubeus:
Figura 15: el evento 30 captura la sintaxis OID original antes de que AD la transforme en operadores bitwise
¿Ves el formato OID? 1.2.840.113556.1.4.803:=2 eso es lo que envió el cliente antes de que AD lo tradujera a bitwise.
Cuando correlacionas esto por marca de tiempo e IP del cliente, obtienes el panorama completo. El evento 30 demuestra que se usó el patrón OID, y el evento 1644 confirma que AD lo procesó como una operación bitwise. Juntos te dan una atribución definitiva en lugar de solo "alguien hizo consultas LDAP".
Esta correlación es valiosa para herramientas como Rubeus y el reconocimiento basado en PowerShell. Para Impacket y otras herramientas con pilas LDAP personalizadas, tendrás que depender únicamente de los patrones del evento 1644.
Paso 5: cómo escribir reglas que realmente funcionen
Ahora que entiendes qué se registra en realidad, puedes escribir reglas que se activen.
Nota: lo siguiente son fragmentos de lógica de detección que muestran los criterios clave de selección. Los nombres de campo usan Elastic Common Schema (ECS). Ajústalos para tu SIEM. El filtro Source coincide con conexiones remotas IP:puerto, y filtra las operaciones locales del DC.
findDelegation.py (reconocimiento de delegación):
detection:
selection_filter:
event.code: 1644
winlog.event_data.Source|re: '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}$'
winlog.event_data.LDAPFilter|contains|all:
- 'userAccountControl&16777216'
- 'userAccountControl&524288'
selection_attrs:
winlog.event_data.AttributesRequested|contains|all:
- 'msDS-AllowedToDelegateTo'
- 'msDS-AllowedToActOnBehalfOfOtherIdentity'
condition: selection_filter and selection_attrs
GetNPUsers.py (AS-REP Roasting):
detection:
selection:
event.code: 1644
winlog.event_data.Source|re: '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}$'
winlog.event_data.LDAPFilter|contains|all:
- 'sAMAccountType=805306368'
- 'userAccountControl&4194304'
condition: selection
GetUserSPNs.py (Kerberoasting):
detection:
selection:
event.code: 1644
winlog.event_data.Source|re: '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}$'
winlog.event_data.LDAPFilter|contains: '(servicePrincipalName=*)'
condition: selection
Nota: Esta regla es intencionalmente más amplia que las demás. Para filtros de Kerberoasting más precisos, consulta esta publicación.
GetADUsers.py (enumeración de usuarios):
detection:
selection_event:
event.code: 1644
selection_filter:
winlog.event_data.LDAPFilter|contains: '(sAMAccountName=*)'
selection_attrs:
winlog.event_data.AttributesRequested|contains|all:
- 'sAMAccountName'
- 'pwdLastSet'
- 'mail'
- 'lastLogon'
selection_attrs_count:
winlog.event_data.AttributesRequested_Count: 4
selection_src:
winlog.event_data.Source|re: '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}$'
condition: selection_event and selection_filter and selection_attrs and selection_attrs_count and selection_src
GetADComputers.py (enumeración de equipos):
detection:
selection_event:
event.code: 1644
selection_filter_short:
winlog.event_data.LDAPFilter|contains|all:
- '(objectCategory=computer)'
- '(objectClass=computer)'
selection_filter_long:
winlog.event_data.LDAPFilter|contains|all:
- '(objectCategory=CN=Computer,CN=Schema,CN=Configuration,'
- '(objectClass=computer)'
selection_attrs:
winlog.event_data.AttributesRequested|contains|all:
- 'sAMAccountName'
- 'dNSHostName'
- 'operatingSystem'
- 'operatingSystemVersion'
selection_attrs_count:
winlog.event_data.AttributesRequested_Count: 4
selection_src:
winlog.event_data.Source|re: '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}$'
condition: selection_event and (selection_filter_short or selection_filter_long) and selection_attrs and selection_attrs_count and selection_src
secretsdump.py (preparación para DCSync):
detection:
selection_event:
event.code: 1644
selection_attrs:
winlog.event_data.AttributesRequested|contains|all:
- 'msDS-PrincipalName'
- 'objectSid'
selection_attrs_count:
winlog.event_data.AttributesRequested_Count: 2
selection_src:
winlog.event_data.Source|re: '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}$'
condition: selection_event and selection_attrs and selection_attrs_count and selection_src
Paso 6: pruebas, y no engañarte a ti mismo
Las pruebas son donde descubres si tus reglas realmente funcionan o si solo te estás engañando. He visto demasiadas reglas que parecen perfectas pero nunca se activan con actividad real.
Cuando empecé a construir reglas de detección para las consultas de delegación de Impacket, mi primer enfoque parecía lógico: buscar la sintaxis LDAP exacta que envía Impacket:
# Mi primer intento, parecía lógico
selection:
winlog.event_data.LDAPFilter|contains: '1.2.840.113556.1.4.803'
# Resultado: 0 coincidencias, siempre
La regla se implementó con éxito. Se veía correcta. Pero nunca se activó. Ni una sola vez.
Ahí fue cuando investigué los registros del evento 1644 y descubrí la transformación de OID que vimos antes. Windows no registra los OID de la regla de coincidencia LDAP: registra la sintaxis bitwise convertida. Una vez que entendí esto, la solución fue directa:
# Después de entender la transformación
selection:
winlog.event_data.LDAPFilter|contains: 'userAccountControl&'
# Resultado: realmente se activa con actividad de Impacket
AD normalmente conserva las mayúsculas y minúsculas, pero no siempre:
winlog.event_data.LDAPFilter|contains:
- 'useraccountcontrol&524288'
Y distintas herramientas pueden usar distintos operadores bitwise:
winlog.event_data.LDAPFilter|contains|any:
- 'userAccountControl&'
- 'userAccountControl|'
La verdadera prueba es: ¿tu regla se activa con una repetición conocida como buena en el laboratorio y se mantiene en silencio durante un día normal de trabajo? Si falla en cualquiera de las dos pruebas, te queda trabajo por hacer.
Lo que aprendí por las malas
Después de años escribiendo reglas de detección LDAP, esto es lo que realmente importa:
- Valida siempre contra registros reales, no contra un comportamiento teórico. No puedo contar cuántas reglas he visto que apuntan a OIDs en el Evento 1644; parecen inteligentes, pero nunca se activan.
- El Evento 1644 por sí solo no es suficiente para la atribución. Combinarlo con el Evento 30 convierte "alguien hizo consultas LDAP" en "esta herramienta específica desde esta IP realizó un reconocimiento de delegación". (Nota: esta correlación funciona para herramientas que usan wldap32.dll como Rubeus, pero no para Impacket, que tiene su propia pila LDAP).
- Las herramientas evolucionan. Impacket se actualiza, surgen nuevas herramientas y las técnicas de ataque cambian. Construye metodologías, no solo reglas. El proceso de analizar código, generar telemetría, entender las transformaciones y validar la cobertura funciona sin importar qué herramienta específica intentes detectar.
- No pases por alto los patrones obvios. Todos se enfocan en filtros LDAP complejos, pero las herramientas que solicitan combinaciones de atributos extrañas como msDS-PrincipalName,objectSid suelen ser más fáciles de detectar y son indicadores más confiables.
La siguiente capa del infierno: cuando tu regla perfecta aún no coincide
Resolvimos el acertijo de la transformación de OID. Ahora sabes que debes buscar userAccountControl&524288 en lugar de 1.2.840.113556.1.4.803. Pero aquí está el detalle: incluso con este conocimiento, tus reglas podrían seguir fallando.
¿Por qué? Porque hay otra transformación ocurriendo que es aún más insidiosa: la normalización de espacios en blanco (o la falta de ella).
¿Ese filtro LDAP limpio y hermoso en tu regla de detección? Supone que el registro será igual de limpio. Spoiler: no lo será. El Evento 1644 de Active Directory conserva cada espacio, tabulación y salto de línea exactamente como se procesó. Y diferentes herramientas formatean sus filtros de manera distinta. La misma consulta lógica puede aparecer como:
- (userAccountControl&524288)
- ( userAccountControl & 524288 )
- (userAccountControl&524288 )
- Incluso con saltos de línea y tabulaciones en registros de producción
En la Parte 2, abordaremos la pesadilla de los espacios en blanco y te mostraremos cómo escribir reglas Sigma que manejen el caos de los registros LDAP del mundo real. Porque saber sobre la transformación de OID es solo la mitad de la batalla: tus reglas aún necesitan coincidir con lo que realmente se registra, espacios incluidos.
El panorama general
Las reglas específicas de este blog quedarán obsoletas a medida que las herramientas cambien. La metodología no. Empieza siempre por entender el comportamiento de la herramienta mediante el análisis de código. Prueba siempre en entornos controlados. Valida siempre contra telemetría real. Itera y mejora siempre.
Este enfoque funciona para cualquier herramienta de reconocimiento basada en LDAP: colectores de BloodHound, scripts de PowerShell personalizados, herramientas comerciales de pruebas de penetración, lo que sea. El proceso sigue siendo el mismo: analizar, emular, entender la transformación, construir reglas y validar de manera exhaustiva.
Entender la transformación de OID a operador bitwise es solo el comienzo. El verdadero valor viene de construir un enfoque sistemático que convierta cualquier comportamiento LDAP ofensivo en una lógica de detección confiable. Así es como pasas de la detección reactiva a la cobertura proactiva.
Lo que cubrimos
A lo largo de este proceso, usé estas herramientas de Impacket como ejemplos:
- GetNPUsers.py para la detección de AS-REP Roasting
- GetADUsers.py para la enumeración de usuarios
- GetADComputers.py para el inventario de equipos
- GetUserSPNs.py para la preparación de Kerberoasting
- findDelegation.py para el reconocimiento de delegación
- secretsdump.py con -ldapfilter para la preparación de DCSync
Las técnicas aplican a cualquier herramienta que use LDAP para el reconocimiento de Active Directory. La clave está en entender que lo que ves en el código fuente no es lo que obtienes en los registros, y en construir un proceso que tome en cuenta esa brecha.
Gracias a Jonathan Johnson, Charlie Clark, Anton Ovrutsky, Matt Anderson, Tyler Bohlmann, Lindsey O'Donnell-Welch, Michael Haag, Nasreddine Bencherchali, y Adam Bienvenu por su ayuda en la revisión de esta publicación.