Reversing Executables

Compilación y estructura PE.

Lenguajes Compilados

Los lenguajes compilados se convierten directamente en machine code que el procesador puede ejecutar.

  • Tienden a ser más rápidos y eficientes que los lenguajes interpretados
  • Ejemplos: C, C++, Pascal, Fortran

Proceso de Compilación ( Ejemplo: Hello.c)

  1. gcc -E hello.c -o hello.i — Preprocesar solo; no compila, ensambla ni enlaza
  • Produce: hello.i ( código preprocessado)
  1. gcc -S hello.i -o hello.s — Compilar solo; no ensambla ni enlaza
  • Produce: hello.s ( código assembly)
  1. gcc -c hello.s -o hello.o — Compilar y ensamblar, pero no enlaza
  • Produce: hello.o ( código objeto)
  1. gcc hello.o -o hello — Enlazar
  • Produce: hello ( ejecutable)

Determinación del Tipo de Archivo

El comando file se usa para determinar el tipo de archivo:

file -i nameoffile

Magic Numbers

Un magic number es una constante numérica o string que indica el tipo de archivo.

Strings

El comando strings imprime por defecto las secuencias de caracteres imprimibles que tienen al menos 4 caracteres de largo dentro de un archivo:

strings file

The Portable Executable ( PE)

El formato de archivo PE se usa en ejecutables de Windows, código objeto y DLLs. El archivo PE es una estructura de datos que contiene la información necesaria para que el loader del SO Windows maneje el código ejecutable envuelto. Casi todo archivo con código ejecutable que carga Windows es un PE.

Valor del PE Header

El PE header es de gran valor porque contiene:

  • Imports
  • Linked libraries
  • Imported functions
  • Exported functions IDA puede identificar las diferentes secciones.

PE Header Structure

La estructura de un archivo PE ( Windows):

  1. MZ Header/DOS Header — Define el archivo como un ejecutable binario
  2. DOS Stub — Programa que no puede ejecutarse en modo DOS ( existe por compatibilidad)
  3. PE File Header ( Signature) — Define el ejecutable como PE
  4. Image Optional Header — Almacena información importante sobre el ejecutable: subsystem, entry point
  5. Sections Table — Instrucciones sobre cómo cargar el ejecutable en memoria
  6. Sections — Secciones ejecutables de código y datos usadas por el ejecutable

PE Sections

  • .text:** Contiene el código ejecutable
  • .rdata:** Contiene datos de solo lectura globalmente accesibles
  • .data:** Almacena datos globales accesibles en todo el programa
  • .idata:** A veces presente, almacena información de funciones importadas; si no está, se almacena en .rdata
  • .edata:** A veces presente, almacena información de funciones exportadas; si no está, se almacena en .rdata
  • .pdata:** Solo en ejecutables de 64-bit, almacena información de manejo de excepciones
  • .rsrc:** Almacena recursos necesarios por el ejecutable
  • .reloc:** Contiene información para relocalización de archivos de biblioteca

PEview

Herramienta para inspeccionar estructuras PE: http://wjradburn.com/software/

Linking y Funciones

Imports

Los imports son funciones usadas por un programa que en realidad se almacenan en un programa diferente. Las librerías de código pueden conectarse al ejecutable principal mediante enlazamiento ( linking). Los programadores enlazan imports a sus programas para no tener que re-implementar cierta funcionalidad. Las librerías de código pueden enlazarse:

  • Estáticamente**
  • At runtime**
  • Dinámicamente**

Static Linking

Método menos comúnmente usado de enlazamiento de librerías, aunque es común en programas Linux.

  • Todo el código de la librería se copia en el ejecutable
  • Aumenta el tamaño del binario

Runtime Linking

Conectar a librerías solo cuando esa función se necesita, no al inicio del programa como con programas dinámicamente enlazados.

  • Popular en malware
  • No puedes saber estáticamente qué funciones se están enlazando
  • Algunas funciones de Windows permiten a programadores importar funciones enlazadas no listadas en el encabezado del archivo: LoadLibrary y GetProcAddress

Dynamic Linking

Es el método más común y más interesante para nosotros.

  • Cuando las librerías se enlazan dinámicamente, el SO anfitrión busca las librerías necesarias cuando se carga el programa
  • El encabezado PE almacena información sobre cada librería que se cargará y cada función que usará el programa
  • Un programa puede importar, por ejemplo, la función URLDownloadToFile

Imported y Exported Functions

Imported Functions

El encabezado PE contiene información sobre funciones específicas usadas por un ejecutable.

Exported Functions

Funciones para interactuar con otros programas y código.

  • Una DLL usualmente implementa una o más funciones y las exporta para uso por un ejecutable
  • El archivo PE contiene información sobre qué funciones un archivo exporta
  • Las funciones exportadas son raras en archivos EXE, más comunes en DLLs

Rabin2

Binary program info extractor, parte del framework radare2. Proporciona más opciones e información que strings:

rabin2 -l binary # List direct dependencies
rabin2 -g binary # Show all possible information
rabin2 -z binary # Show strings inside .data section

Más info en: https://r2wiki.readthedocs.io/en/latest/tools/rabin2/

IDA Names Window

Lista todas las localizaciones conocidas con nombre en el programa. Basado en imports, exports y análisis:

  • F:** Función
  • L:** Función de librería
  • C:** Código/instrucción
  • A:** String
  • D:** Datos definidos
  • I:** Función importada ( dinámicamente enlazada)

Tipos de Análisis

Hay principalmente dos tipos de análisis binario:

  • Estático:** El código no se está ejecutando ( análisis que hemos hecho hasta ahora)
  • Dinámico:** El código se está ejecutando

Reconocimiento de Estructuras Básicas

Allocations

Las asignaciones de memoria se pueden identificar mediante patrones específicos de instrucciones.

Comparisons

Las comparaciones se usan frecuentemente en condicionales y se pueden identificar por instrucciones cmp/test.

Loops

Los loops pueden ser identificados por patrones de jump condicionales que apuntan atrás en el código.

Arrays

Los arrays se identifican por acceso a memoria mediante índices y desplazamientos.

Functions

Las funciones tienen patrones consistentes de prologue y epilogue.

ELF Binaries

Executable and Linkable Format ( ELF) es el estándar común de ejecutables en el mundo Unix. Tipos:

  • Ejecutable
  • Shared library
  • Object file Se usa en: Linux, BSD, PS4, Wii, etc.

Estructura de un archivo ELF

Cada archivo ELF consiste en un encabezado ELF seguido de datos del archivo:

  • Program header table:** descripción de cero o más segmentos de memoria
  • Section header table:** descripción de cero o más secciones
  • Datos:** referenciados por entradas en las tablas anteriores Los segmentos contienen información necesaria para ejecución en tiempo de ejecución, mientras que las secciones contienen datos importantes para enlazamiento y relocalización.

Secciones de un Archivo ELF ( Linux)

  • .data:** Contiene datos inicializados del programa
  • .debug:** Contiene información para asistir en debugging
  • .ctors:** Contiene punteros a constructores C++
  • .dtors:** Contiene punteros a destructores C++
  • .dynamic:** Contiene información relevante a enlazamiento dinámico
  • .dynsym:** Contiene la tabla de símbolos para enlazamiento dinámico
  • .fini:** Contiene código de finalización del programa
  • .init:** Contiene código de inicialización del programa
  • .shstrtab:** Contiene una tabla de strings con los nombres de las secciones
  • .strtab:** Contiene una tabla de strings usada normalmente para nombrar elementos de la tabla de símbolos
  • .symtab:** Contiene una tabla de símbolos
  • .text:** Contiene la parte ejecutable de un programa

Consideraciones Importantes

  • Los archivos ejecutables pueden o no contener información de debugging
  • Si contienen información de debugging ( nombres de función, nombres de variables, etc.), son más fáciles de entender
  • Los ejecutables pueden o no estar optimizados; si están optimizados, pueden ser más complejos de leer
  • Los ejecutables pueden ser dinámicamente enlazados ( típico) o estáticamente enlazados

Calling Conventions

Calling conventions describen la interfaz del código llamado:

  • El orden en que se asignan parámetros atómicos ( escalares) o partes individuales de un parámetro complejo
  • Cómo se pasan los parámetros ( pusheados en el stack, puestos en registros, o una mezcla de ambos)
  • Qué registros la función llamada debe preservar para la llamadora ( callee-saved registers o non-volatile registers)
  • Cómo se divide la tarea de preparar el stack y restaurarlo entre la función llamadora y la llamada

Tipos de Calling Conventions

  • stdcall:** Usado para llamar funciones de Win32 API
  • cdecl:** Convención de C estándar
  • fastcall:** Usa registros para parámetros por performance

Análisis con Ghidra

Crear un Proyecto

Crear un nuevo proyecto en Ghidra.

Añadir Archivos

  • Drag and drop o File : Import file

Decompiler

Ghidra es capaz de descompilar y mostrar código C del ejecutable analizado.

  • Algunos códigos siempre se añaden por el compilador
  • Variables importantes:
  • Argv: Variable que contiene los argumentos pasados a un programa
  • Argc: Contiene el número de argumentos pasados a un programa
  • envp: Array de punteros a variables de entorno Se pueden renombrar variables con click derecho : edit function.

Stack Frame Setup ( cdecl)

Principio de la creación de un stack frame:

PUSH EBP ; Pushea valor EBP al stack
MOV EBP, ESP ; EBP es ahora el tope actual del stack
PUSH ECX ; Mueve el stack pointer 4 bytes ( o SUB ESP)

Recuerda:

  • ESP es el stack pointer actual
  • EBP es el base pointer del stack frame actual

Stack Cleanup ( cdecl)

La llamadora es responsable de limpiar el stack. Después de la función hay un ADD a ESP:

ADD to ESP 0x8 ; Recupera espacio de stack para cada push ( 4 bytes cada uno)

Calling Convention stdcall

La limpieza del stack cambia según la convención de llamada usada. El valor de retorno se almacena usualmente en EAX:

MOV ESP, EBP ; Restore stack pointer
POP EBP ; Remove EBP value from the Stack
RET 0x10 ; Retorn y limpiar stack ( 0x10 = 4 argumentos × 4 bytes)

Calling Convention fastcall

  • Fastcall usa registros para parámetros ( performance benefit)
  • La CPU tiene acceso directo a esos registros
  • La callee limpia el stack
  • Las variables locales tienen direcciones más bajas: [EBP - 0x4]
SUB ESP, 0x8 ; Se necesitan 8 bytes de espacio de stack local
RET 0x8 ; 4 bytes para cada push

Optimización

Los compiladores generalmente optimizan binarios:

  • En algunos puedes seleccionar el nivel de optimización
  • La optimización se enfoca en reducción de tamaño binario, uso de memoria, etc. Impacto en reversing:
  • Eliminación de código
  • Eliminación de asignaciones innecesarias
  • Eliminación de redundancias Ver más en: https://en.wikipedia.org/wiki/Optimizingcompiler