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é.