Introduccion a Windows
Última actualización
Última actualización
Los programas a menudo necesitan acceder o modificar subsistemas o hardware de Windows, pero están restringidos para mantener la estabilidad de la máquina. Para resolver este problema, Microsoft lanzó la API Win32 , una biblioteca para interactuar entre las aplicaciones en modo de usuario y el kernel.
Windows distingue el acceso al hardware mediante dos modos distintos: modo de usuario y modo kernel . Estos modos determinan el acceso al hardware, al kernel y a la memoria que se permite a una aplicación o controlador. API o llamadas al sistema interactúan entre cada modo, enviando información al sistema para ser procesada en modo kernel.
Modo de usuario
Modo kernel
Sin acceso directo al hardware
Acceso directo al hardware
Acceso a ubicaciones de memoria "propias"
Acceso a toda la memoria física.
Para obtener más información sobre la administración de memoria .
A continuación se muestra una representación visual de cómo una aplicación de usuario puede utilizar llamadas API para modificar los componentes del kernel.
Al observar cómo interactúan los idiomas con la API de Win32 , este proceso puede deformarse aún más; la aplicación pasará por el tiempo de ejecución del lenguaje antes de pasar por la API.
La API de Win32 , más comúnmente conocida como API de Windows, tiene varios componentes dependientes que se utilizan para definir la estructura y organización de la API.
Dividamos la API de Win32 mediante un enfoque de arriba hacia abajo. Asumiremos que la API es la capa superior y los parámetros que componen una llamada específica son la capa inferior. En la siguiente tabla, describiremos la estructura de arriba hacia abajo en un nivel alto y profundizaremos en más detalles más adelante.
Capa
Explicación
API
Un término o teoría general/de alto nivel utilizado para describir cualquier llamada que se encuentre en la estructura de la API de win32 .
Archivos de encabezado o importaciones (Header files or imports)
Define las bibliotecas que se importarán en tiempo de ejecución, definidas por archivos de encabezado o importaciones de bibliotecas. Utiliza punteros para obtener la dirección de la función.
DLL principales(Core DLLs)
Un grupo de cuatro DLL que definen estructuras de llamadas. (KERNEL32, USUARIO32 y ADVAPI32). Estas DLL definen los servicios del kernel y del usuario que no están contenidos en un único subsistema.
DLL suplementarias(Supplemental DLLs)
Otras DLL definidas como parte de la API de Windows . Controla subsistemas separados del sistema operativo Windows. ~36 otras DLL definidas. (NTDLL, COM, FVEAPI, etc.)
Estructuras de llamadas(Call Structures)
Define la llamada API en sí y los parámetros de la llamada.
Llamadas API(API Calls)
La llamada API utilizada dentro de un programa, con direcciones de función obtenidas de punteros.
Parámetros de entrada/salida(In/Out Parameters)
Los valores de los parámetros definidos por las estructuras de llamada.
Ampliemos estas definiciones; En la siguiente tarea, analizaremos la importación de bibliotecas, el archivo de encabezado principal y la estructura de llamadas. En la tarea 4, profundizaremos en las llamadas y comprenderemos dónde y cómo digerir los parámetros y variantes de las llamadas.
En esta tarea, profundizaremos en la teoría de cómo funcionan ambas implementaciones y, en tareas futuras, las pondremos en práctica.
Microsoft ha lanzado el archivo de encabezado de Windows, también conocido como cargador de Windows, como una solución directa a los problemas asociados con la implementación de ASLR. Manteniendo el concepto en un nivel alto, en tiempo de ejecución, el cargador determinará qué llamadas se realizan y creará una tabla de procesadores para obtener direcciones o punteros de funciones.
Afortunadamente, no tenemos que profundizar más para continuar trabajando con llamadas API si no deseamos hacerlo.
Una vez que el windows.h
archivo está incluido en la parte superior de un programa no administrado; Se puede llamar a cualquier función de Win32.
Cubriremos este concepto a un nivel más práctico en la tarea 6.
Microsoft describe P/Invoke o invocación de plataforma como "una tecnología que le permite acceder a estructuras, devoluciones de llamadas y funciones en bibliotecas no administradas desde su código administrado".
P/invoke proporciona herramientas para manejar todo el proceso de invocar una función no administrada desde código administrado o, en otras palabras, llamar a la API Win32 . P/invoke comenzará importando la DLL deseada que contiene la función no administrada o la llamada API de Win32. A continuación se muestra un ejemplo de importación de una DLL con opciones.
En el código anterior, estamos importando la DLL user32
usando el atributo: DLLImport
.
Nota: no se incluye un punto y coma porque la función p/invoke aún no está completa. En el segundo paso, debemos definir un método administrado como externo. La extern
palabra clave informará al tiempo de ejecución de la DLL específica que se importó previamente. A continuación se muestra un ejemplo de cómo crear el método externo.
Ahora podemos invocar la función como un método administrado, ¡pero estamos llamando a la función no administrada!
En esta tarea, echaremos un vistazo introductorio a los esquemas de nombres y los parámetros de entrada/salida de las llamadas API .
La funcionalidad de llamada API se puede ampliar modificando el esquema de nomenclatura y añadiendo un carácter representativo. A continuación se muestra una tabla de los caracteres que Microsoft admite para su esquema de nombres.
Character
Explicación
A
Representa un juego de caracteres de 8 bits con codificación ANSI.
W.
Representa una codificación Unicode
Ex
Proporciona funcionalidad extendida o parámetros de entrada/salida a la llamada API .
Para cada parámetro de E/S, Microsoft también explica su uso, la entrada o salida esperada y los valores aceptados.
Incluso con una explicación, determinar estos valores a veces puede resultar complicado para determinadas llamadas. Sugerimos siempre investigar y encontrar ejemplos de uso de llamadas API antes de utilizar una llamada en su código.
Microsoft proporciona lenguajes de programación de bajo nivel, como C y C++, con un conjunto de bibliotecas preconfiguradas que podemos usar para acceder a las llamadas API necesarias .
El windows.h
archivo de encabezado, como se analizó en la tarea 4, se utiliza para definir estructuras de llamadas y obtener punteros de funciones. Para incluir el encabezado de Windows, anteponga la línea siguiente a cualquier programa C o C++.
#include <windows.h>
Pasemos directamente a crear nuestra primera llamada API . Como primer objetivo, pretendemos crear una ventana emergente con el título: "¡Hola THM!" usandoCreateWindowExA
. Para reiterar lo que se cubrió en la tarea 5, observemos los parámetros de entrada/salida de la llamada.
Tomemos estos parámetros predefinidos y les asignaremos valores. Como se mencionó en la tarea 5, cada parámetro de una llamada API tiene una explicación de su propósito y valores potenciales. A continuación se muestra un ejemplo de una llamada completa aCreateWindowsExA
.
¡Hemos definido nuestra primera llamada API en C! Ahora podemos implementarlo en una aplicación y utilizar la funcionalidad de la llamada API. A continuación se muestra una aplicación de ejemplo que utiliza la API para crear una pequeña ventana en blanco.
Si tiene éxito, deberíamos ver una ventana con el título “¡Hola THM!”.
Como se demostró a lo largo de esta tarea, los lenguajes de bajo nivel hacen que sea muy fácil definir rápidamente una llamada API . Debido a su facilidad de uso y extensibilidad, los lenguajes basados en C son los más populares tanto entre los actores de amenazas como entre los proveedores.
Como se analizó en la tarea 4, P/Invoke nos permite importar archivos DLL y asignar punteros a llamadas API .
Para comprender cómo se implementa P/Invoke, veamos un ejemplo a continuación y analicemos los componentes individuales después.
La función de clase almacena llamadas API definidas y una definición a la que hacer referencia en todos los métodos futuros.
Desde la importación de DLL , podemos crear un nuevo puntero a la llamada API que queremos usar, en particular definido por intPtr
. A diferencia de otros lenguajes de bajo nivel, debe especificar la estructura de parámetros de entrada/salida en el puntero. Como se analizó en la tarea 5, podemos encontrar los parámetros de entrada/salida para la llamada API requerida en la documentación de Windows.
Ahora podemos implementar la llamada API definida en una aplicación y usar su funcionalidad. A continuación se muestra una aplicación de ejemplo que utiliza la API para obtener el nombre de la computadora y otra información del dispositivo en el que se ejecuta.
Si tiene éxito, el programa debería devolver el nombre de la computadora del dispositivo actual.
Ahora que hemos cubierto cómo se puede lograr en .NET, veamos cómo podemos adaptar la misma sintaxis para que funcione en PowerShell .
Definir la llamada API es casi idéntica a la implementación de .NET, pero necesitaremos crear un método en lugar de una clase y agregar algunos operadores adicionales.
Las llamadas ahora están definidas, pero PowerShell requiere un paso más antes de poder inicializarlas. Debemos crear un nuevo tipo para el puntero de cada DLL Win32 dentro de la definición del método. La funciónAdd-Type
colocará un archivo temporal en el /temp
directorio y compilará las funciones necesarias usando csc.exe
. A continuación se muestra un ejemplo de la función que se utiliza.
Ahora podemos usar las llamadas API requeridas con la sintaxis siguiente.
[Win32.Kernel32]::<Imported Call>()
Varias llamadas API dentro de la biblioteca Win32 se prestan para ser aprovechadas fácilmente para actividades maliciosas.
Si bien se abusa de muchas llamadas, algunas se ven en la naturaleza más que otras. A continuación se muestra una tabla de las API de las que se abusa con más frecuencia, organizadas por frecuencia en una colección de ejemplos.
Llamada API
Explicación
LoadLibraryA
Asigna una DLL especificada al espacio de direcciones del proceso de llamada
GetUserNameA
Recupera el nombre del usuario asociado con el hilo actual.
GetComputerNameA
Recupera un nombre NetBIOS o DNS de la computadora local
GetVersionExA
Obtiene información sobre la versión del sistema operativo que se está ejecutando actualmente.
GetModuleFileNameA
Recupera la ruta completa para el archivo del módulo y proceso especificados.
GetStartupInfoA
Recupera el contenido de la estructura STARTUPINFO (estación de ventana, escritorio, identificadores estándar y apariencia de un proceso).
GetModuleHandle
Devuelve un identificador de módulo para el módulo especificado si está asignado al espacio de direcciones del proceso de llamada
GetProcAddress
Devuelve la dirección de una función DLL exportada especificada
VirtualProtect
Cambia la protección en una región de la memoria en el espacio de direcciones virtuales del proceso de llamada
# Malware Case Study
Ahora que entendemos las implementaciones subyacentes de la biblioteca Win32 y las llamadas API de las que se abusa comúnmente , analicemos dos muestras de malware y observemos cómo interactúan sus llamadas.
En esta tarea, analizaremos un registrador de teclas C# y un iniciador de shellcode.
Para comenzar a analizar el keylogger, necesitamos recopilar qué llamadas API y enlaces está implementando. Debido a que el keylogger está escrito en C#, debe usar P/Invoke para obtener punteros para cada llamada. A continuación se muestra un fragmento de las definiciones de p/invoke del código fuente de muestra de malware.
A continuación se muestra una explicación de cada llamada API y su uso respectivo.
Llamada API
Explicación
SetWindowsHookEx
Instala un gancho de memoria en una cadena de ganchos para monitorear ciertos eventos
UnhookWindowsHookEx
Elimina un gancho instalado de la cadena del gancho.
GetModuleHandle
Devuelve un identificador de módulo para el módulo especificado si está asignado al espacio de direcciones del proceso de llamada
GetCurrentProcess
Recupera un pseudo identificador para el proceso actual.
Para mantener la integridad ética de este estudio de caso, no cubriremos cómo la muestra recopila cada pulsación de tecla. Analizaremos cómo la muestra fija el gancho en el proceso actual. A continuación se muestra un fragmento de la sección de enlace del código fuente de muestra de malware.
Comprendamos el objetivo y el procedimiento del keylogger, luego asignemos su respectiva llamada API del fragmento anterior.
Para comenzar a analizar el iniciador de shellcode, una vez más necesitamos recopilar qué llamadas API está implementando. Este proceso debería ser idéntico al estudio de caso anterior. A continuación se muestra un fragmento de las definiciones de p/invoke del código fuente de muestra de malware.
A continuación se muestra una explicación de cada llamada API y su uso respectivo.
Llamada API
Explicación
VirtualAlloc
Reserva, confirma o cambia el estado de una región de páginas en el espacio de direcciones virtuales del proceso de llamada.
WaitForSingleObject
Espera hasta que el objeto especificado esté en el estado señalado o transcurra el intervalo de tiempo de espera
CreateThread
Crea un hilo para ejecutar dentro del espacio de direcciones virtuales del proceso de llamada.
Ahora analizaremos cómo se escribe y ejecuta el código shell desde la memoria.
Comprendamos el objetivo y el procedimiento de ejecución del código shell, luego asignemos su respectiva llamada API del fragmento anterior.
Cada llamada API de la biblioteca Win32 reside en la memoria y requiere un puntero a una dirección de memoria. El proceso de obtención de punteros a estas funciones está oscurecido debido a las implementaciones de ASLR ( A ddress Spac Layout R andomization) ; Cada idioma o paquete tiene un procedimiento único para superar ASLR. A lo largo de esta sala, analizaremos las dos implementaciones más populares: y el .
Las llamadas API son el segundo componente principal de la biblioteca Win32. Estas llamadas ofrecen extensibilidad y flexibilidad que se pueden utilizar para satisfacer una gran cantidad de casos de uso. La mayoría de las llamadas a la API de Win32 están bien documentadas en la y .
Para obtener más información sobre este concepto, consulte la .
Cada llamada API también tiene una estructura predefinida para definir sus parámetros de entrada/salida. Puede encontrar la mayoría de estas estructuras en la página del documento de llamada API correspondiente de la , junto con explicaciones de cada parámetro de E/S.
Echemos un vistazo a la llamada API como ejemplo. A continuación se muestra la estructura de E/S para la llamada obtenida .WriteProcessMemory
La biblioteca en la que se almacena la estructura de llamadas API ahora debe importarse usando DllImport
. Las DLL importadas actúan de manera similar a los paquetes de encabezado, pero requieren que importe una DLL específica con la llamada API que está buscando. Puede hacer referencia al o para determinar dónde se encuentra una llamada API particular en una DLL.
Varias entidades han intentado documentar y organizar todas las llamadas API disponibles con vectores maliciosos, incluidos y .
Utilizando la y el contexto del fragmento anterior, comience a analizar el registrador de teclas, utilizando las preguntas 1 a 4 como guía para trabajar en el ejemplo.
Utilizando la y el contexto del fragmento anterior, comience a analizar el iniciador de shellcode