06 - Ejecutables y herramientas para Windows
El formato PE.
El formato utilizado para los ejecutables y librerías compartidas (DLLs) en windows se llama Portable Executable. En esencia, al igual que los ELF para Linux, su objetivo es contener todo lo necesario para que el loader del sistema operativo pueda subir el ejecutable a memoria y ponerlo a correr. Eso incluye, entre otras cosas, las secciones de text y datos e información sobre las librerías externas y funciones externas de las que depende el ejecutable. Al igual que en linux lo común es que los ejecutables sean dynamically linked.
Las primeras partes de un PE sirven como headers y metadata para el resto del contenido del ejectable. Una manera de entender el contenido de los headers de los PE es saber que cada posición en el archivo corresponde a algún data member de una estructura que a su vez representa alguna pieza de información sobre ejecutable, e.g. su magic number, su cantidad de secciones, la dirección del entry point. Por ejemplo, he aquí los primeros bytes de un PE:

(BTW encontré un hex editor online muy bueno: https://hexed.it/)
Según el formato PEs los primeros bytes corresponden a los campos de la siguiente estructura (llamada DOS header).
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
Eso quiere decir, que para el ejemplo, algunos de los valores son.
e_magic = 0x5A4D
e_cblp = 0x0090
e_cp = 0x0003
...
...
e_lfanew = 0x00000080
No es necesario entender cada uno de esos campos, pero si necesitaras puedes consultar fuentes como https://0xrick.github.io/win-internals/pe3/.
El último campo de esa estructura (e_lfanew) indica dónde comienza la próxima estructura importante. En nuestro ejemplo, su valor de 0x00000080 indica que el primer byte de la próxima estructura (IMAGE_NT_HEADERS) está en la posición 0x80 del archivo.
struct IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
}
Así que el Signature de nuestro file es 0x00004550, etc. Nota que algunas de las estructuras (como IMAGE_NT_HEADERS) contienen data members que a su vez son estructuras! Por ejemplo, el data member FileHeader es de tipo IMAGE_FILE_HEADER que es una estructura así:
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // 0x014c
WORD NumberOfSections; // 0x0003
DWORD TimeDateStamp; // 0x4d81fa8d
DWORD PointerToSymbolTable; // 0x00000000
DWORD NumberOfSymbols; // 0x00000000
WORD SizeOfOptionalHeader; // 0x000e
WORD Characteristics; // 0x0102
}
(Le puse como comentario los valores para el ejemplo)
El próximo data member de la struct IMAGE_NT_HEADERS también es una estructura llamada IMAGE_OPTIONAL_HEADER:
struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
...
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
}
Y al final de ese data member hay un arreglo llamado DataDirectory que contiene los headers y punteros a las secciones del ejecutable como text y data. Si te sientes con ganas de estudiar los detalles de las diversas partes de un PE puedes hacerlo en la ventana Program Trees de ghydra.
¿Has programado alguna vez usando el API de Windows?
Si eres como yo, un hater de windows declarado es posible que (1) nunca hayas programado una aplicación de consola o GUI en Windows, (2) no lo quieras admitir. Sin embargo, si te vas a convertir en un reverse engineer capaz de entender ejecutables PE, es imprescindible que conozcas algunas funciones básicas o al menos que le pierdas el miedo a buscar referencia sobre ellas en el web.
Windows provee servicios a través de funciones que expone en sus dynamic linked libraries (DLLs). Cuando una aplicación del usuario necesita usar algun servicio del sistema operativo, lo hace invocando una de esas funciones del API de Windows, la cual a su vez invoca algun system service routine (que son las que corren en el kernel space, con provilegios de acceso a al hardware)

Imagen del libro Learning Malware Analysis.
Veamos un PE en Ghidra y/o en IDA Free! Comenzaremos con un PE de un programa para la consola (terminal).
Descarga este file. Descomprime usando passwd: 4702c32.
C para Windows
Según trato de resolver los ejercicios de reverse engineering me doy cuenta que la mejor forma de prepararse (aparte de la práctica) es familiarizarse con la creación de las aplicaciones. Por ejemplo, me resulta difícil tratar de hacer reverse engineering a una aplicación de Win32 si no recuerdo cómo se estructuran esas aplicaciones.
Las aplicaciones que están creadas para el comand line, lucen iguales que las que hemos estado trabajando para Linux. Sin embargo, dependiendo del compilador que uses, la secuencia de funciones que observas antes de llegar a main puede variar.
Las que son para GUI, son otro cantar...
¿Te hace falta el main?
Ghidra no es tan eficiente como otras herramientas identificando la función main cuando su símbolo ha sido borrado. Si no lo encuentras, algunas cosas que puedes tratar:
-
a partir del entry point perseguir el programa hasta que veas un CALL precedido de 3 PUSH (en el caso de
main) o de 4 PUSH (en el caso deWinMain). -
si conoces un poco del comportamiento del programa puedes idenitificar en la pantalla de Symbol Tree la función externa que sabes que se está invocando y luego averiguar desde dónde se invoca (right click sobre la función, Show References To)
-
Si te sospechas que cierto string es usado por alguna función que te interesa, puedes usar Window -> Defined Strings y luego, sobre el address de ese string: right click References -> Show References to Address
Recomendación, una vez deduzcas a main o WinMain (o cualquier otra función interesante) cambia su nombre usando right click -> Function -> Rename Function!
Programación dirigida por eventos (event-drive programming)
-
paradigma de programación en el que el flujo del programa es guiado por los eventos que suceden sobre su interfaz gráfico, señales de sensores o mensajes de otros programas o hilos. [Adaptado de Wikipedia]
-
las aplicaciones dirigidas por eventos típicamente consisten de un loop principal (event loop) que escucha eventos e invoca una función de callback. Ejemplo: cuando ocurre el evento de click en el menu "Help-About" se invoca una función que muestra una caja con el mensaje sobre la versión del programa.

Figura tomada de https://courses.cs.washington.edu/courses/cse331/13sp/lectures/lect24-events.pdf
Los siguientes ejemplos de un programa en Win32 vienen de este tutorial
Al programar para Win32, una de las primeras cosas que debe hacer un programa es establecer la función que procesará los eventos de la aplicación.
El siguiente es un trozo de la función WinMain:
WNDCLASSEX wcx; // WINDOW class information
wcx.style = CS_HREDRAW|CS_VREDRAW |CS_DBLCLKS ; // Class styles
wcx.lpfnWndProc = (WNDPROC)MainWndProc; // Pointer to the callback procedure
Cada control (ventana, botón, etc.) es creado usando una función que establece algunas de sus propiedades (posición, nombre, padre).
// Create the window
hwndMain = CreateWindowEx(0, //Extended window style
"Lesson2", // Window class name
"Lesson 2 - A simple win32 application", // Window title
WS_OVERLAPPEDWINDOW, // Window style
CW_USEDEFAULT,CW_USEDEFAULT, // (x,y) pos of the window
CW_USEDEFAULT,CW_USEDEFAULT, // Width and height of the window
HWND_DESKTOP, // HWND of the parent window (can be null also)
NULL, // Handle to menu
hInstance, // Handle to application instance
NULL); // Pointer to window creation data
La función que hemos asignado para que responda a los mensajes se llama MainWndProc. Típicamente consiste de un switch con case's para cada uno de los mensajes a los que deseamos responder.
LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) {
switch (msg) {
case WM_DESTROY: // User closed the window
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN:
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
wsprintf(str, "Co-ordinates are\nX=%i and Y=%i",pt.x,pt.y);
MessageBox(hwnd, str, "Left Button Clicked", MB_OK);
break;
default: // Call the default window handler
return DefWindowProc(hwnd,msg,wParam,lParam);
}
return 0;
}
Algunas librerarias compartidas en programas Windows
-
KERNEL32.DLL: provee acceso a funciones de manejo de memoria, operaciones I/O, creación de procesos e hilos. Muchas de sus funciones a su vez incocan otras funciones contenidas en la librería NTDLL.DLL.GetCurrentProcess,CreateFileA,WriteFile,OpenFile,OpenProcess, etc. -
USER32.DLL: funciones para la creación y manejo de elementos del GUI de windows (ventanas, menus). Aquí viven las funciones para recibir mensajes de los diferentes controles o acciones, para crear message boxes, etc. Muchas de estas funciones invocan funciones delGDI32para realizar el rendering de los elementos gráficos.GetDlgItemTextA,MessageBoxA, etc. -
GDI32.DLL: funciones para realizar gráficos low-level, output de texto, manejo de fuentes (fonts). -
COMCTL32.DLL: funciones para controles comúnes de Windows, "File Open", "Save", progress bars, y list views.
Los strings en Windows:
-
Pueden que estén en unicode: 16bit por caracter.
-
Afortunadamente los caracteres ASCII usan un código unicode 0x00(codigo ASCII)
- 'A' en ASCII: 0x41
- 'A' en unicode: 0x0041
-
Para usar el comand
stringsde linux en búsqueda por strings unicode, puedes usar:strings --encoding=l Win32Project1.exe
Immunity debugger
Algunos hot keys del Immunity Debugger
| shortcut | acción | equivalente en gdb |
|---|---|---|
| F3 | abrir un ejectuable | gdb ejecutable |
| F2 | toggle a breakpoint | b |
| F8 | step over | ni |
| F9 | run or continue | r, c |
| CTRL-F2 | restart | r |
| CTRL-F9 | execute until return | ?? |
Algunos "wokflow" que uso con frecuencia:
- En la ventanta de dissasembly:
CTRL-Nabre la ventana de Names que permite buscar por nombres (de funciones). Una vez has encontrado la función, si das ENTER abres ventana que muestras las referencias a esa función - En subventana de Dump (izquierda inferior) si das CTRL-G puedes especificar la dirección que deseas observar.
- El stack se puede visualizar relativo a EBP, ESP.