Cifrando un ejecutable a mano
Cifrando un ejecutable a mano, dos variaciones
En las dos entradas anteriores hemos visto como cifrar un ejecutable a mano y algunas posibles variaciones a la hora de hacerlo. Conociendo el proceso para realizarlo a mano hacer un programa que haga lo mismo es cuestión de paciencia.
Aquí os dejo el código fuente en C del programa que he realizado.
#include <stdio.h>
#include <windows.h>
//Rutina encargada de descifrar la sección cifrada
char 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 sectionEndPos = 10;
const int OEPPos = 17;
//Dado un numero y un alineamiento, devuelve el numero alineado superior mas cercano
DWORD align(DWORD number, DWORD alignment)
{
if(number % alignment == 0)
return number;
else
return (number / alignment) * alignment + alignment;
}
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Pasame un archivo como argumento, ej: %s program.exe", *argv);
return EXIT_FAILURE;
}
HANDLE file = CreateFile((LPCTSTR)argv[1],
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if(file == INVALID_HANDLE_VALUE){
fprintf(stderr, "No se pudo abrir el fichero: %s Error: %d\n", argv[1], GetLastError());
return EXIT_FAILURE;
}
//Abrimos una vista de solo lectura para leer el fichero y saber cuanto ocupará el nuevo fichero cifrado
HANDLE mappedFile = CreateFileMapping(file, 0, PAGE_READONLY, 0, 0, 0);
if(mappedFile == INVALID_HANDLE_VALUE)
{
fprintf(stderr, "No se puede mapear el fichero en memoria. Error: %d\n" , GetLastError());
CloseHandle(file);
return EXIT_FAILURE;
}
byte *viewMappedFile = (byte*)MapViewOfFile(mappedFile, FILE_MAP_READ, 0, 0, 0);
if(viewMappedFile == NULL)
{
fprintf(stderr, "No se puede mapear el fichero en memoria. Error: %d\n" , GetLastError());
CloseHandle(mappedFile);
CloseHandle(file);
return EXIT_FAILURE;
}
PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)viewMappedFile;
if(pIDH->e_magic != IMAGE_DOS_SIGNATURE) //MZ
{
fprintf(stderr, "No se encontro la cabecera MZ!\n");
UnmapViewOfFile(viewMappedFile);
CloseHandle(mappedFile);
CloseHandle(file);
return EXIT_FAILURE;
}
PIMAGE_NT_HEADERS pINH = (PIMAGE_NT_HEADERS)&viewMappedFile[pIDH->e_lfanew];
//Ahora con pINH->OptionalHeader.FileAlignment ya podemos saber exactamente el tamaño del nuevo ejecutable cifrado
DWORD fileAlignment = pINH->OptionalHeader.FileAlignment;
//Reabrimos las vistas con el nuevo tamaño (el viejo tamaño + el tamaño de la sección nueva)
UnmapViewOfFile(viewMappedFile);
CloseHandle(mappedFile);
DWORD size = GetFileSize(file, NULL);
mappedFile = CreateFileMapping(file,
0,
PAGE_READWRITE,
0,
size + align(sizeof(uncryptroutine) ,fileAlignment),
0);
if(mappedFile == INVALID_HANDLE_VALUE)
{
fprintf(stderr, "No se puede mapear el fichero en memoria. Error: %d\n" , GetLastError());
CloseHandle(file);
return EXIT_FAILURE;
}
viewMappedFile = (byte*)MapViewOfFile(mappedFile, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
pIDH = (PIMAGE_DOS_HEADER)viewMappedFile;
pINH = (PIMAGE_NT_HEADERS)&viewMappedFile[pIDH->e_lfanew];
if(pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS) + pINH->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) > pINH->OptionalHeader.SizeOfHeaders)
{
fprintf(stderr, "No hay espacio en la cabecera PE para agregar una seccion y es tan lioso agrandarla, quizas en futuras versiones...");
UnmapViewOfFile(viewMappedFile);
CloseHandle(mappedFile);
CloseHandle(file);
return EXIT_FAILURE;
}
PIMAGE_SECTION_HEADER pISH;
DWORD i;
//Busca la sección donde apunta el EP, que es la que cifrará
for(i = 0; i < pINH->FileHeader.NumberOfSections; i++)
{
pISH = (PIMAGE_SECTION_HEADER)&viewMappedFile[pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS) + i*sizeof(IMAGE_SECTION_HEADER)];
if(pINH->OptionalHeader.AddressOfEntryPoint >= pISH->VirtualAddress &&
pINH->OptionalHeader.AddressOfEntryPoint < pISH->VirtualAddress + pISH->Misc.VirtualSize)
{
printf("EP apunta a la seccion: %s\n", pISH->Name);
break;
}
}
//Añadimos permisos de escritura a la sección que se va a encriptar
pISH->Characteristics |= IMAGE_SCN_MEM_WRITE;
//Ciframos la sección donde apunta el EP
for(i = 0; i < pISH->Misc.VirtualSize; i++)
viewMappedFile[pISH->PointerToRawData + i] ^= 0xBB;
printf("Seccion %s cifrada\n", pISH->Name);
//Obtenemos un puntero a la última sección
PIMAGE_SECTION_HEADER pLastISH = (PIMAGE_SECTION_HEADER)&viewMappedFile[pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (pINH->FileHeader.NumberOfSections - 1) * sizeof(IMAGE_SECTION_HEADER)];
//Puntero a la nueva sección
PIMAGE_SECTION_HEADER pNewISH = pLastISH + 1;
memset(pNewISH, 0, sizeof(IMAGE_SECTION_HEADER));
sprintf((char *)pNewISH->Name, ".crypt");
pNewISH->Characteristics = IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_CODE;
pNewISH->VirtualAddress = align(pLastISH->VirtualAddress + pLastISH->Misc.VirtualSize, pINH->OptionalHeader.SectionAlignment);
pNewISH->Misc.VirtualSize = sizeof(uncryptroutine);
pNewISH->PointerToRawData = align(pLastISH->PointerToRawData + pLastISH->SizeOfRawData, pINH->OptionalHeader.FileAlignment);
pNewISH->SizeOfRawData = align(sizeof(uncryptroutine), pINH->OptionalHeader.FileAlignment);
//Corregimos los valores del PE para que tenga en cuenta la nueva sección
pINH->FileHeader.NumberOfSections++;
DWORD OEP = pINH->OptionalHeader.AddressOfEntryPoint;
//El nuevo entry point estará al comienzo de la nueva sección
pINH->OptionalHeader.AddressOfEntryPoint = pNewISH->VirtualAddress;
pINH->OptionalHeader.SizeOfImage = align(pNewISH->VirtualAddress + pNewISH->Misc.VirtualSize, pINH->OptionalHeader.SectionAlignment);
printf("Seccion %s agregada\n", pNewISH->Name);
//Modificamos la rutina de desencriptación
DWORD VirtualStart = pINH->OptionalHeader.ImageBase + pISH->VirtualAddress;
memcpy(&uncryptroutine[sectionStartPos], &VirtualStart, sizeof(DWORD));
DWORD VirtualEnd = VirtualStart + pISH->Misc.VirtualSize;
memcpy(&uncryptroutine[sectionEndPos], &VirtualEnd, sizeof(DWORD));
OEP += pINH->OptionalHeader.ImageBase;
memcpy(&uncryptroutine[OEPPos], &OEP, sizeof(DWORD));
if (size != pNewISH->PointerToRawData)
{
printf("Existen datos EOF, se copiaran al final del fichero\n");
//Los datos EOF se desplazan al final del fichero
memcpy(&viewMappedFile[pNewISH->PointerToRawData + pNewISH->SizeOfRawData], &viewMappedFile[pNewISH->PointerToRawData], size - pNewISH->PointerToRawData);
}
//Escribimos la rutina de desencriptación al inicio de la nueva sección
memcpy(&viewMappedFile[pNewISH->PointerToRawData], uncryptroutine, sizeof(uncryptroutine));
//Rellenamos de 00s el resto de la sección
memset(&viewMappedFile[pNewISH->PointerToRawData + sizeof(uncryptroutine)], 0, pNewISH->SizeOfRawData - sizeof(uncryptroutine));
printf("Rutina de descifrado escrita\n");
//Cerramos la vista mapeada
UnmapViewOfFile(viewMappedFile);
CloseHandle(mappedFile);
CloseHandle(file);
printf("Archivo encriptado!\n");
return EXIT_SUCCESS;
}
El ejecutable conserva los datos EOF que puedan existir. La rutina de descifrar a sido cambiada levemente ("JMP dirección" por "MOV EAX, dirección; JMP EAX") para que sea mas fácil de modificar desde el código.
Al ejecutarlo muestra algo como esto:
Al ejecutarlo muestra algo como esto:
Es posible volver a cifrar el ejecutable resultante. De modo que podemos llegar a tener 20 secciones “.crypt”, y una tras otra van descifrándose hasta que la última descifra el código original. Como curiosidad he probado ha cifrarlo 88 veces, obteniendo 91 secciones (inicialmente había 3) y funciona correctamente:
Este “crypter” no funciona con todos los ejecutables, de hecho da problemas con muchos de ellos, en siguientes entradas veremos los problemas que pueden surgir y como tratarlos.
Cualquier sugerencia será bienvenida. Saludos!
Enhorabuena por las entradas sobre el formato PE, me han gustado muchísimo.
ResponderEliminarEn la función (línea 20): DWORD align(DWORD number, DWORD alignment) se debería comprobar también que el aligment no sea cero ;-)
Muchas gracias!
ResponderEliminarTienes razón, si el alignment es 0 dará una excepción :( Lo corregiré.