martes, 15 de noviembre de 2011

Cifrando un ejecutable en C (2º intento)

Hace algunos meses hice un programa que cifraba ejecutables en C, mas tarde comente los problemas que podía dar y qué ejecutables no era capaz de cifrar.

Ahora quiero seguir con ello y para retomar el tema tranquilamente he reorganizado el código del “crypter” utilizando los ficheros de funciones que comencé en esta entrada y ya de paso he añadido código para detectar cuando no es posible cifrar el programa. Para recordar esto podía suceder en las siguientes situaciones:

  • No hay espacio suficiente en la cabecera PE para agregar una sección.
  • La tabla de importaciones está en la sección de código (.text, .code) que es la que cifra mi programa. Esto hace que la tabla quede cifrada y no pueda ser leída por el cargador de Windows.
  • La tabla de importaciones o la “Bound Import Table” está justo después de la tabla de secciones, de modo que si se agrega una sección sobrescribe estas tablas dañándolas.

A continuación pongo el contenido del fichero main.cpp

#include <stdio.h>
#include <windows.h>
#include "PECore.h"

//Rutina encargada de descifrar la sección cifrada
byte uncryptroutine[] =    //21 bytes
 "\xB8\x00\x10\x40\x00"  //B8 00104000  MOV EAX, 401000    Section Start  ||Este valor será modificado
 "\x80\x30\xBB"          //8030 BB      XOR BYTE[EAX], BB  Key
 "\x40"                  //40           INC EAX
 "\x3D\x00\x45\x40\x00"  //3D 00454000  CMP EAX, 404500    Section End  ||Este valor será modificado
 "\x75\xF5"              //75 F5        JNZ SHORT -11      (Complemento a dos de 11 = F5)
 "\xB8\x00\x20\x40\x00"  //B8 00204000  MOV EAX, 402000    Pone en EAX el OEP || Este valor será modificado
 "\xFF\xE0";    //FFE0         JMP EAX     Salta al OEP

//Posiciones de la rutina que tienen que ser corregidas
const int SectionStartPos = 1;
const int KeyPos = 7;
const int SectionEndPos = 10;
const int OEPPos = 17;

const byte CipherKey = 0xBB;

//Modifica la rutina de descifrado para introducirle los datos que cambian
void ChangeUncryptRoutine(byte *uncryptroutine, DWORD VirtualStart, DWORD VirtualEnd, DWORD OEP, byte CipherKey)
{
 memcpy(&uncryptroutine[SectionStartPos], &VirtualStart, sizeof(DWORD));
 memcpy(&uncryptroutine[SectionEndPos], &VirtualEnd, sizeof(DWORD));
 memcpy(&uncryptroutine[OEPPos], &OEP, sizeof(DWORD));
 memcpy(&uncryptroutine[KeyPos], &CipherKey, sizeof(byte));
}

int main(int argc, char *argv[])
{
 if(argc != 2)
 {
  printf("Pasame un archivo como argumento, ej: %s program.exe\n", *argv);
  return EXIT_FAILURE;
 }
 //Necesitamos saber el pINH->OptionalHeader.FileAlignment para saber exactamente el tamaño del nuevo ejecutable una vez cifrado
 DWORD fileAlignment = GetFileAlignment(argv[1]);
 if (fileAlignment == -1)
  return EXIT_FAILURE;

 DWORD fileSize = GetFileSize(argv[1]);
 if (fileSize == -1)
  return EXIT_FAILURE;
 //El nuevo tamaño es el original mas el tamaño de la sección alineada
 DWORD newSize = fileSize + Align(sizeof(uncryptroutine) ,fileAlignment);
 MapInfo mapInfo;
 if (OpenAndMapFile(&mapInfo, argv[1], newSize) == -1)
  return EXIT_FAILURE;
 if (!IsValidExe(mapInfo.viewMappedFile))
 {
  printf("%s no es un archivo ejecutable valido\n", *argv);
  return EXIT_FAILURE;
 }
 PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)mapInfo.viewMappedFile;
 PIMAGE_NT_HEADERS pINH = (PIMAGE_NT_HEADERS)&mapInfo.viewMappedFile[pIDH->e_lfanew];
 PIMAGE_SECTION_HEADER pISH = RVAToSection(mapInfo.viewMappedFile, pINH->OptionalHeader.AddressOfEntryPoint);
 printf("EP apunta a la seccion: %s\n", pISH->Name);

 //Comprobamos si la IT está también en la sección de código, donde apunta el EP, si es así no podemos cifrar el ejecutable
 PIMAGE_SECTION_HEADER pIT = RVAToSection(mapInfo.viewMappedFile, pINH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
 if (pISH == pIT)
 {
  printf("La tabla de importaciones está en la sección de código, el crypter de momento no puede cifrar estos ejecutables\n");
  return EXIT_FAILURE;
 }

 //Añadimos permisos de escritura a la sección que se va a encriptar
 pISH->Characteristics |= IMAGE_SCN_MEM_WRITE;
 printf("Añadidos permisos de escritura a la seccion: %s\n", pISH->Name);

 //Ciframos la sección donde apunta el EP
 CipherSectionXOR(mapInfo.viewMappedFile, pISH, CipherKey);
 printf("Seccion %s cifrada\n", pISH->Name);

 //Añadimos una sección para incluir en ella la rutina de descifrado
 PIMAGE_SECTION_HEADER pNewISH = AddSection(mapInfo.viewMappedFile, ".crypt", sizeof(uncryptroutine));
 if ((int)pNewISH < 0)
 {
  CloseAndUnmapFile(&mapInfo);
  PrintfAddSectionError(pNewISH);
  return EXIT_FAILURE;
 }
 printf("Seccion %s agregada\n", pNewISH->Name); 

 DWORD OEP = pINH->OptionalHeader.AddressOfEntryPoint;
 //El nuevo entry point estará al comienzo de la nueva sección
 pINH->OptionalHeader.AddressOfEntryPoint = pNewISH->VirtualAddress;
 printf("EP cambiado a: %#x\n", pNewISH->VirtualAddress);

 //Modificamos la rutina de desencriptación
 DWORD VirtualStart = pINH->OptionalHeader.ImageBase + pISH->VirtualAddress;
 DWORD VirtualEnd = VirtualStart + pISH->Misc.VirtualSize;
 OEP += pINH->OptionalHeader.ImageBase;
 //Modifica la rutina de descifrado cambiando los datos necesarios para que funcione
 ChangeUncryptRoutine(uncryptroutine, VirtualStart, VirtualEnd, OEP, CipherKey);
 printf("Rutina de descifrado modificada con los valores: %#x-%#x, %#x, %d\n", VirtualStart, VirtualEnd, OEP, CipherKey);

 if (fileSize != pNewISH->PointerToRawData)
 {
  printf("Existen datos EOF, se copiaran al final del fichero\n");
  //Los datos EOF se desplazan al final del fichero
  memcpy(&mapInfo.viewMappedFile[pNewISH->PointerToRawData + pNewISH->SizeOfRawData], &mapInfo.viewMappedFile[pNewISH->PointerToRawData], fileSize - pNewISH->PointerToRawData);
 }

 //Escribimos la rutina de desencriptación al inicio de la nueva sección
 memcpy(&mapInfo.viewMappedFile[pNewISH->PointerToRawData], uncryptroutine, sizeof(uncryptroutine));
 //Rellenamos de 00s el resto de la sección
 memset(&mapInfo.viewMappedFile[pNewISH->PointerToRawData + sizeof(uncryptroutine)], 0, pNewISH->SizeOfRawData - sizeof(uncryptroutine));
 printf("Rutina de descifrado escrita\n");
 
 //Cerramos la vista mapeada
 CloseAndUnmapFile(&mapInfo);

 printf("Archivo encriptado!\n");
 return EXIT_SUCCESS;
}

El resto de ficheros necesarios (PECore.cpp y PECore.h) junto al propio main.cpp los subo aquí.

En las siguientes entradas modificaré el crypter para que no cifre la IT si la encuentra en la sección de código, mejoraré el código en ensamblador encargado de cifrar y otras cosas según vayan surgiendo.

Saludos!