martes, 30 de agosto de 2011

Hacer un ejecutable aun más pequeño

En el anterior post se mostraba cómo configurar Visual Studio para generar ejecutables pequeños de 1 kb. Es posible que un ejecutable sea todavía mas pequeño. Aunque ya hay que meterse en ensamblador y usar un editor hexadecimal.

Lo único que he encontrado al respecto es este artículo de Solar Eclipse.
http://www.phreedom.org/solar/code/tinype/

En él llega a un ejecutable de 97 bytes pero tras probarlo en diferentes versiones de Windows he constatado que solo funciona en versiones anteriores a Windows XP SP2, en esta versión y en posteriores no funciona, ni este ejecutable de 97 bytes, ni otros tantos del mismo artículo. También hay problemas con las versiones de Windows de 64 bits, donde el loader tiene comportamiento ligeramente diferente. Veámoslo.

En el artículo de Solar Eclipse, lo primero que hace es eliminar la librería Runtime de C para conseguir un ejecutable de 1k, como se hizo en el anterior artículo.

Lo siguiente que hace es reducir el valor de SectionAlignment mediante el parámetro /ALIGN:1 (Explicación en MSDN).

El problema es que VC++ 9.0 devuelve un error al intentar usar dicho parámetro:

Error    1    fatal error LNK1164: alineación de la sección 0x2 (2) superior al valor /ALIGN

El mínimo valor que deja usar es 4 y aun así modifica solo el valor de SectionAlignment dejándolo a 4, pero no modifica el valor de FileAlignment (como si hacía VC 6.0) para dejarlo también a 4.

image

Esto hace que el ejecutable no cumpla el estándar (Microsoft Portable Executable and Common Object File Format Specification), el cúal dice claramente:
”The section align must be greater than or equal to FileAlignment.”

“If the SectionAlignment is less than the architecture’s page size (0x1000), then FileAlignment must match SectionAlignment.”

En este caso en el que SectionAlignment es menor que 0x1000 FileAlignment también debe valer 4. Si cambiamos el valor de FileAlignment veremos como el ejecutable si funciona en todos los Windows >= Windows XP excepto en las versiones de x64 bits.

¿Por qué no funciona en las versiones de 64 bits? No se la razón exacta, solo se que para que funcione correctamente es necesario que SizeOfImage sea 0x1000 mayor de lo que debería ser (Extraño, si alguien sabe el por qué exacto que no dude en decirlo). Esto solo se da cuando SectionAlignment es menor que el page size.

Continuemos con el artículo de Solar Eclipse.

 

“Switching to assembly and removing the DOS stub”

El siguiente paso que toma para reducir el tamaño de un ejecutable es utilizar ensamblador, NASM. Es curioso que en el propio código en ensamblador del programa defina los campos de la cabecera PE. De este modo evita el uso del linker, que debería ser el que ensambla el ejecutable, crea la cabecera PE, las secciones, incluye los recursos, etc… Con esto logra una gran flexibilidad a la hora de generar un ejecutable con una cabecera personalizada.

Aunque de nuevo nos encontramos con problemas. Ya que en la primera revisión de 2009 de Nasm se introdujo el macro SECTALIGN. El código de Solar Eclipse, usa una variable con el mismo nombre, NASM nos da un error al ser una palabra reservada. Así que es necesario renombrar la variable SectAlign.

Este ejecutable, el de 356 bytes, no funciona en las versiones de 64 bits por lo que he explicado antes. Se puede solucionar cambiando esta línea:

dd round(filesize, sectalign) ; SizeOfImage

Por esta:

dd round(filesize, sectalign) + 0x1000 ; SizeOfImage

Así nos funcionará en todas las versiones de Windows >= XP tanto en 32 como en 64 bits.

tiny_356_mod.asm | tiny_356_mod.exe

 

“Collapsing the MZ header”

En este apartado superpone la cabecera PE con la cabecera MZ. Sitúa la cabecera Pe en el offset 4 haciendo que varios campos de ambas cabeceras se superpongan. El único campo importante de la cabecera MZ, aparte del magic number, es el e_lfanew, en el offset 0x3C. Este campo coincide con el campo de la cabecera PE, SectionAlignment. Da a ambos campos el valor de 4.

Para entender mejor esto en el desparecido blog de Ero Carrera se pueden ver algunos gráficos explicando los campos de este ejecutable.
http://web.archive.org/web/20081011023041/http://blog.dkbza.org/2007/03/tiny-and-crazy-pe.html

Como en el anterior apartado para que funcione en todos los Windows es necesario cambiar el valor del SizeOfImage.

tiny_296_mod.asm | tiny_296_mod.exe

 

“Removing the data directories”

Este apartado será el último que seremos capaces de realizar si queremos que el ejecutable funcione en todas las versiones de Windows.

La modificación que realiza aquí consiste en eliminar el directorio de datos, ya que no se están usando.

dd 0             ; NumberOfRvaAndSizes

Ahorrando así, 16x8=128 bytes.

Pero el ejecutable resultante no funciona en Windows XP SP2 y en siguientes versiones de Windows. El mensaje es curioso:

image
¿128 bytes demasiado extenso? :P

El problema en esta ocasión parece estar en que a pesar de que se ha puesto en el campo “NumberOfRvaAndSizes” un 0, el Loader espera que en disco esté toda la tabla de directorios, aunque esté vacía.

Así este ejecutable funciona:

image

Pero este otro no:

image

Fijaos que los últimos 4 ceros perteneces al Overlay del ejecutable. No pertenecen a ninguna sección, ni se ven reflejados en ningún campo del PE. Un tanto enigmático…

De este modo llegamos al ejecutable mas pequeño que funciona en todas las versiones de Windows superiores a XP, incluida.

; tiny.asm

BITS 32

;
; MZ header
;
; The only two fields that matter are e_magic and e_lfanew

mzhdr:
    dw "MZ"       ; e_magic
    dw 0          ; e_cblp UNUSED

;
; PE signature
;

pesig:
    dd "PE"       ; e_cp, e_crlc UNUSED       ; PE signature

;
; PE header
;

pehdr:
    dw 0x014C     ; e_cparhdr UNUSED          ; Machine (Intel 386)
    dw 0          ; e_minalloc UNUSED         ; NumberOfSections, 0
code:
    dd 0xC3582A6A ; e_maxalloc, e_ss UNUSED   ; TimeDateStamp UNUSED     ; Code (push byte 42, pop eax, ret)
    dd 0          ; e_sp, e_csum UNUSED       ; PointerToSymbolTable UNUSED
    dd 0          ; e_ip, e_cs UNUSED         ; NumberOfSymbols UNUSED
    dw opthdrsize ; e_lsarlc UNUSED           ; SizeOfOptionalHeader
    dw 0x103      ; e_ovno UNUSED             ; Characteristics

;
; PE optional header
;

filealign equ 4
sectalignn equ 4   ; must be 4 because of e_lfanew

%define round(n, r) (((n+(r-1))/r)*r)

opthdr:
    dw 0x10B      ; e_res UNUSED              ; Magic (PE32)
    db 8                                      ; MajorLinkerVersion UNUSED
    db 0                                      ; MinorLinkerVersion UNUSED
    dd round(4, filealign)                    ; SizeOfCode UNUSED
    dd 0          ; e_oemid, e_oeminfo UNUSED ; SizeOfInitializedData UNUSED
    dd 0          ; e_res2 UNUSED             ; SizeOfUninitializedData UNUSED
    dd code                                   ; AddressOfEntryPoint
    dd code                                   ; BaseOfCode UNUSED
    dd round(filesize, sectalignn)             ; BaseOfData UNUSED
    dd 0x400000                               ; ImageBase
    dd sectalignn ; e_lfanew                  ; SectionAlignment
    dd filealign                  ; FileAlignment
    dw 4                          ; MajorOperatingSystemVersion UNUSED
    dw 0                          ; MinorOperatingSystemVersion UNUSED
    dw 0                          ; MajorImageVersion UNUSED
    dw 0                          ; MinorImageVersion UNUSED
    dw 4                          ; MajorSubsystemVersion
    dw 0                          ; MinorSubsystemVersion UNUSED
    dd 0                          ; Win32VersionValue UNUSED
    dd round(filesize, sectalignn) + 0x1000; SizeOfImage
    dd round(hdrsize, filealign)  ; SizeOfHeaders
    dd 0                          ; CheckSum UNUSED
    dw 2                          ; Subsystem (Win32 GUI)
    dw 0x400                      ; DllCharacteristics UNUSED
    dd 0x100000                   ; SizeOfStackReserve UNUSED
    dd 0x1000                     ; SizeOfStackCommit
    dd 0x100000                   ; SizeOfHeapReserve
    dd 0x1000                     ; SizeOfHeapCommit UNUSED
    dd 0                          ; LoaderFlags UNUSED
    dd 0                          ; NumberOfRvaAndSizes UNUSED

;
; Data directories
;

    times 16 dd 0, 0 ; Empty Directory data, it's needed in Windows >= XP SP2
    dd 0, 0, 0, 0  ; Compatibility with x64 Windows

opthdrsize equ $ - opthdr

hdrsize equ $ - $$

filesize equ $ - $$

Una última cosa. Para que funcione en Windows de 64 bits requiere 16 bytes más a continuación de la tabla de directorios :|

tiny-Compatible.asm | tiny-Compatible.exe

268 bytes, esto es lo mínimo que puede ocupar un ejecutable que funcione en XP, Vista y W7 tanto en versiones de 32 como de 64 bits.

4 bytes de la cabera MZ + 24 bytes de la cabecera PE + 96 bytes de la cabecera PE Opcional + 128 bytes de la tabla de directorios (compatibilidad >= XP SP2) + 16 bytes (compatibilidad x64) = 268 bytes.

 

Documentación relacionada:

Después en el artículo para conseguir dejar el exe en 97 bytes superpone la tabla de secciones sobre la cabecera PE. Otra opción hubiese sido eliminar la tabla de secciones y utilizar un ejecutable sin ninguna sección. Esto está explicado en esta presentación de Alexander Liskin. PE: Specification vs. Loader.

Otro documento muy interesante en el que se lleva el formato PE es el white papper Undocumented PECOFF de ReversingLabs, presentado este año en la BlackHat USA.

 

Ha sido un artículo un poco pesado, con bastantes cosas “mágicas” que no se sabe muy bien por qué suceden. Para entender bien la implementación del loader de windows habría que realizar ingeniería inversa al mismo, ¿eso es legal?. Todas las demás conclusiones pueden estar equivocadas.

Si alguien logra un ejecutable menor de 268 bytes compatible con "todos" los Windows o hay alguna parte equivocada os animo a corregirme en los comentarios.
Saludos!

lunes, 22 de agosto de 2011

Generar ejecutables pequeños con VC++ 9.0 y Visual Studio 2008

En esta entrada voy a explicar cómo configurar Visual C++ 9.0, en el entorno Visual Studio 2008, para que genere ejecutables lo más pequeños posibles.

Me he basado en este artículo, que explica lo mismo para VC++ 6.0:
http://www.catch22.net/tuts/minexe

Para hacer las pruebas vamos a realizar un programa muy sencillo, que muestra un mensaje y se cierra.

#include <stdio.h>

int main(int argc, char* argv[])
{
	printf("Hola");
	return 0;
}

Este sencillo programa ocupa 30 Kb. Lo primero que hay que hacer cambiar la configuración del proyecto que por defecto está en modo DEBUG. Cambiándola a RELEASE conseguimos que el ejecutable baje a 7 kb. Ahora sobre esta configuración iremos cambiando cosas para disminuir aun mas el peso.

El compilador VC++ como se explica en el artículo que puse al principio, define su propio punto de entrada, donde hace distintas inicializaciones y ya después de todo eso ejecuta el main. Esquema de esta función:

int mainCRTStartup()
{
    int retval;
    init_heap();
    parse_command_line();
    init_global_vars();
    init_exception_handling();
    // finally call the user-defined main
    retval = main();
    // terminate all threads and exit
    ExitProcess(retval);
}

Esto supone código y dependencias extra. Podemos decir a VC++ que nuestro punto de entrada sea nuestra función main, de esta forma ignorará su función por defecto.

Para ello en Visual Studio, en las propiedades de nuestro proyecto en el apartado “Propiedades de configuración > Vinculador > Avanzadas” en “Punto de entrada” pondremos “main”.

image

Con esto reducimos el ejecutable hasta los 3.5 kb.

Hay que tener cuidado porque al saltarnos la función inicial de VC++ (donde se llamaba a “parse_command_line()”) ya no podemos acceder a los argumentos mediante argc y argv. Si necesitamos acceder a los mismos se puede hacer con la API GetCommandLine.

Sigamos adelgazando el ejecutable, veamos las secciones que tiene:

image

Una sección que nos podemos ahorrar es la de .reloc. Las relocalizaciones. Esta sección es mas típica de DLLs, que pueden ser cargadas en diferentes direcciones de memoria. En este ejecutable aparece porque esta activada la opción de DYNAMICBASE para que el ejecutable sea compatible con ASLR. Podemos confiar en que nuestro ejecutable conseguirá cargarse en la dirección base que tiene configurada por defecto y que no necesitará las relocalizaciones, en los ejecutables no suele dar problemas esto.

Para desactivar esta opción vamos a “Propiedades de configuración > Vinculador > Avanzadas”, “Dirección base aleatoria” y seleccionamos Deshabilitar.

image

Ahora el ejecutable pesa 3.37 kb.

Volvemos a ver las secciones y se ve como hay información de depuración en EOF. En la tabla de directorios también aparece:

image

Solo es una ruta hacia el fichero pdb de nuestro equipo el cual contiene toda la información de depuración, no nos hace ahorrar mucho, pero cuanto mas simple mantengamos el ejecutable mejor.

“Propiedades de configuración > Vinculador > Depuración”, “Generar información de depuración”, “No”.

Dejamos el ejecutable en los 3 kb.

Otra sección que se puede eliminar es la sección de recursos, .rsrc. se puede ver que solo contiene el archivo de manifiesto. En él se especifica los permisos con los que se necesita arrancar el ejecutable (UAC). Además también se incluye aquí algunas dependencias, ensamblados, como es el caso de MSVCR90.DLL, que solo se puede cargar desde el manifiesto (no entiendo muy bien el por qué, se admiten explicaciones en los comentarios y lo incorporo aquí).

El hecho de depender de MSVCR90.DLL hace además que sea necesario instalar el “Paquete redistribuible de Microsoft Visual C++ 2008” en aquellos equipos donde se vaya a ejecutar la aplicación.

¿Opciones?

  • Usar otra librería en tiempo de ejecución para C. Aquí podemos ver las alternativas:
    http://msdn.microsoft.com/en-us/library/abx4dbyh(v=VS.90).aspx
    Por defecto se usa “Multithreaded, dynamic link” /MD. La cual requiere la carga de la DLL MSVCR90.DLL.
    Se puede usar la opción “Multithreaded, static link”. La librería se “linka” estáticamente, nos libramos de DLLs externas pero el ejecutable incrementa su tamaño en 37 Kbytes.
  • Usar solamente la API de Windows, así no dependemos de librerías y no aumentamos el tamaño del ejecutable.

Como buscamos disminuir el tamaño del ejecutable elegimos la segunda opción. Así que nos toca buscar una equivalencia al printf con la API de Windows:

#include <windows.h>

int main()
{
	WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), "Hola", 4, NULL, NULL);
	return 0;
}

Como ya no se va a usar la librería en tiempo de ejecución para C, la quitamos. “Propiedades de configuración > Vinculador > Entrada” en “Omitir todas las bibliotecas predeterminadas” ponemos Si.

image

Ahora al compilar veremos un error:

image

Desaparece poniendo en “Propiedades de configuración > C/C++ > Generación de código” en “Comprobación de seguridad de buffer” No (/GS-).

image

Ya podremos quitar el manifiesto ya que no necesitamos permisos especiales para el UAC, ni la carga de ningún ensamblado. Con ello quitaremos también al sección .rsrc.

En “Propiedades de configuración > Vinculador > Archivo de manifiesto” en “Generar manifiesto” ponemos No.

image

Nos quitamos al sección .rsrc. 1 kb menos, el ejecutable pesa 2 kb.

Ahora solo tenemos 2 secciones .text y .rdata.

image

En .text tenemos el código mínimo para ejecutar nuestro programa. Sin inicializaciones ni cosas raras.

image

En .rdata están la cadena “Hola” y la Import Table.

Lo que sigue dando tamaño al ejecutable es la existencia de las 2 secciones alineadas. Para ello es posible combinar una en la otra con la siguiente directiva:

#include <windows.h>

#pragma comment(linker,"/merge:.rdata=.text")

int main()
{
	WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), "Hola", 4, NULL, NULL);
	return 0;
}

Con esto dejamos el ejecutable en 1 kb. Lo mas bajo que es posible dejarlo sin editar el ejecutable a mano. Subo aquí el proyecto de Visual Studio.

Para reducirlo mas, ~130 bytes, se puede leer este texto: Tiny PE, es una pasada, aunque el ejecutable generado no es compatible con Windows 7.

Saludos!

jueves, 18 de agosto de 2011

Sencillo programa para encontrar huecos en ejecutables

Seguramente muchos conozcáis la herramienta ToPo un programa, con 12 años a sus espaldas, para buscar huecos en los ejecutables y así poder insertar código sin necesidad de añadir nuevas secciones.

Estos huecos surgen cuando se alinean las secciones del ejecutable en disco con el valor FileAlignment que suele tomar el valor de 0x200. Por ejemplo si un programa tiene una sección que realmente ocupa 0x310 bytes, estará obligado a tener una sección en el ejecutable en disco de tamaño como mínimo 0x400 bytes y gracias a este alineamiento hay 0xF0 bytes al final de la sección que no son utilizados para nada.

El caso es que quería crearme un sencillo programa que encontrara estos huecos, para después usarlo en el crypter y si se puede no añadir una nueva sección aprovechando los huecos.

La idea es recorrer todas las secciones y comprobar una a una si el valor de VirtualSize, el tamaño real de al sección, es menor que el tamaño de al sección en disco, SizeOfRawData. En este caso existe un hueco en esa sección de tamaño SizeOfRawData – VirtualSize.

Aquí os dejo el código, la mayoría de la funcionalidad se encuentra en el fichero PECore.cpp, un fichero donde he sacado las funciones mas comunes ha realizar con ejecutables. Así es mas fácil entender el código.

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

int main(int argc, char *argv[])
{
	if(argc != 2)
	{
		printf("Se esperaba 1 parametro, el fichero donde se buscarán los huecos\n");
		printf("Ej: %s notepad.exe\n", argv[0]);
		return EXIT_FAILURE;
	}
	MapInfo mapInfo;
	if (OpenAndMapFile(&mapInfo, argv[1]) == -1)
		return EXIT_FAILURE;
	//En cada sección se comprueba si el tamaño virtual es menor que el tamaño en disco de la sección
	//VirtualSize < SizeofRawData, en ese caso hay un hueco que ha surgido de las alineaciones
	PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)mapInfo.viewMappedFile;
	PIMAGE_NT_HEADERS pINH = (PIMAGE_NT_HEADERS)&mapInfo.viewMappedFile[pIDH->e_lfanew];
	for(DWORD i = 0; i < pINH->FileHeader.NumberOfSections; i++)
	{
		PIMAGE_SECTION_HEADER pISH = (PIMAGE_SECTION_HEADER)&mapInfo.viewMappedFile[pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS) + i*sizeof(IMAGE_SECTION_HEADER)];
		if (pISH->Misc.VirtualSize < pISH->SizeOfRawData)
		{
			//Hay un hueco, imprimimos su ubicación y tamaño
			DWORD Pos = pISH->VirtualAddress + pISH->Misc.VirtualSize;
			printf("Hueco encontrado!\n");
			printf("Section: %s\n", pISH->Name);
			printf("RVA:\t%#x\n", Pos);
			printf("Offset:\t%#x\n", RVAToOffset(mapInfo.viewMappedFile, Pos));
			printf("Size:\t%#x\n\n", pISH->SizeOfRawData - pISH->Misc.VirtualSize);
		}
	}
	CloseAndUnmapFile(&mapInfo);

	return EXIT_SUCCESS;
}

Subo los ficheros PETools.h y PETools.cpp aquí, espero ir mejorándolos con lo que necesite.

Por supuesto ToPo es mucho mas completo, da la posibilidad de redireccionar el EP al hueco, añadir secciones y modificar sus permisos.

Saludos!

viernes, 12 de agosto de 2011

Cifrando un ejecutable en C, problemas


Cifrando un ejecutable en C

El código que se mostró en la anterior entrada para cifrar un ejecutable haciendo XOR sobre toda su sección de código no funciona con todos los ejecutables. Vamos a ver algunas de las posibles situaciones en las que se me ocurre que puede fallar.

En el ejecutable del netcat la IT, Import Table, estaba en la sección .rdata. Para ver en que sección está la IT se puede mirar en la cabecera PE, en el directorio de datos, la VirtualAddress del directorio de importaciones.

image

Y mirar en que sección cae esta dirección. Claro que es mucho mas fácil abrir el ejecutable con OllyDbg y ver el mapa de memoria:

image

En el caso del netcat el crypter no tocaba en ningún momento la IT. Pero hay muchos ejecutables, por ejemplo la calculadora de windows, en el que la IT está en la sección de código:

image

¿Qué sucede en estos casos?

Que nuestro crypter cifra toda la sección de código incluida la IT. Y cuando Windows carga el ejecutable, y el loader trata de recorrer la IT para rellenar la IAT, Import Address Table, se encuentra con una tabla sin sentido, con punteros a zonas de memoria que no existen y BOOM!! Explota:

image

Deberíamos o bien, omitir a la hora de cifrar la zona donde está la IT y la IAT. O bien, invalidar la IT en el directorio de datos y cargarla desde la rutina que hemos añadido después de descifrar la sección.


Otra de las cosas que puede fallar es cuando la IT u otra tabla de datos se encuentra en la cabecera PE, aprovechando el espacio libre que surge de alinear el tamaño de la cabecera, este espacio está justo después de la tabla de secciones. Volviendo a la calculadora de nuevo, podemos observar como la tabla “BoundImportDirectory” está dentro de la cabecera en la posición 260h:

image

Está tabla es usada para cargar mas rápidamente un ejecutable, ya que digamos que tiene precargada toda la IAT para una determinada versión de librerias, DLLs, ahorrandose así el tiempo de recorrer toda la IT e ir buscando la dirección de cada función para compeltar la IT. Si ya conoce al versión de DLL que se va a cargar ya sabe exactamente en que dirección se va a encontrar la función.

El caso es que esta tabla está en la posición 260h, justo despues de la tabla de secciones:

image

Y cuando nuestro crypter de la anterior entrada añade una sección sobrescribe parte de está tabla dejándola con datos erróneos.

Al añadir una sección deberíamos tener cuidado de no sobrescribir ninguna tabla, y si es necesario relocalizar la tabla en otro lugar.


Otro problema, es que nuestra rutina de descifrado usa el registro EAX, el cual deja con el valor del Original Entry Point, así cuando se ejecuta el código original no está todo como debería estar si no se hubiese cifrado el ejecutable, ya que aunque la pila contiene exactamente lo mismo y la mayoría de los registros están sin cambiar, el registro EAX, que suele inicializarse a 0, en nuestro caso tendría otro valor. Esto puede dar problemas en muy raras ocasiones en las que se presuponga que inicialmente EAX vale 0 y el programa no se encargue de inicializar el registro. Pero si he visto código en ensamblador que usa el valor inicial de EBX, ya que el loader de Windows lo inicializa con la dirección del Process Enviroment Block, PEB, y es una forma mas cómoda de acceder al PEB, sin pasar antes por el TIB (fs:30).

Conclusión, el crypter debería dejar los valores de los registros y de la pila intactos tras ejecutar su rutina de descifrado. Para ello es muy habitual hacer uso de las instrucciones PUSHAD y POPAD que guardan y restablecen respectivamente todos los registros.

Y esto es lo que de momento se me ocurre que podría fallar, seguro que van surgiendo mas fallos según vaya probándolo.

Ahora toca mejorar el código del crypter para que tenga en cuenta estas cosas.

Saludos!

jueves, 11 de agosto de 2011

Cifrando un ejecutable en C


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:
image

Subo aquí el programa compilado.

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:
image

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!

martes, 9 de agosto de 2011

Cifrando un ejecutable a mano, dos variaciones

En la anterior entrada hemos cifrado el netcat haciendo xor sobre toda la sección de código. Para ello hemos añadido una sección, en ella se ha escrito el código para descifrar la sección de código y se ha cambiado el EP a esta sección.

Se me ocurren 2 posibles variaciones al método anterior.

La primera es no añadir una nueva sección sino usar los huecos existentes en la sección “.text”. Es muy habitual que debido a la necesidad de que el tamaño de una sección en disco esté alineada con el valor FileAlignment, al final de todas las secciones sobren algunos bytes que solo están ahí para cumplir con el alineado. Podemos usar estos huecos para introducir nuestro código sin la necesidad de añadir una sección de código. Pero claro esto no siempre se puede hacer y lo que se busca a la hora de programar algo es que funcione la mayoría de las veces, así que este método queda descartado.

Aun así para ver que dicen los AV al respecto, he realizado la prueba y lo detectan 19/43, este cambio no confunde a ningún AV.

La segunda variación es la que hace Mati Aharoni en el video que puse en la anterior entrada. Y consiste en no modificar en ningún momento el EP. En cambio donde apunta el EP pone una instrucción de salto, JMP, a la nueva sección donde tenemos el código que descifra y una vez descifrado el código se encarga de ejecutar las instrucciones que había sobrescrito y retornar a la sección “.text” descifrada para continuar la ejecución normalmente.

Originalmente en el EP existen estas instrucciones: image

Tras el cambio quedara así:

image

Fijaos que el resto del código está cifrado con xor, solo se ha conservado el salto.

La sección que hemos añadido y donde salta ese JMP que veíamos quedaría así:

image

Las 2 instrucciones iniciales son las mismas que había en el exe original y que fueron reemplazas por la instrucción JMP.

Subo el exe modificado por aquí. Este es solo detectado por 13/43 antivirus. Que conste que el objetivo no es hacer indetectable el netcat, para eso existen mejores técnicas de las que ya hablé, sino ver diversas formas de cifrar un archivo y ya de paso comprobar cómo afectan a los antivirus.

Esta técnica que acabamos de ver tiene algunas dificultades para ser implantada en un programa. Y es que para insertar el JMP en el EP necesitamos 5 bytes que es lo que ocupa una instrucción JMP, y esos 5 bytes serán sobrescritos afectando a las instrucciones que estuviesen en ese lugar, pero a priori no sabemos cuantas instrucciones son ni que tamaño tienen. Es mas fácil verlo con el ejemplo anterior. Las instrucciones sobrescritas eran:

PUSH 18
PUSH 0040C098

Las cuales ocupan 7 opcodes: 6A 18 68 98 C0 40 00

Sería un grave error hacer el programa que solo copiase solo los 5 opcodes sobrescritos por el JMP. Necesitamos analizar las instrucciones afectadas por esos 5 bytes y conservarlas completamente. Programar esto ya es mas complicado, requiere conocer todas las instrucciones posibles, el tamaño de los operandos, etc… Así que de momento esta variación queda también descartada para realizar un programa automático que cifre un ejecutable, un crypter.

Saludos!

lunes, 8 de agosto de 2011

Cifrando un ejecutable a mano

Buenas!

En las entradas que se han visto hasta ahora sobre los ficheros ejecutables en Windows se explicaba el formato que tiene un exe, cómo añadir una sección, cómo añadir código ejecutable, qué es la IAT y cómo cargar funciones manualmente y alguna otra cosa relacionada. Todo esto tenía un objetivo, ir preparándonos para entender cómo funcionan los protectores de ejecutables e ir programando poco a poco uno.

Y para empezar que mejor que cifrar un ejecutable a mano de la forma mas sencilla posible para después ya desarrollar el programa que haga el proceso automáticamente. La idea es usar la función XOR para cifrar la sección de código de un ejecutable, después insertar al inicio un pequeño código en el mismo ejecutable que deshaga esta operación de cifrado y por último ejecute el código una vez descifrado.

El proceso puede verse en este video de la conferencia ShmooCon 2008 donde Mati Aharoni habla sobre cómo saltarse la detección de los antivirus. Para ello usa la herramienta netcat que es detectada por unos cuantos. Al final del proceso logra que su Antivirus, AVG, no detecte el netcat.

Voy a hacer en esencia lo mismo pero con algunos cambios.

Como Mati, voy a usar el netcat, que se puede descargar de aquí. Nada mas bajarlo, lo he subido a Virustotal y actualmente es detectado por 28/43 antivirus.

Lo siguiente que voy a hacer es añadir una sección al netcat. Para ello uso mi querido 010 Editor. Me ha quedado así:

image

No os olvidéis de cambiar el NumberOfSections, ImageSize y añadir físicamente 1000 bytes al final del fichero. Si tenéis algún problema añadiendo la sección revisad este post de Ferchu sobre cómo añadir una sección a un exe “y no morir en el intento”.

En esta sección que acabamos de crear incluiremos el código que descifre la sección de código del ejecutable y una vez finalizada esta tarea salte a la misma. Para que lo primero que se ejecute en el programa sea este código que ahora veremos es necesario modificar el EntryPoint del ejecutable de netcat para que el programa inicie la ejecución en nuestra sección.

image

En este ejemplo el código que se usará para cifrar la sección será el mismo que se use para descifrarla. Esto es posible porque vamos a usar la función XOR para cifrar los bytes de la sección de código, y esta función tiene una característica curiosa y es que: X XOR C = Y Y XOR C = X

C es la clave que por ejemplo puede ser 0xBB. X es uno de los bytes de la sección de código, una vez cifrado con la clave C obtenemos Y, que será el byte cifrado. Para descifrarlo solo tenemos que volver a aplicar la función XOR con la misma clave.

La sección de código en el ejecutable de netcat es la primera sección, llamada “.text”. Dicha sección se encuentra en este rango de memoria virtual: 401000h-40B000h.

Y con todo esta ahora ya vamos con el código ensamblador que cifrará/descifrará la sección. La idea es situarnos al comienzo de la sección e ir cifrando cada byte de la misma hasta que lleguemos al final. Este código realiza esa tarea:

MOV EAX, 401000  ;Inicio de la sección
XOR [EAX], BB ;Clave usada para la función xor BB
INC EAX
CMP EAX, 40B000 ;Fin de la sección
JNZ 00410005 ;Si no es el fin de la sección seguir en el bucle
JMP 00404AC3 ;EP original

Ahora tenemos el netcat con una nueva sección que contiene el código para cifrar/descifrar. Lo abrimos con OllyDbg y ejecutamos el bucle para que cifre toda la sección “.text”. Para ello lo mejor es poner un breakpoint en el último JMP y darle a ejecutar.

Una vez parado el Olly en el JMP, podemos ver como la sección ”.text” se encuentra cifrada:

image

A la izquierda vemos el netcat original, a la derecha el cifrado. Ahora solo nos queda guardar el ejecutable en este estado, con al sección cifrada. Para ello en OllyDbg selecionamos toda la sección y damos a “Copy to executable > Selection”:

image

Y en la nueva ventana “Save file”.

Ahora ya tendremos todo listo, el netcat con la sección de código cifrada, una nueva sección con el código que la descifrará y el EP apuntando allí. Ahora al ejecutarlo se descifrara la sección y la ejecutará como se haría normalmente.

Subo aquí el netcat tal cual me ha quedado a mi.

Esta versión cifrada la detectan 19/43 antivirus. Antes eran 28. Uno de los que no lo detectan es AVG, el mismo que utilizó Mati Aharoni en su conferencia. Hace años seguramente hubiesen sido mas los AV que no hubiesen detectado este netcat modificado, pero hoy en día muchos antivirus son capaces de mediante emulación o análisis heurísticos detectar que se usa un cifrado XOR y revertirlo para analizar el ejecutable. Otros tantos no detectaban el netcat, ya que en si es una herramienta para “hacking”, pero si detectan esta variante como un programa empaquetado “Malicious Packer”. Hay varias cosas en las que puede fijarse un antivirus para sospechar que es un programa cifrado, tiene 2 secciones con permisos de ejecución, lo cual no suele ser muy normal, la sección donde apunta el EP no es la primera, lo que tampoco es habitual y la sección “.text” no parece contener instrucciones ejecutables.

En siguientes entradas programaré esto en C y se verán los problemas que pueden surgir con este método.

Saludos!


Continuación: Dos posibles variaciones a este método