Arquitectura de Windows
Última actualización
Última actualización
Un procesador dentro de una máquina que ejecuta el sistema operativo Windows puede operar en dos modos diferentes: Modo de usuario (User Mode) y Modo de kernel (Kernel Mode). Las aplicaciones se ejecutan en modo de usuario y los componentes del sistema operativo se ejecutan en modo de kernel. Cuando una aplicación desea realizar una tarea, como crear un archivo, no puede hacerlo por sí misma. La única entidad que puede completar la tarea es el kernel, por lo que las aplicaciones deben seguir un flujo específico de llamadas a funciones. El siguiente diagrama muestra de manera general este flujo:
User Processes: Un programa o aplicación ejecutado por el usuario, como Notepad, Google Chrome o Microsoft Word.
Subsystem DLLs: DLLs que contienen funciones API llamadas por los procesos de usuario. Un ejemplo de esto sería kernel32.dll que exporta la función API de Windows (WinAPI) CreateFile, otras DLL de subsistema comunes son ntdll.dll, advapi32.dll y user32.dll.
Ntdll.dll: Una DLL de sistema que se encuentra en todo el sistema y es la capa más baja disponible en modo de usuario. Esta es una DLL especial que crea la transición de modo de usuario a modo de kernel. A menudo se conoce como la API nativa o NTAPI.
Núcleo ejecutivo (Executive Kernel): Esto es lo que se conoce como el núcleo de Windows y llama a otros controladores (drivers) y módulos disponibles en el modo de kernel para completar tareas. El núcleo de Windows se almacena parcialmente en un archivo llamado ntoskrnl.exe en C:\Windows\System32.
La siguiente imagen muestra un ejemplo de una aplicación que crea un archivo. Comienza con la aplicación de usuario llamando a la función WinAPI CreateFile
, que está disponible en kernel32.dll. Kernel32.dll es una DLL crítica que expone a las aplicaciones la interfaz de programación de aplicaciones de Windows (WinAPI) y, por lo tanto, se carga en la mayoría de las aplicaciones. A continuación, CreateFile
llama a su función equivalente de NTAPI, NtCreateFile
, que se proporciona a través de la dll ntdll.dll. Luego, ntdll.dll ejecuta una instrucción de ensamblador sysenter
(x86) o syscall
(x64), que transfiere la ejecución al modo de kernel. Se utiliza la función de kernel NtCreateFile
, que llama a controladores y módulos del kernel para realizar la tarea solicitada.
Es importante destacar que las aplicaciones pueden invocar las syscalls (es decir, las funciones de NTDLL) directamente sin tener que pasar por la API de Windows. La API de Windows simplemente actúa como un envoltorio para la API nativa. Dicho esto, la API nativa es más difícil de usar porque no está oficialmente documentada por Microsoft. Además, Microsoft desaconseja el uso de las funciones de la API nativa porque pueden cambiar en cualquier momento sin previo aviso.
Comprender cómo Windows maneja la memoria es crucial para desarrollar malware avanzado.
En los sistemas operativos modernos, la memoria no se asigna directamente a la memoria física (es decir, la RAM). En su lugar, los procesos utilizan direcciones de memoria virtual que se asignan a direcciones de memoria física. Hay varias razones para esto, pero en última instancia, el objetivo es ahorrar la mayor cantidad posible de memoria física. La memoria virtual puede asignarse a la memoria física, pero también puede almacenarse en disco. Con la dirección de memoria virtual, es posible que varios procesos compartan la misma dirección física mientras tienen una dirección de memoria virtual única. La memoria virtual se basa en el concepto de paginación de memoria, que divide la memoria en fragmentos de 4 kb llamados "páginas".
Las páginas que residen dentro del espacio de direcciones virtuales de un proceso pueden estar en uno de los 3 estados siguientes:
Libre: la página no está comprometida ni reservada. La página no es accesible para el proceso. Está disponible para ser reservada, comprometida o reservada y comprometida simultáneamente. Intentar leer o escribir en una página libre puede provocar una excepción de violación de acceso.
Reservada: la página se ha reservado para uso futuro. El rango de direcciones no puede ser utilizado por otras funciones de asignación. La página no es accesible y no tiene almacenamiento físico asociado. Está disponible para ser comprometida.
Committed: se han asignado cargas de memoria del tamaño total de la RAM y los archivos de paginación en disco. La página es accesible y el acceso está controlado por una de las constantes de protección de memoria. El sistema inicializa y carga cada página comprometida en la memoria física solo durante el primer intento de leer o escribir en esa página. Cuando el proceso termina, el sistema libera el almacenamiento de las páginas comprometidas.
Una vez que las páginas están comprometidas, es necesario establecer su opción de protección. La lista de constantes de protección de memoria se puede encontrar en la documentación oficial, pero a continuación se muestran algunos ejemplos.
PAGE_NOACCESS
: Deshabilita todo acceso a la región de páginas comprometidas. Intentar leer, escribir o ejecutar en la región comprometida resultará en una violación de acceso.
PAGE_EXECUTE_READWRITE
: Permite lectura, escritura y ejecución. Se desaconseja en gran medida su uso y generalmente es un indicador de compromiso (IoC), ya que es poco común que la memoria sea tanto escribible como ejecutable al mismo tiempo.
PAGE_READONLY
: Permite acceso de solo lectura a la región de páginas comprometidas. Intentar escribir en la región comprometida dará lugar a una violación de acceso.
Los sistemas operativos modernos generalmente tienen protecciones de memoria incorporadas para frustrar exploits y ataques. También es importante tenerlas en cuenta, ya que es probable que se encuentren al construir o depurar el malware.
Prevención de la ejecución de datos (Data Execution Prevention DEP): DEP es una función de protección de memoria a nivel del sistema que se encuentra integrada en el sistema operativo a partir de Windows XP y Windows Server 2003. Si la opción de protección de página se establece en PAGE_READONLY, DEP evitará que se ejecute código en esa región de memoria.
Aleatorización de la disposición del espacio de direcciones (Address space layout randomization ASLR): ASLR es una técnica de protección de memoria utilizada para prevenir la explotación de vulnerabilidades de corrupción de memoria. ASLR organiza aleatoriamente las posiciones del espacio de direcciones de áreas clave de datos de un proceso, incluida la base del ejecutable y las posiciones de la pila, el montón y las bibliotecas.
Cuando se trabaja con procesos de Windows, es importante tener en cuenta si el proceso es x86 o x64. Los procesos x86 tienen un espacio de memoria más pequeño de 4GB (0xFFFFFFFF) mientras que x64 tiene un espacio de memoria mucho mayor de 128TB (0xFFFFFFFFFFFFFFFF).
Este ejemplo va a través de pequeños fragmentos de código para entender mejor cómo se puede interactuar con la memoria de Windows a través de funciones C y APIs de Windows. El primer paso para interactuar con la memoria es asignar memoria. El siguiente fragmento de código demuestra varias formas de asignar memoria, que es esencialmente reservar una memoria dentro del proceso en ejecución.
Las funciones de asignación de memoria devuelven la dirección base, que es simplemente un puntero al principio del bloque de memoria que se asignó. Usando los fragmentos anteriores, pAddress
será la dirección base del bloque de memoria que fue asignado. Usando este puntero se pueden realizar varias acciones como leer, escribir y ejecutar. El tipo de acciones que se pueden realizar dependerá de la protección asignada a la región de memoria asignada.
La siguiente imagen muestra el aspecto de pAddress
bajo el depurador.
Cuando se asigna memoria, ésta puede estar vacía o contener datos aleatorios. Algunas funciones de asignación de memoria ofrecen la opción de poner a cero la región de memoria durante el proceso de asignación.
El siguiente paso después de la asignación de memoria es generalmente escribir en ese buffer. Se pueden utilizar varias opciones para escribir en la memoria, pero para este ejemplo se utiliza memcpy
.
La función en la biblioteca de Windows.h HeapAlloc
utiliza la bandera HEAP_ZERO_MEMORY que hace que la memoria asignada se inicialice a cero. La cadena es entonces copiada a la memoria asignada usando memcpy. El último parámetro en memcpy
es el número de bytes a copiar. A continuación, se vuelve a comprobar el búfer para verificar que los datos se han escrito correctamente.