
Ejecutar ejecutables de Windows no administrados en Balizas de CobaltStrike
Inline-Execute-PE es un conjunto de archivos objeto de Beacon (BOF) y un script Aggressor para CobaltStrike que permite a los Operadores cargar ejecutables de Windows no administrados en la memoria de Beacon y ejecutarlos, recuperando la salida y mostrándola en la consola de Beacon.
Esto permite a los Operadores usar muchas herramientas de terceros (Mimikatz, Dsquery, herramientas de Sysinternals, etc.) sin necesidad de descargarlas a disco, reformatearlas como código independiente de posición usando una herramienta como Donut, o crear un nuevo proceso para ejecutarlas.
Estos ejecutables se asignan en la memoria de Beacon para que puedan ejecutarse repetidamente sin necesidad de enviarlos a través de la red, asignar nueva memoria y crear un nuevo proceso conhost.exe cada vez.
Los ejecutables cargados en los Beacons son accesibles y pueden ser ejecutados por todos los clientes de CobaltStrike conectados al servidor de equipo de CobaltStrike.
Inline-Execute-PE fue diseñado para Beacons x64 y ejecutables de Windows x64 en C o C++ compilados con Mingw o Visual Studio. Este proyecto no soporta ejecutables x86 ni ejecutables x64 escritos en otro lenguaje o compilados con un compilador diferente.

Clone el repositorio y, opcionalmente, ejecute make para recompilar los BOF.
Cargue Inline-Execute-PE.cna en el cliente de CobaltStrike. Asegúrese de que el directorio desde el que se ejecuta CobaltStrike sea escribible por su usuario; Inline-Execute-PE crea un archivo de texto allí (petable.txt) para garantizar la disponibilidad de los datos necesarios para que Inline-Execute-PE funcione.
Inline-Execute-PE consta de 3 comandos orientados al objetivo que ejecutan BOF y 3 comandos internos que manipulan la estructura de datos del proyecto:
Orientados al objetivo:
Estructura de datos interna:
peload es el inicio de Inline-Execute-PE. Este comando se utiliza para cargar un PE en la memoria de Beacon. Realiza las siguientes acciones principales:
perun es el segundo paso en Inline-Execute-PE. Realiza las siguientes acciones principales:
peunload se llama para eliminar el PE de la memoria de Beacon cuando un Operador termina con él o desea cargar un PE diferente. Realiza las siguientes acciones principales:
petable se utiliza para mostrar información sobre todos los PE cargados actualmente en los Beacons.
Cada cliente de CobaltStrike tiene su propio petable; Inline-Execute-PE hace grandes esfuerzos para garantizar la sincronización de sus datos entre todos los clientes de CobaltStrike conectados para que los PE puedan ser utilizados por todos los Operadores. Para más información, consulte "Consideraciones de diseño y comentarios".

peconfig se utiliza para configurar opciones relacionadas con el funcionamiento de Inline-Execute-PE. Las dos opciones actuales que se pueden modificar son:
pebroadcast se puede usar para transmitir manualmente el contenido del petable de un Cliente a todos los demás clientes de CobaltStrike conectados.
Cada otro cliente de CobaltStrike actualizará su petable con los datos transmitidos. Esto nunca debería ser realmente necesario, pero la característica existe por si acaso.
Use peload para cargar un PE en la memoria de Beacon

Alternativamente, si hay un PE en la máquina objetivo que desea usar sin crear un nuevo proceso, proporcione la ruta y el conmutador --local

Llame a perun, pasando cualquier argumento al PE cargado

Las comillas dobles en los argumentos deben escaparse con barras invertidas

Si ha identificado que un PE causa problemas al intentar liberar DLL durante la descarga, use peconfig para establecer unloadlibraries en false

Una vez que haya terminado de usar un PE, llame a peunload para limpiarlo de Beacon

Ahora se puede cargar un PE diferente en el Beacon

Debe tener cuidado con los argumentos de línea de comandos que pasa al PE; algunos PE se bloquearán directamente si se les dan argumentos incorrectos, mientras que otros se ejecutarán sin fin, haciendo que Beacon nunca responda aunque el proceso aún se esté ejecutando.
Esto se puede ver con Mimikatz.exe cuando no se especifica 'exit' al final de la lista de argumentos

...

Inline-Execute-PE terminará el hilo del PE en ejecución después de que se haya alcanzado el valor de timeout especificado. Esto permite que Beacon pueda reanudar las comunicaciones normales (Beacon no responde hasta que el BOF perun haya completado su ejecución). Aunque aún se pueden usar comandos normales de CobaltStrike y otros BOF en este Beacon, Inline-Execute-PE ahora está deshabilitado; cuando un PE en ejecución se termina de esta manera, parece romper stdout y stderr en el proceso de Beacon, y los PE cargados posteriormente no funcionan correctamente.
El PE aún puede (y debe) descargarse de la memoria de Beacon, sin embargo, mirar petable mostrará que este Beacon ya no puede tener PE adicionales cargados en él. 
Es imperativo que pruebe los PE que desea ejecutar usando Inline-Execute-PE, y que tenga cuidado al dar argumentos de línea de comandos a perun. Algunos PE son más tolerantes que otros.
A continuación se presentan, sin ningún orden en particular, algunas observaciones realizadas durante las pruebas y el desarrollo con respecto a ciertos PE que los usuarios podrían querer cargar en Beacon.
Los IOC asociados con Inline-Execute-PE incluyen, entre otros:
No le di una prueba completa contra un EDR durante el desarrollo, en parte por pereza y en parte por falta de disponibilidad de un entorno de prueba. Sin embargo, se probó contra la última versión de Windows Defender (que en mi experiencia es un producto AV bastante bueno).
Mimikatz.exe es probablemente el PE más sospechoso y conocido que viene a la mente como candidato para usar con Inline-Execute-PE. Descubrí que la capacidad de Windows Defender para detectar Mimikatz ejecutándose con Inline-Execute-PE dependía del proceso en el que se estuviera ejecutando Beacon.
Un beacon ejecutándose en un ejecutable independiente (piensa en beacon.exe con artifact kit para que pueda ejecutarse y funcionar normalmente más allá de Defender) será detectado al usar Mimikatz.exe con Inline-Execute-PE.
Un beacon ejecutándose en un proceso de Windows (inyectado en Explorer.exe, notepad.exe, etc., o DLL cargada lateralmente en un proceso legítimo) NO será detectado al usar Mimikatz.exe con Inline-Execute-PE.
En cuanto a los EDR que realizan hooking en el espacio de usuario, como dije, no lo he probado, pero tengo las siguientes reflexiones generales:
Dado que el PE se ejecuta dentro del proceso de Beacon, que presumiblemente ya ha desenganchado/refrescado NTDLL dentro de él, creo que no debería tener demasiados problemas con que las llamadas a la API realizadas por el PE sean marcadas. Los mismos problemas con respecto a lo que realmente hace el PE (toca procesos, altera claves de registro, etc.) aún se aplican.
Hace un par de meses me encontré con RunPE-In-Memory y tuve la idea de intentar convertirlo en un BOF para CobaltStrike. El viaje que siguió fue mucho más complejo y tomó mucho más tiempo de lo anticipado. Este proyecto fue particularmente desafiante porque no es una herramienta independiente por derecho propio, es una herramienta utilizada para ejecutar otras herramientas. Esto requiere una gran flexibilidad y esfuerzo hacia la compatibilidad con una amplia gama de PE y todas las diferentes formas en que esos PE podrían lograr la misma tarea (obtener argumentos, terminar, etc.).
Al principio, Inline-Execute-PE fue concebido como un BOF todo en uno, responsable de cargar, ejecutar y liberar un PE en un Beacon. Aproximadamente 3 semanas después del proyecto, momento en el que tenía un POC ~75% completo, encontré Pezor que se lanzó hace ~1.5 años y ya hacía casi todo lo que estaba tratando de hacer; la principal diferencia era que Pezor llamaba a Donut internamente para convertir el PE en shellcode, en lugar de mapear manualmente el PE original en memoria.
Este descubrimiento fue bienvenido en un sentido y decepcionante en otro; fue fenomenal tener un proyecto maduro del cual inspirarme y ayudarme a superar algunos puntos difíciles en mi código, pero desalentador en que efectivamente había estado reinventando la rueda sin saberlo. Después de leer sobre Pezor y pensar en su diseño, algunos asuntos relacionados con el tradecraft y las necesidades operativas de mi organización, alteré el curso de Inline-Execute-PE a lo que ve hoy. Esta decisión fue impulsada por varios factores que se discutirán a continuación, así como algunas de las opciones de diseño más curiosas que pueden haber levantado cejas para aquellos que han leído hasta aquí.
Al examinar mi experiencia operativa, encontré múltiples instancias y herramientas donde necesitaba ejecutar la herramienta repetidamente; con Pezor, un Operador debe enviar repetidamente el PE a través de la red, crear un conhost.exe, asignar nueva memoria en Beacon, etc., lo que me pareció potencialmente indeseable al considerar AV/EDR. Esta línea de pensamiento llevó a la idea de 'cargar' un PE en Beacon, de manera similar a como se puede cargar un .PS1 en Beacon para uso repetido. El conhost.exe se crea cuando el PE se carga por primera vez y persiste mientras el PE está cargado en memoria; de manera similar, se asigna nueva memoria para el PE una vez cuando se carga por primera vez y, por supuesto, evita tener que enviar el PE a través de la red cada vez que se desea usar. El modelo que adoptó Inline-Execute-PE no está exento de fallos, que intenté abordar con diversos grados de éxito.
Una elección de diseño que debería saltar a la vista es el hecho de que Inline-Execute-PE mapea el PE en Beacon DOS VECES. Esto ciertamente no es deseable ni una elección que hice voluntariamente, sino que nació de la necesidad. Como se mencionó anteriormente, Inline-Execute-PE debe enganchar varias funciones relacionadas con los argumentos de línea de comandos en el PE. Debido a que el PE mapeado se ejecuta dentro del proceso de Beacon, el PE intentará usar los argumentos de línea de comandos especificados en la sección PROCESS_PARAMETERS del PEB; para evitar esto, cuando el PE llama a una de las diversas funciones que recuperan los argumentos de línea de comandos, debemos dirigir el PE a nuestras propias funciones personalizadas donde podemos proporcionar los argumentos previstos tal como se pasaron desde CobaltStrike usando perun.
Esto funciona bien, pero durante el desarrollo noté algo extraño con varios PE diferentes. La primera vez que se ejecutó el PE, la función personalizada que proporcionamos al IAT del PE se llamó correctamente; sin embargo, en todas las veces posteriores en que se ejecutó el PE y se proporcionaron argumentos diferentes, el PE no llamó a la función personalizada y, por lo tanto, no recibió los argumentos pasados desde CobaltStrike. No estoy seguro de lo que realmente está sucediendo bajo el capó, pero me lleva a creer que después de que el PE se ejecuta una vez, copia los argumentos de línea de comandos en algún lugar de la memoria, y en ejecuciones posteriores busca esa ubicación en la memoria primero antes de llamar a las funciones enganchadas para recuperar los argumentos de línea de comandos como lo hizo la primera vez. Corroboré esta teoría recuperando la ubicación en la memoria donde residía un puntero a otro puntero a la matriz de punteros que contiene los argumentos, y modifiqué manualmente esta ubicación en la memoria para contener el puntero adecuado en cada ejecución. Esto funcionó para las funciones __getmainargs y __wgetmainargs, pero otros PE llaman a funciones alternativas como __p___argv y __p___argc para las cuales este método no funcionó.
Para poder "restablecer" el PE a un estado en el que realmente llame a las funciones enganchadas para obtener argumentos, recurrí a hacer una segunda copia del PE en memoria durante peload. Esta copia también está cifrada con XOR y permanece con protecciones RX durante toda la vida útil de Inline-Execute-PE, simplemente usándose para sobrescribir la copia del PE que realmente se ejecuta con perun. Como se mencionó, no es una solución perfecta, pero es una solución general que cubre todos los PE sin necesidad de perderse en los detalles tratando de encontrar una solución para todos los diferentes PE existentes y las diferentes API que utilizan.
Siendo uno de los principales puntos de venta de Inline-Execute-PE que se pueden ejecutar herramientas sin crear nuevos procesos, es un duro golpe tener que... crear un nuevo proceso (conhost.exe) para hacerlo. Este requisito proviene del hecho de que los flujos estándar (stdin/stdout/stderr) no se inicializan en los programas de Windows a menos que haya una consola presente. En nuestro caso no necesitamos la consola en absoluto; los flujos estándar se redirigen a una tubería anónima y se capturan de esa manera, pero sin el conhost los flujos no se inicializan y no se pueden redirigir.
Inline-Execute-PE aborda el problema del conhost de la misma manera que lo hace Pezor, llama a AllocConsole e inmediatamente después lo oculta de la vista usando ShowWindow. En una VM de Windows 11 con 8 GB de RAM nunca veo la ventana de la consola parpadear y desaparecer, pero el resultado variará dependiendo del sistema objetivo.
Hablé con un desarrollador que trabaja en un C2 comercial muy avanzado que recientemente lanzó un equivalente nativo (bueno, una versión mucho más avanzada) de Inline-Execute-PE, quien me dijo que pudieron evitar generar un conhost.exe "engañando a Windows para que pensara que tenía una consola". Con esta pista pasé aproximadamente una semana buscando en Internet documentación sobre cómo los programas de Windows interactúan con conhost, tratando de rastrear las llamadas a la API asociadas con funciones de escritura y la consola en WinDBG, e incluso examinando el código fuente de Windows Terminal que, sorprendentemente, está disponible en Github. Aunque aprendí mucho sobre el PEB y cosas relacionadas con los flujos estándar, salí del otro lado de esto con las manos vacías. Sospecho que el camino a seguir podría implicar parchear ciertas funciones relacionadas con la consola en kernel32, pero no lo sé. Honestamente, estoy bastante decepcionado de no haber podido encontrar una solución aquí, pero siendo autodidacta y con solo unos años de carrera, probablemente sea de esperar.### Tiempo de espera y rescate de PE Todos aquellos que alguna vez han intentado escribir un BOF saben que, a pesar de todas las ventajas que conllevan, existe un gran peligro en el hecho de que un error o bloqueo en tu BOF puede y matará tu Beacon. El peligro se amplifica en este proyecto debido a la naturaleza de cuánto control tienen los usuarios sobre los datos pasados a Inline-Execute-PE y a cuántas pocas medidas de seguridad puede implementar de manera fácil o fiable yo, el desarrollador. Los usuarios podrían, por ejemplo, bloquear su Beacon cargando un PE x86 en un Beacon x64, o mucho más comúnmente pasando argumentos incorrectos al PE mapeado como mencioné antes. Aunque no puedo evitar que los usuarios bloqueen sus Beacons con malos argumentos para sus PE, puedo intentar rescatar su Beacon en el caso de un PE que se ejecute sin fin, como en el caso de Mimikatz cuando no se especifica 'exit'.
Idealmente podría detener la ejecución del PE, permitiendo que Beacon reanude su funcionamiento normal, y luego dejar que el usuario lo intente de nuevo con los argumentos (esperemos) correctos esta vez. En la práctica descubrí que terminar el PE parece romper los FILE* asociados con stdout/stderr, e incluso descargar completamente el PE y luego cargarlo de nuevo no soluciona esto; están rotos a nivel de todo el proceso.
Para terminar un PE que continúa ejecutándose más allá de la opción 'timeout', se llama a TerminateThread en el identificador devuelto por CreateThread. Esto no permite que el hilo salga de manera ordenada, por lo que tiene sentido que algunas cosas se rompan. Intenté mitigar esto implementando secuestro de hilos, con el objetivo de suspender el hilo del PE y redirigir su ejecución a la API ExitThread(). La esperanza era que si era el hilo el que iniciaba los procedimientos de salida (en lugar de ser terminado forzosamente desde fuera), podría resultar en que stdout/stderr siguieran funcionando, pero terminé teniendo el mismo problema (además de experimentar una incapacidad para suspender el hilo del PE en el caso de Mimikatz).
Al no poder mitigar este problema, me decidí simplemente a evitar que los usuarios puedan continuar ejecutando el PE o cargar PE adicionales en el Beacon afectado (lo QUE resultaría en un bloqueo). Este es otro caso en el que Inline-Execute-PE no alcanza donde me gustaría, pero me conformé con que el Operador al menos conserve su Beacon y pueda usarlo para funcionalidad normal.
Una parte desafiante de este proyecto fue garantizar la disponibilidad de los PE cargados en Beacons para todos los Clientes de CobaltStrike conectados al Team Server. Los datos de Inline-Execute-PE se almacenan en estructuras creadas por Inline-Execute-PE.cna, que deben cargarse en cada Cliente que desee usar la herramienta; como resultado, estas estructuras de datos residen dentro de cada Cliente, no en el Team Server. Si estos datos residieran en una única ubicación central (TS), sería trivial recuperarlos desde cada Cliente y todo esto no sería un problema; si el equipo de CobaltStrike integrara formalmente una capacidad como Inline-Execute-PE en CobaltStrike, estoy seguro de que irían en esa dirección. Pero como esto es un complemento de la comunidad, nos las arreglamos con lo que tenemos.
Hay varios escenarios diferentes que debemos considerar para asegurar que cada Cliente de CobaltStrike tenga los datos más recientes y precisos sobre los PE cargados en los Beacons:
Se adoptó un enfoque multifacético para abordar estos escenarios. Para manejar el caso en que solo un Cliente de CobaltStrike está conectado al TS (y por lo tanto es la única entidad que tiene los datos petable), cada vez que el Cliente modifica la petable (peload, peconfig, peunload, etc.) también escribe el contenido de su petable en un archivo de texto local ubicado en el directorio de CobaltStrike. Si el Cliente sale/reinicia, o cuando se vuelve a cargar Inline-Execute-PE.cna, primero intentará leer desde el archivo local petable.txt para poblar su petable en memoria.
Cuando varios Clientes están conectados a un TS y un nuevo Cliente se une (según el Registro de Eventos), cada Cliente obtiene una lista de todos los usuarios conectados al TS y la ordena alfabéticamente. El Cliente que está primero en esa lista es seleccionado como Cliente "Broadcast", y después de esperar 5 segundos (para permitir que el nuevo Cliente se inicialice y lea su petable.txt local) enviará mensajes (Acciones) en el Registro de Eventos por cada entrada en su petable. Todos los clientes (excepto el que transmite) leerán estos mensajes y actualizarán sus petables con la información transmitida; esto incluye actualizar las entradas existentes y agregar cualquier adicional que sus respectivas petables no contengan.
Las operaciones normales que involucran Inline-Execute-PE también dependen del envío de mensajes en el Registro de Eventos. Cuando el Cliente A ejecuta peload, se transmite un mensaje que contiene toda la información pertinente de la petable; TODOS los clientes actualizan sus respectivas petables analizando estos mensajes del Registro de Eventos transmitidos usando el hook "on Event_Action". También se realizan cambios en los datos de Inline-Execute-PE cuando peload y peunload terminan de ejecutar sus BOF; estos cambios son comunicados de vuelta por Beacon (por ejemplo, después de ejecutar peload, Beacon responde con la ubicación de memoria de la estructura pMemAddrs) y, por lo tanto, son visibles para todos los Clientes conectados, que actualizan sus respectivas petables usando el hook "on Beacon_Output".
Estos esfuerzos combinados resultan en que Inline-Execute-PE pueda sincronizar datos críticos de manera eficiente y fiable entre múltiples Clientes.
Este proyecto no habría sido posible sin los siguientes proyectos y recursos, que fueron consultados en gran medida y de los cuales se originaron partes centrales de este proyecto. Muchas gracias a los autores por su código y su visión.