Golang y CGO: Artemis 0x1
Golang es un lenguaje relativamente nuevo; ha tenido mucho impacto por su sencillez, porque es compilado y tiene mucha similitud a lenguajes como C/C++. En este post intentaré mostrar que fue lo que aprendí en el tiempo que he usado Go para construir malware.
Si te preguntas porqué en el título he colocado artemis; se debe a la herramienta que he esta desarrollando en la última semana, justamente desarrollado en Go. No hay ninguna motivación detrás, ni busco competir con otras (mejores) herramientas; solo la hice para explicar diferentes cosas con respecto a Go y C; y la escalabilidad que me puede dar en el futuro.
Golang
Creado por Rober Griesemer, Rob Pike y Ken Thompson, desarrolladores de Google allá por el año 2009, que proponía una alternativa a los lenguajes tradicionales para el desarrollo de software, tales como C++ y Java. Esta pensado en solucionar el flujo de compilación, ejecución y automatización; y adicionalmente ofrecer una mejor eficiencia en el desarrollo. Hay muchas cosas más que se pueden decir de este lenguaje en torno a la construcción de software; sin embargo voy a enfocarlo exclusivamente al desarrollo de malware.
Malware y Go
Hace mucho tiempo empecé a usar Go para desarrollar pruebas de concepto enfocado en malware; y respondiendo a la pregunta de sí puede ser usado en un “escenario real”; la respuesta es no; y trataré de convencerte porqué; al final puedes no estar de acuerdo y tomar otra decisión.
Una de las ventajas de usar Go es que es multiplataforma; puede usarse un código base para desplegar el mismo malware en Windows, GNU/Linux o MacOSX; lo cuál es un ahorro de tiempo increíblemente grande. Al ser nativo, este no tiene dependencias y solo va directo por las syscalls; y de aquí deriva el primer problema: el tamaño del archivo.
Un malware compilado en Golang puede tener un tamaño entre 1mb a 4mb; lo cual es muchísimo si se piensa en perspectiva. Esto se debe a la arquitectura que tiene actualmente el lenguaje para el manejo del stack trace en runtime ( lo cual pasa con Java y .Net, solo que estos si tienen dependencias). Esto se vio incluso mas afectado en las versiones actuales del lenguaje; para optimizar el inicio de un binario, se embebió el framework no comprimido dentro del binario(release); esto sumále que Go linkea de forma estática las librerías. (si lo piensas como atacante claro) Si deseas leer más te recomiendo pases por acá donde expican algunas cosas adicionales: https://dr-knz.net/go-executable-size-visualization-with-d3.html
Para un atacante ya esto es complicado, dado que no puede ser enviado por correo o incluso hacer un troyano con esto. Pero no todo es malo(según como lo veas); ya que muchos antimalwares por el mismo hecho de ser muy largos, ignoran mucho de lo que hay dentro o simplemente se les hace más difícil identificarlo.
Cuando desarrollas malwares un punto clave es el manejo de errores; ya que cuando un malware se despliega en un entorno real, no esperas que tu operación se termine con un algún error no controlado (SIGSEGV por ejemplo). Aquí Go desde mi perspectiva hace un buen trabajo, a pesar que se criticó su poca capacidad para manejar errores no controlados. Y eso creo que es perspectiva; si vienes de C no te harás mucho problema porque trabajas de una forma parecida, asumiendo que toda instrucción puede generar error. Diferente si vienes de lenguajes como Python o C#, donde puedes encerrar todo un bloque código con try catch y te olvidas del problema, muchas veces sin saber que paso o porque no funcionó. Pues esto no pasa Go (y C también jeje), donde debes ser consciente de los errores (o posibles errores) que puede generar un método, y de esta forma aseguras el correcto funcionamiento de todo.
Cuando un atacante desarrolla un malware no solo se preocupa por el código que escribe si no también hay un trabajo lógico en la evasión por comportamiento; y es aquí donde entra un problema adicional. Los antimalwares como sabemos analizan las instrucciones a nivel estático, y dinámico; y dentro de este último hay un análisis en función a que utiliza el malware para lograr algo. Por ejemplo un EDR puede monitorear ciertos WinAPIs que son comunes en un process hollowing y de esa forma detectar que se está realizando un ataque de este tipo. Teniendo esto claro, cuando se hace un malware en Go es muy difícil cambiar esto; llegar al punto de modificar el “espacio” en el que se ejecuta el malware es complicado por la accesibilidad que tiene GO; no digo que no se pueda, claro que es posible; sin embargo requiere de mucho más esfuerzo que hacerlo en C/C++. El uso de syscall para el desarrollo de malware en Go es bastante común porque permite acceder a estas WinAPI, y por ejemplo no es muy difícil para un AV/EDR saber que has importado esa función ( o cualquier otra) para detectar una firma y así terminar en una detección de malware.
Otro punto en consideración es CGO, y particularmente una de las razones por las que usé Go para desarrollar artemis. Está de más decir que Java, C# o Python no son los mejores para hacer un malware; eso nos deja con pocas opciones (c++ y delphi no cuentan aquí :D). CGO te permite agregar código C directamente a tu proyecto de Go, lo cuál es excelente; pero como todo, un gran poder conlleva una gran responsabilidad
Hace unos años (cuando empecé con Go) era posible tener una estructura de proyectos casi independiente de Go y C lo cual era genial; sin embargo, esto al parecer ya no es posible (y si es posible no encontré la forma de como hacerlo :D). Pero según esto no se puede.
https://codereview.appspot.com/149720043
Bueno en realidad te mentí, si se puede pero no con archivos C si no utilizando .h y haciendo uso de inline
. En fin, es obligatorio usar CGO para poder agregar código C en tu proyecto, según su propia documentación te dicen si pero no (si lo puedes usar, pero quizá no deberías) https://golang.org/doc/faq. Esto en realidad más que un problema es algo bueno para el desarrollo de malware, Go al no tener dependencias puede correr de forma nativa, y además tener la capacidad de enlazarse con programas en C/C++, a pesar que no los llama directamente; ya que usa una interface de comunicación; pero es lo de menos, para este caso que se quiere solo desarrollar un malware.
Artemis v0.1
Hace unos meses decidí crear un framework que genere payloads y que sea escalable. Finalmente decidí usar Go; a pesar de los pocos beneficios que trae en hacer malware, es un lenguaje bastante simple y hacer un ejemplo de esto aplicando un “escenario real” me pareció interesante. Pero quise hacer algo diferente con respecto a las herramientas en Go que ya vienen circulando; algo que adicionalmente me fuera de utilidad.
Luego de pensarlo bien, decidí hacer un RAT; algo simple que pueda ser didáctico y escalable como ya dije. Lo primero fue definir la arquitectura que tendría; y como todo RAT deberá tener una conexión remota a un dispositivo (windows en este caso). Pero además quise que agregar algo adicional, la capacidad de poder leer un DLL directamente en memoria (reflective dll) y que el malware tenga un procedimiento lo más genérico posible para poder parametrizar esta dll.
Lo primero fue lo mas simple; hacer un reflective DLL. En un principio pensé hacerlo en Go, sin embargo no encontré alguna referencia que pudiera ser de utilidad, así que finalmente decidí usar CGO; que es precisamente el foco de este malware.
Puedes aprender a hacer un Reflective DLL desde esta publicación, sin embargo no es suficiente, ya que necesito que la función que llame pueda ser parametrizada. Podría hacerlo a través de envs o de forma externa pero iba a ser algo complicado. Así que pensé en lo más fácil; si ya tengo la base del DLL porque voy directamente a buscar la función que necesito en el IMAGE_DATA_DIRECTORY
. Esto es importante porque también recordé que la función que voy a invocar lo haré desde el server; irá hasta el cliente y finalmente se ejecutará con los parámetros que correspondan.
Para que se entienda que hice explicaré el proceso que llevará el malware:
- El malware se ejecuta en client y hace una conexión remota hacia server.
- Desde server se lanza una sesión que me permite interactuar con client; dentro de esta consola interactiva puedo desplegar una función de la DLL
- Si agrego un nuevo módulo, por ejemplo un nuevo expoit; puedo desarrollarlo en C y poder enviarlo por el mismo socket sin cambiar absolutamente nada en el entorno client
- Desde server se envía el nuevo exploit, client descarga nuevamente la DLL, y ejecuta el módulo especificado junto a sus parámetros.
El flujo como ves no es nuevo; de hecho muchos malwares ya lo hacen, así que no supone un reto técnico más que conocer que técnicas deben aplicarse. Si estas familiarizado con C sabrás que hay una tarea algo “complicada”; y es generar esta “call” genérica. Es decir si llamo a una función nueva el client debe poder invocarla sin conocer su estructura. Para esto no me complique la vida y construí una definición genérica con una estructura de posible valores como parámetros; que ya explico luego.
Si ya leíste la publicación de reflective dll; ves que al final se hace la call al entrypoint de la DLL.
DLLEntry DllEntry = (DLLEntry)((DWORD_PTR)dllBase + ntHeaders->OptionalHeader.AddressOfEntryPoint);
(*DllEntry)((HINSTANCE)dllBase, DLL_PROCESS_ATTACH, 0);
Pues esto no me sirve porque supone que tendría una única función como EP en mi DLL; y no es el caso; lo que voy a tener en cada DLL será una función como EP con una estructura como esta:
int artm_test(...){
MessageBox(0,"hacked","Test from remote" ,0);
return 1;
}
Por lo pronto está en tener la capacidad de definir como string esa función y ese parámetro. Si sé que el artm_test
es una función exportada del DLL y ya tengo la base del DLL haciendo el reflective DLL; solo me basta con ir a IMAGE_DATA_DIRECTORY
como ya dije y extraer las funciones.
typedef int( * Generic_Prototype) (W64Pointer *object);
....
IMAGE_DATA_DIRECTORY idd = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
PIMAGE_EXPORT_DIRECTORY nExp = (PIMAGE_EXPORT_DIRECTORY)( dllBase+idd.VirtualAddress);
PDWORD expFns = (PDWORD)(dllBase + nExp->AddressOfFunctions);
PDWORD expNms = (PDWORD)(dllBase + nExp->AddressOfNames);
PVOID fn = NULL;
PVOID name;
for(int i=0;i<nExp->NumberOfNames;i++)
{
name = (PVOID)(dllBase+expNms[i]);
//compara la función hasta obtenerla
if(memcmp(name, ep, strlen(name))==0){
fn = (PVOID)(dllBase+expFns[i]);
break;
}
}
if(!fn){return 0;};
Generic_Prototype prototype_fn = (Generic_Prototype)fn;
prototype_fn(...);
VirtualFree(dllBase, 0, MEM_RELEASE);
//DLLEntry DllEntry = (DLLEntry)((DWORD_PTR)dllBase + ntHeaders->OptionalHeader.AddressOfEntryPoint);
//(*DllEntry)((HINSTANCE)dllBase, DLL_PROCESS_ATTACH, 0);
Con esto lo que hago básicamente es obtener la función que corresponde y a través de un prototipo genérico la invoco y le paso una estructura; al final le agrego un VirtualFree
dado que todo se volverá a ejecutar. Hasta ahora todo simple, nada nuevo; lo que toca hacer es colocar esto dentro un archivo loader.h
e importarlo a un archivo .go
//#cgo CFLAGS: -Iw64
//#cgo LDFLAGS: -Lw64
// #include <stdlib.h>
// #include "loader.h"
import "C"
...
La estructura de los módulo que planeo implementar queda algo así:
Po un lado tengo las funciones de Go que en resumen me ayudan con la parte de la administración y por otro lado los modulos que son básicamente los payloads que voy a enviar a través del Reflective DLL. Luego de generar el payload ( esto lo explicaré en una siguiente publicación )
Entonces haciendo finalmente las pruebas:
Genero un listener que escuche el puerto del payload:
Al ejecutar el malware payload generado en host de windows; este se conecta al server y ya puedo enviar la DLL y ejecutar una función.
Y bueno obviamente funciona jeje:
¿Ahora qué?
Esta primera parte la voy a dejar aquí, si bien es cierto hasta el momento no he presentado nada nuevo; quería dar pie a lo que haré con esta tool en el futuro. Por el momento solo tengo un par de funciones (process hollowing y ppid spoof); pero planeo agregar más y posteriormente generar un payload con motor polimórfico.
La herramienta no la he publicado aún en gitlab ; pero espero poder hacerlo estas semanas; te dejo por mientras el análisis que arrojan los av y una demo del Reflective DLL (generic version) jeje no se como llamarlo.
Demo Reflective DLL (Generic Exported Function) CGO:
Referencias
https://www.ired.team/offensive-security/code-injection-process-injection/reflective-dll-injection
https://unit42.paloaltonetworks.com/the-gopher-in-the-room-analysis-of-golang-malware-in-the-wild/