Departament d’Enginyeria Informàtica i M atemàtiques Firewall: controlando el acceso a la red. TITULACIÓN: Ingeniería Técnica Informática de Sistemas AUTORS: Jairo de la Fuente Vilaltella. DIRECTORS: Carlos Molina Clemente FECHA : Abril / 2011 [Firewall: controlando el acceso a la red.] Índice 0. Prefacio……………………………………………………………..……………p. 6 1. Introducción……………………………………………………………………...p. 9 1.1 Seguridad informática…………………………………………………………p. 10 1.2 Amenazas actuales…………………………………………….………………p. 11 1.3 Porque proteger un ordenador personal...............................................................p. 12 1.4 Firewalls……………………………………………………....………………..p. 13 2. Objetivos……………………………………………..…………………………...p.15 2.1 Control de conexiones………………………………...………………………..p. 16 2.1.1 Crear un driver…………………………………………...………………....p. 16 2.1.2 Filtro………………………………………………………………...............p.16 2.2 Control por parte del usuario………………………………………...…………p. 17 2.2.1 Comunicación con el driver……………………………………...…………p. 17 2.2.2 Sincronismo…………………………………………………...……………p. 17 2 [Firewall: controlando el acceso a la red.] 3. Especificaciones…………………………………………………………………..p. 18 3.1 Componentes de la solución……………………………………………………p. 19 3.1.1 Driver………………………………………………………………………..p. 19 3.1.1.1 Acciones del driver……………….……………………………………..p. 19 3.1.1.1.1 Comunicación…………………………………………………..……p. 19 3.1.1.1.2Lectura………………………………………………………………p. 19 3.1.1.1.3 Escritura………………………...…………………………………..p. 20 3.1.1.2 Filtro………………………….………………………………………..p. 20 3.1.2 Programa de control……………………………...………………………….p. 20 3.1.2.1 Sincronismo…………………………………..………………………….p. 20 3.1.2.1.1 Creación de eventos…………………………………………………..p. 20 3.1.2.2Comunicación………………………………………….…………………p. 21 3.1.2.2.1 Informar al usuario…………………….……………………………..p. 21 3.1.2.2.2 Dar órdenes al driver………………………..………………………..p. 21 3.1.3 Lanzador………………………………………...…………………………..p. 21 3.1.3.1 Cargar el programa de control…………………..……………………….p. 21 3.1.3.2 Cargar el driver…………………………………………………………..p. 21 4. Diseño y desarrollo………………………………………………………………..p. 22 4.1 El Kernel de Windows…………………………………………………………p. 23 4.1.1 Modo kernel y modo Usuario…………………….…………………………p. 23 4.1.1.1 Drivers…………………………………………………………………..p. 25 4.1.1.1.1 API para drivers…………………………..…………………………p. 26 4.1.1.2 Memoria protegida y Hooks…………………………………………….p. 26 4.1.1.2.1 Modificar memoria protegida……………………….………………p. 27 3 [Firewall: controlando el acceso a la red.] 4.1.1.2.2 Suplantación de funciones del sistema (hook)……………………………p. 28 4.1.1.2.2.1 Creación de un filtro………………………………….…………..p. 29 4.1.1.2.2.1.1 Substitución del Major original……….………………………p. 29 4.1.1.2.2.1.2 Contexto de la llamada………………………………………..p. 31 4.1.1.2.2.1.3 Obtener información de los parámetros de la llamada………..p. 32 4.1.1.2.2.1.4 Averiguar quien llama a la función………………………...…p. 33 4.1.1.2.2.1.4.1 Llamadas a API's no documentadas ni definidas…….…….p. 33 4.1.1.2.2.2 Listas encadenadas en Drivers…………………………………....p. 35 4.1.1.2.2.2.1 Reserva de memoria…………………………………………..p. 35 4.1.1.2.2.2.2 Mutexs…………………………………………………….…..p. 36 4.1.1.2.2.3 Cola de trabajo……………………………………………………p. 37 4.1.1.2.2.4 Cola de reglas definidas…………………………………….…….p. 37 4.1.1.2.2.5 Tabla hashing……………………………………………..……p. 38 4.1.1.3 Comunicación entre Modo kernel y modo usuario………………...…..p. 39 4.1.1.3.1 Creación de dispositivos………………………………………….p. 39 4.1.1.3.1.1 Estructura de un dispositivo……………………………….….p. 40 4.1.1.3.1.1.1 Lectura…………………………………………………….....p. 41 4.1.1.3.1.1.2 Escritura…………………………………………………...p. 42 4.1.1.3.2 Sincronismo…………………………………………………………p. 42 4.1.1.3.2.1 Creación de eventos……………………………………………..p. 43 4.2 Esquema del conjunto……………………………………………………..…..p. 44 5. Evaluación y juego de pruebas…………………………………………………....p. 48 4 [Firewall: controlando el acceso a la red.] 6. Recursos usados …………………………………………………………………..p. 51 6.1 Bibliografía……………………………..………………………………………p. 52 6.2 Páginas web…………………………………………………………………….p. 53 6.3 Software…………………………………………………...……………………p. 53 6.3.1 Visual studio 2010………………………………………………….……….p. 53 6.3.2 Windows Drivers Kit………………………………………………………..p. 54 6.3.3 WinDbg…………………………………………………………………..…p. 54 6.3.4 DbgView…………………………………………………………………….p. 54 7. Conclusiones……………………………………..………………………………..p. 55 7.1 Comparación con otros productos…………………………...…………………p. 55 7.2 Posibles mejoras…………………………………………………………….….p. 56 7.3 Como proseguiría con el proyecto…………………………………………...…p. 56 8. Planificación temporal…………………………………………………………..p. 58 9. Manual de uso……………………………………………………….…………….p. 61 10. Anexo: Partes del código fuente……………………..…………………………..p. 63 5 [Firewall: controlando el acceso a la red.] PREFACIO 6 [Firewall: controlando el acceso a la red.] En este proyecto se encontrará el diseño y desarrollo de un programa que intenta dar una respuesta a las necesidades actuales de seguridad en un ordenador personal. Es evidente que internet ha marcado una auténtica revolución en los sistemas de comunicación, constituyendo por su extensión y crecimiento exponencial, un fenómeno social de primera magnitud a nivel mundial. Internet, se puede considerar como una red de redes de ordenadores que se encuentran interconectados a modo de superautopistas de la información, lo que permite comunicarnos fácilmente de forma rápida y directa con cualquier otra persona del planeta que esté conectada al sistema. Para el mundo científico y profesional, internet es una herramienta de extraordinaria importancia, solo comparable a la aparición de la imprenta. Es, además, un instrumento tecnológico de uso muy frecuente por parte de niños y jóvenes de todas las edades; las mayoría de las veces como elemento de ocio, y, en ocasiones como método de ayuda para la adquisición de datos y conocimientos académicos. Todas las funcionalidades de Internet (navegación por las páginas web, publicación de weblogs y webs, correo electrónico, mensajería instantánea, foros, chats, gestiones y comercio electrónico, entornos para el ocio...) pueden comportar algún riesgo, al igual como ocurre en las actividades que realizamos en el "mundo físico". Al facilitar información personal y códigos secretos de diversos tipos de cuentas, pueden ser interceptados por ciberladrones y pueden ser usadas para suplantar la personalidad de sus propietarios y realizar incluso compras a su cargo si se ha obtenido números bancarios o cuantas de pago electrónico. Con todo, se van desarrollando sistemas de seguridad (firmas electrónicas, certificados digitales...) que cada vez aseguran más la confidencialidad al enviar los datos personales necesarios para realizar las transacciones económicas. Hay empresas que delinquen vendiendo los datos personales de sus clientes a otras empresas y estafadores. A través de mecanismos como troyanos, spyware o keyloggers se puede conocer todo lo que se hace desde un ordenador, copiar todos los archivos que tiene almacenados un usuario y recabar información confidencial. En la red cada día surgen nuevas amenazas a las que se ven expuestos los usuarios. 7 [Firewall: controlando el acceso a la red.] Este proyecto parte de este ambiente como motivación para realizar un estudio sobre cómo elaborar un sistema de protección que detecte y pueda bloquear las conexiones realizadas por programas en un ordenador personal, actuando así como primera línea defensiva. 8 [Firewall: controlando el acceso a la red.] Capítulo 1 Introducción 9 [Firewall: controlando el acceso a la red.] 1. Introducción 1.1 Seguridad Informática La seguridad informática es una disciplina que se relaciona a diversas técnicas, aplicaciones y dispositivos encargados de asegurar la integridad y privacidad de la información de un sistema informático y sus usuarios. Técnicamente es imposible lograr un sistema informático ciento por ciento seguros, pero buenas medidas de seguridad evitan daños y problemas que pueden ocasionar intrusos. Si bien es cierto que todos los componentes de un sistema informático están expuestos a un ataque (hardware, software y datos) son los datos y la información los sujetos principales de protección de las técnicas de seguridad. La seguridad informática se dedica principalmente a proteger la confidencialidad, la integridad y disponibilidad de la información. Confidencialidad La confidencialidad se refiere a que la información solo puede ser conocida por individuos autorizados. Existen infinidad de posibles ataques contra la privacidad, especialmente en la comunicación de los datos. La transmisión a través de un medio presenta múltiples oportunidades para ser interceptada y copiada: las líneas "pinchadas" la intercepción o recepción electromagnética no autorizada o la simple intrusión directa en los equipos donde la información está físicamente almacenada. Integridad La integridad se refiere a la seguridad de que una información no ha sido alterada, borrada, reordenada, copiada, etc., bien durante el proceso de transmisión o en su propio equipo de origen. Es un riesgo común que el atacante al no poder descifrar un paquete de información y, sabiendo que es importante, simplemente lo intercepte y lo borre. 10 [Firewall: controlando el acceso a la red.] Disponibilidad La disponibilidad de la información se refiere a la seguridad que la información pueda ser recuperada en el momento que se necesite, esto es, evitar su pérdida o bloqueo, bien sea por ataque doloso, mala operación accidental o situaciones fortuitas o de fuerza mayor. 1.2 Amenazas Actuales Malware Por malware se entiende cualquier programa de software que se instala inadvertidamente y de forma discreta en el equipo de un usuario y ejecuta acciones inesperadas o no autorizadas, pero siempre con intenciones maliciosas. Es un término genérico utilizado para referirse a virus, troyanos y gusanos. Virus Es un programa o software que se autoejecuta y se propaga insertando copias de sí mismo en otro programa o documento. Un virus informático se adjunta a un programa o archivo de forma que pueda propagarse, infectando los ordenadores a medida que viaja de un ordenador a otro. Gusanos Los gusanos informáticos se propagan de ordenador a ordenador, pero a diferencia de un virus, tiene la capacidad a propagarse sin la ayuda de una persona. Un gusano informático se aprovecha de un archivo o de características de transporte de tu sistema, para viajar. Lo más peligroso de los worms o gusanos informáticos es su capacidad para replicarse en tu sistema, por lo que tu ordenador podría enviar cientos o miles de copias de sí mismo, creando un efecto devastador enorme. Un ejemplo sería el envío de una copia 11 [Firewall: controlando el acceso a la red.] de sí mismo a cada uno de los contactos de tu libreta de direcciones de tu programa de email. Entonces, el gusano se replica y se envía a cada uno de los contactos de la libreta de direcciones de cada uno de los receptores, y así continuamente. Troyanos Crean puertas traseras o backdoors en tu ordenador permitiendo el acceso de usuarios malévolo a tu sistema, accediendo a tu información confidencial o personal. A diferencia de los virus y gusanos, los troyanos ni se auto replican ni se reproducen infectando otros archivos. Spyware El spyware es cualquier pieza de software, instalado o utilizado sin su conocimiento, que vigila, registra e informa de sus movimientos electrónicos. El spyware comercial vende la información que recopila a empresas de publicidad; el spyware utiliza esta información con fines delictivos para robar la identidad de los usuarios; se trata de la amenaza más grave a la que deben hacer frente los usuarios. Crimeware Es un término general que describe un tipo de software que se utiliza para el hurto de datos financieros. El crimeware se puede propagar prácticamente por medio de cualquier vector de amenaza, incluidos virus/troyanos/gusanos,spyware/adware, entre otros programas, y está formado por programas robot, redes robot y ransomwar 1.3 Porque Proteger un Ordenador Personal Cuando hablamos de seguridad informática, muchas veces hacemos caso omiso a los problemas que nos pueden ocasionar el no estar bien protegidos, pero hasta el momento que nos sucede ahí es cuando nos arrepentimos de no haber tomado las medidas necesarias con anterioridad, creo que la clave de la seguridad informática esta y comienza en nosotros mismos usando solo nuestro sentido común. Lo primero que tenemos que tomar en cuenta antes de tomar precauciones es 12 [Firewall: controlando el acceso a la red.] precisamente conocer de que nos estamos previniendo, debemos saber de qué nos estamos cuidando antes de actuar. Los daños en nuestro ordenador pueden ser muy diferentes dependiendo de la amenaza o virus que tengamos residente en nuestro equipo. Los usuarios cada vez dependen más de los sistemas informáticos como medios para almacenar datos, trabajos, información personal, comunicarse entre ellos y realizar compras por internet, lo que supone una gran cantidad de información privada y susceptible de ser substraída para diversos fines, como usar cuentas de paypal ajenas con las consiguientes consecuencias. 1.4 Firewalls El firewall es un programa que actúa a modo de filtro, bloqueando o permitiendo determinadas conexiones y transmisiones de datos desde o hacia Internet, es decir, actúa como una barrera entre la red y nuestro PC. Para entender esta definición podemos observar la figura: Figura 1. Firewall 13 [Firewall: controlando el acceso a la red.] Como lo dice su traducción, no es otra cosa que un cortafuegos, acepción adaptada de la terminología de los servicios contra incendios, que cuentan con una técnica especial para poder controlar fuegos de gran intensidad y evitar su propagación. Un firewal en informática es muy similar porque permite controlar los accesos no deseados mediante políticas de seguridad que pueden ser definidas por el usuario, tanto a nivel local, como prestando servicios en red. Para ello existen dos modalidades de aplicaciones Firewall: • Aquellas diseñadas para empresas y para ser instaladas en proxys (equipos que sirven como punto de conexión para otros conectados en red). • Las destinadas a los usuarios individuales, que será el tipo de firewall tratado en este proyecto. Este tipo de Firewall te avisa cuando algún programa trata de conectarse a internet desde tu PC, intentando de esta forma proteger el ordenador de posibles intrusiones de Internet. Últimamente debido a la gran cantidad de programas espía que existen, el uso de un firewall se ha vuelto indispensable. De esta manera un usuario puede bloquear programas que ni si quiera se conoce su residencia en el ordenador debido a técnicas de ocultación y no solo bloquear su conexión a internet evitando así una fuga de información sino también descubriendo su existencia en el sistema. 14 [Firewall: controlando el acceso a la red.] Capítulo 2 Objetivos del Proyecto 15 [Firewall: controlando el acceso a la red.] 2. Objetivos del Proyecto El resultado que se espera alcanzar serán los siguientes. Lo que se pretende conseguir con este proyecto es un control por parte del usuario de las conexiones a la red de los programas. 2.1 Control de Conexiones Se propone conseguir es una aplicación (firewall) que regule los accesos a la capa de red de las aplicaciones que lo soliciten. El sistema operativo escogido es Windows 7, ya que es uno de los sistemas operativos con más recepción de malware y el SO más común en los hogares y centros de docencia españoles. Para este menester será necesaria la creación de un driver que filtre las conexiones. 2.1.1 Crear un Driver Para tal cometido se creará un driver, un programa que formará parte del núcleo del sistema operativo y permitirá trabajar a bajo nivel pudiendo obtener el control de ciertas partes del sistema. 2.1.2 Filtro Para acceder a la capa de red será necesario pasar por un filtro que decidirá si la aplicación puede acceder a la capa o no, manteniendo una lista de los procesos que pueden acceder y cuáles no. 16 [Firewall: controlando el acceso a la red.] 2.2 Control por Parte del Usuario Las decisiones del filtro de permitir o denegar el acceso a internet a una determinada aplicación serán tomas por el usuario, por eso mismo será necesario una comunicación y sincronismo entre el usuario y el driver. 2.2.1 Comunicación con el Driver Para dotar al usuario del control del filtro se implementara una infraestructura que permitirá la comunicación bidireccional desde el núcleo del sistema y la interfaz de usuario. 2.2.2 Sincronismo El filtro y las decisiones tomadas por el usuario estaran plenamente sincronizadas asincronamente trabajando asi cada parte por separado a su frecuencia de trabajo sin afectar el rendimiento del sistema ni esperas innecesarias. 17 [Firewall: controlando el acceso a la red.] Capítulo 3 Especificaciones del Proyecto 18 [Firewall: controlando el acceso a la red.] 3. Especificaciones En este apartado se pretende especificar las funciones que deberá tener el proyecto para realizar su cometido. 3.1 Componentes de la Solución El proyecto se dividirá en tres componentes necesarios para obtener su solución: un driver, un programa de control por parte del usuario y un lanzador del conjunto. 3.1.1 Driver El driver permitirá obtener privilegios de system, los más altos en un ordenador quitando cualquier barrera que impida modificar la totalidad del sistema, necesarios para el fin que se intenta alcanzar. 3.1.1.1 Acciones del Driver Se diferencian dos grandes objetivos por parte del driver: el filtraje a la capa de red y la comunicación con el usuario para definir reglas y accesos. 3.1.1.1.1 Comunicación Quien definirá las políticas de acceso a internet para cada programa de forma individual será el usuario, por lo tanto se necesita una infraestructura que permita la comunicación en entre el driver y éste. Mientras se espera la respuesta del usuario la política por defecto empleada será restrictiva, evitando así fugas de información. El driver creara un dispositivo sobre el cual se podrán hacer lecturas y escrituras. 3.1.1.1.1.2 Lectura El driver contendrá un buffer con la información que se pretende transmitir al usuario y se podrá obtener dicha información mediante una lectura al dispositivo. 19 [Firewall: controlando el acceso a la red.] 3.1.1.1.2 Escritura De la misma manera que en la lectura, el driver contendrá un buffer donde el usuario podrá dar órdenes al driver mediante. Todo esto mediante la escritura de los datos en el dispositivo creado para tal fin. 3.1.1.2 Filtro El driver suplantará la capa de red para interponerse entre ella y los programas que deseen acceder a ella. Para ello, suplantará en memoria el driver original de acceso a la red, llamado TCPIP.sys, por la función filtro del driver del proyecto, pudiendo después pasar la llamada al driver original o por el contrario denegando el acceso. 3.1.2 Programa de Control Este programa será el encargado de interactuar con el usuario y el driver corriendo en el contexto de usuario, no el de núcleo del sistema, tal y como en el driver. Para una comunicación con el usuario serán necesarios métodos de sincronización entre las respuestas del usuario y el driver. 3.1.2.1 Sincronismo Debido a la diferente frecuencia de trabajo entre las aplicaciones cpu y usuario, toda la comunicación y establecimiento de permisos será realizado de una manera asíncrona, así que serán necesarios sistemas de sincronización entre el driver y la aplicación de usuario. Para ello se implementaran eventos. 3.1.2.1.1 Creación de Eventos Para sincronizar la aplicación de usuario y el driver, que correrán en contextos de ejecución y privilegios diferentes, recordemos que el driver formará parte del núcleo del sistema operativo y la aplicación de usuario se ejecutará como un usuario administrador. Será necesario la creación de eventos para una sincronización del sistema firewall. 20 [Firewall: controlando el acceso a la red.] 3.1.2.2 Comunicación Para la comunicación con el driver se implementaran lecturas y escrituras al dispositivo creado por el driver para informar y recibir órdenes del usuario. 3.1.2.2.1 Informar al Usuario Mediante la lectura del dispositivo se tratará la información que el driver quiera comunicar al usuario. Se sabrá cuando leer por el sincronismo entre las dos partes. 3.1.2.2.2 Dar Órdenes al Driver Mediante la escritura del dispositivo que crea el driver se permite darle órdenes, que serán las encargadas de definir las políticas de acceso para cada programa. 3.1.3 Lanzador Será el programa que se ejecute para iniciar el firewall y se encargará de poner en funcionamiento las diversas partes. 3.1.3.1 Cargar el Programa de Control Primero de todo, se ejecutara el programa de control del usuario que iniciará los mecanismos de sincronización, para después pasar a cargar el driver. 3.1.3.2 Cargar el Driver Para inicializar el driver en el sistema, antes de todo se registrará el driver como servicio y después se inicializara como servicio activo. 21 [Firewall: controlando el acceso a la red.] Capítulo 4 Diseño y Desarrollo 22 [Firewall: controlando el acceso a la red.] 4. Diseño y Desarrollo En el diseño y desarrollo se explicaran los conocimientos, técnicas e implementaciones usadas para resolver el problema que se plantea. 4.1 El Kernel de Windows En informática, un núcleo o kernel es un software que actúa de sistema operativo. Es el principal responsable de facilitar a los distintos programas acceso seguro al hardware del ordenador o en forma más básica. Es el encargado de gestionar recursos, a través de servicios de llamada al sistema. Como hay muchos programas y el acceso al hardware es limitado, también se encarga de decidir qué programa podrá hacer uso de un dispositivo de hardware y durante cuánto tiempo, lo que se conoce como multiplexado. Acceder al hardware directamente puede ser realmente complejo, por lo que los núcleos suelen implementar una serie de abstracciones del hardware. Esto permite esconder la complejidad, y proporciona una interfaz limpia y uniforme al hardware subyacente, lo que facilita su uso al programador. Las partes formadas por el núcleo son las que corren en privilegios de kernel y los programas ajenos al núcleo que llaman a las abstracciones son los que corren en el modo usuario. 4.1.1 Modo Kernel y Modo Usuario Los privilegios de un sistema operativo se dividen en los siguientes: Figura 2. Anillos 23 [Firewall: controlando el acceso a la red.] Los Ring's (Anillos) de la imagen, son privilege rings (anillos de privilegios). Los que tienen más privilegios están en el corazón (Ring0), y los que menos, al exterior (Ring3). Las aplicaciones que ejecutamos en nuestro PC están todas en Ring3, aunque hay algunas que tienen una parte en Ring3 y otra en Ring0 (Anti-Virus, Firewalls). Esta mezcla entre modo usuario y modo kernel es debida a que el modo usuario está muy limitado en cuanto a privilegios. El firewall se compondrá de dos componentes, uno para cada nivel de privilegios: el driver, que será el que corra en nivel Ring 0 y la aplicación de usuario, que correrá en modo usuario(Ring3). El kernel de Windows incorpora dentro del modo usuario un Subsistema de Ambiente Protegido. Esto significa que controla los errores en los procesos, lo que a su vez implica que si se produce un error en algún programa, éste puede ser descargado de memoria sin provocar en el sistema operativo ningún tipo de error (dependiendo de qué aplicación tenga el error, evidentemente). En modo Kernel no existe esta protección, lo que provoca que si un driver tiene un error, el sistema operativo se ve obligado a reiniciar el PC. Los procesos que se ejecutan dentro de modo usuario y poseen memoria virtual. Por lo tanto, la posición de memoria 0x00457ab no es la misma posición físicamente. Esto lo maneja el Kernel de Windows para impedir que otros procesos escriban o modifiquen datos de otros procesos. Los drivers, en cambio, se ejecutan dentro de la memoria física, lo que equivale a que es posible escribir en la memoria de otros drivers y en el mismo kernel, aunque algunas páginas de memoria tengan protección de escritura (que se puede eliminar). 24 [Firewall: controlando el acceso a la red.] 4.1.1.1 Drivers La descripción que podemos encontrar sobre drivers podría ser esta: Un driver o controlador de dispositivo para equipos con sistema operativo Windows, es un programa cuya finalidad es relacionar el sistema operativo con los dispositivos hardware (tarjeta gráfica, tarjeta de sonido, módem, tarjeta de Tv, wifi, lector mp3, etc.) y periféricos (impresora, escáner, cámara fotográfica, cámara de vídeo, etc.) de nuestro equipo. Pero ese sería el objetivo general de un driver, aprovechando que un driver puede modificar totalmente el sistema operativo, porque forma parte del kernel, se podría usar para alterar la memoria del sistema, su estructura o añadir funciones al sistema que no tengan que ver con la gestión directa de hardware. Un claro ejemplo de esto sería un antivirus, un firewall, un control parental…etc. Una vez se está en modo kernel, tenemos que pensar un poco diferente de cómo lo haríamos en modo usuario. Tenemos que hacernos a la idea de que nuestro driver no es un proceso. Los procesos en modo usuario tienen un espacio propio, definido, y remarco el propio, ya que en modo kernel es algo diferente. Es cierto que nuestro driver se aloja en una posición de memoria definida, pero al hacer llamadas a funciones, tenemos que saber que podemos leer y escribir en toda la memoria (luego veremos que en ciertas áreas se nos está restringido escribir, pero modificando un registro lo podemos deshabilitar). Esto equivale a que si, por ejemplo, queremos modificar el contenido de una de las varias tablas que residen en el kernel de Windows, lo podemos hacer sin necesidad de llamar a ninguna API. Como hemos visto en la figura 2, un driver corre en Ring0 y un contexto diferente al de un programa en modo usuario, sus estructuras y requerimientos son distintos, por lo que las APIs empleadas diferirán de un programa convencional. API (Application Program Interface). Conjunto de convenciones internacionales que definen cómo debe invocarse una determinada función de un programa desde una aplicación. Cuando se intenta estandarizar una plataforma, se estipulan unos APIs comunes a los que deben ajustarse todos los desarrolladores de aplicaciones. 25 [Firewall: controlando el acceso a la red.] 4.1.1.1.1 API para Drivers Windows estipula su propio elenco de APIs, que difiere mucho de las APIs a las que podemos ver en programas de modo usuario. Tomando como ejemplo el lenguaje C, donde podemos encontrar funciones como printf y cabeceras típicas como windows.h, vemos que a la hora de programar drivers en C quedan totalmente restringidas esas funciones y cabeceras. Todas las cabeceras y funciones quedan substituidas por otras e interpretadas y compiladas por un compilador especial para drivers. La mayoría de las funciones no tienen un substituto natural en la API para drivers, inclusive muchas cosas fácilmente implementables en modo usuario no son posibles en un driver. Existen unas tablas donde se guardan las direcciones de memoria a las APIs y demás llamadas al sistema, estas tablas se encuentran en una memoria protegida. 4.1.1.2 Memoria Protegida y Hooks La memoria en el sistema operativo está dividida por páginas, como un libro. Algunas de estas páginas están protegidas por seguridad para que solamente sean de lectura. No todas las estructuras que se puedan modificar están protegidas de este modo, ya que algunas no se tiene que modificar nada y se pueden manipular directamente. Un hook es, básicamente, substituir una función por otra. Por ejemplo, se podría substituir la API MessageBox por una función programada por nosotros mismos para que al llamar la API MessageBox, una aplicación, se ejecute nuestra función en vez de la original. Con esto se puedo modificar o filtrar llamadas al sistema. 26 [Firewall: controlando el acceso a la red.] 4.1.1.2.1 Modificar la Memoria Protegida Las tablas donde se guardan las direcciones de memoria a las APIs del sistema y demás funciones provistas por drivers, en este caso el driver TCPIP.sys, se encuentran en una paginación protegida contra la escritura. El objetivo es interponernos en las llamadas al driver TCPIP por tanto necesitaremos modificar esas tablas. El registro CR0 contiene un bit llamado write protect que es el encargado de señalar si una página es de solo lectura o no. Para eliminar esta propiedad se tiene que poner a cero este bit. Para eso, se implementara un pequeño código en ensamblador dentro del driver que desprotegerá la memoria y la volverá a proteger al acabar la modificación oportuna. En seudocódigo: Guardar EAX EAX = CR0 EAX = EAX AND 0FFFEFFFh CR0 = EAX Para devolverlo a su estado original se procederá de la misma manera pero modificando EAX de la siguiente manera: EAX = EAX OR NOT 0FFFEFFFh Anexo 1. Desactivando la protección y pudiendo modificar las direcciones de las llamadas al sistema se puede suplantar funciones y APIs por otras funciones. 27 [Firewall: controlando el acceso a la red.] 4.1.1.2.2 Suplantación de Funciones del Sistema (hook) Para tal cometido tomaremos como supuesto que se quiere substituir la API ZwOpenProcess, en la tabla SSDT (System Service Descriptor Table) encontrándonos la tabla inicialmente de la siguiente forma: Nombre de la función Dirección ZwOpenProcess 0xAC25698 MessageBox 0xB56516D …. …. Lo que se pretende conseguir es modificar la dirección de ZwOpenProcess para que apunte a una función creada por mí. Para ello, primero se deberá crear una función con los mismos parámetros que la original, que devuelva el mismo tipo de resultado, sino se pueden producir todo tipo de errores y cuelgues en el sistema. Una vez preparada la función substituta con las acciones que se pretendan conseguir con ella será el momento de obtener la dirección de la función substituta, desproteger la memoria como se ha visto anteriormente y substituir la dirección de ZwOpenProcess por la dirección donde se encuentra la nueva función. Si la nueva función se encuentra en la posición de memoria 0x45EF465, tras modificar la tabla quedaría así: Nombre de la función Dirección ZwOpenProcess 0x45EF465 MessageBox 0xB56516D …. …. A partir de entonces cualquier programa que llame a la API ZwOpenProcess, en verdad estará ejecutando otra función. Con esta técnica se puede crear un filtro en las llamadas al sistema. 28 [Firewall: controlando el acceso a la red.] 4.1.1.2.2.1 Creación de un Filtro Se puede controlar el resultado de las llamadas a funciones de APIs he incluso modificar o alterar sus resultados con las diversas técnicas de hooking. Prosiguiendo con el ejemplo anterior de ZwOpenProcess, se podría filtrar los procesos que son abiertos y evitar que ciertos procesos pudieran ser cerrados o modificados (para cerrar o modificar un proceso antes hay que abrirlo con la API ZwOpenProcess o una variante de ella que más tarde llamara a ZwOpenProcess). Ésto se podría conseguir de la siguiente forma: La función substituta se podría obtener mediante los parámetros que son pasados a la función el PID (process identification number) del proceso que se quiere abrir y compararlo con el PID del proceso que queremos proteger. Si coinciden se puede devolver un ACCES_DENIED, por el contrario se devolvería el control a la API ZwOpenProcess, llamándola mediante la dirección original almacenada en una variable, pasándole los parámetros con los que ha sido invocada la función substituta. De esta manera se consigue total transparencia y funcionalidad normal del sistema, pero en realidad se está tomando el control de ciertas acciones permitiéndolas o denegándolas. Se usará esta técnica para obtener cierto control sobre la capa de red, pero en vez de substituir APIs 4.1.1.2.2.1.1 se substituirá un Major del driver TCIP.sys. Substitución del Major Original Un driver por definición y estructura contiene una serie de llamadas o eventos que pueden ser invocados sobre él, llamados majors. Un ejemple de majors seria, por ejemplo, IRP_MJ_READ y IRP_MJ_WRITE, que son eventos llamados cuando se intenta leer o escribir en un dispositivo creado por el driver. El programador del driver, si implementa código dentro de esos eventos en el driver puede realizar las tareas que crea oportunas al llamar esos eventos. Más adelante se verá una implementación para esos dos majors, que nos permitirá comunicarnos con el usuario. 29 [Firewall: controlando el acceso a la red.] Existe una gran variedad de majors IRP_MJ_CREATE, IRP_MJ_WRITE_CLOSE, IRP_MJ_DEVICE_CONTROL... El que será usado para el cometido del proyecto será IRP_MJ_INTERNAL_DEVICE_CONTROL, este major es una llamada genérica de entrada/salida donde el programador puede crear eventos propios del driver que programa. Cuando un programa intenta realizar alguna acción sobre la red como escuchar en un puerto, conectarse a un puerto, enviar un datagrama, etc, se comunica con ese driver. Por lo tanto el objetivo es interceptar ese major concreto. Para substituir el major será necesario modificar el puntero que apunta a la función original. Para ello se obtendrá un puntero al dispositivo del driver, para a través de dicho puntero aconseguir otro al driver, con el fin de acceder al major deseado. El puntero de la dirección original se guardará para devolver el sistema a su estado original al descargar el driver o para realizar llamadas al major original. Se procederá de la siguiente manera: PDispositivo = ObtenerPunteroDispositivo(“\\Device\TCP”) PDriver = PDispositivo->ObjetoDriver IrpMajorOriginal=PDriver>MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] Con esto ya está localizada y guardada la direccion al major original. El siguiente paso será intercambiar esa dirección por la dirección de una función en nuestro driver de la siguiente manera: DesprotegerMemoria() IntercambiarDirecciones(PDriver>MajorFunction[IRP_MJ_INTERNAL_DEVICE_C ONTROL], MiFuncion) ProtegerMemoria() En el anexo 2 se encuentra el código completo. 30 [Firewall: controlando el acceso a la red.] Ahora será necesario crear la función MiFuncion que recibirá la llamada de los demás programas y será ejecutado en el diversos contextos dependiendo de quien haga la llamada. 4.1.1.2.2.1.2 Contexto de la Llamada A partir de ahora hablaremos del diseño y desarrollo de la función substituta (MiFuncion), que será la encargada de hacer la función de filtro permitiendo o denegando el acceso a la capa de red. Como nos situamos dentro del contexto y necesidades que tiene un proceso al llamar al driver de red? Con I/O Request Packet. El Abreviado IRP básicamente es una estructura que permite a diferentes drivers comunicarse entre sí y requerir tareas a finalizar El manejo de IRPs puede ser muy simple o demasiado complejo dependiendo de cuál sea la estructura de los drivers. El IRP también contendrá "peticiones secundarias", conocido como "IRP Stack Location". Cada driver contendrá sus propias peticiones secundarias respecto de cómo interpretar el IRP, denominada IO_STACK_LOCATION. La definición de la función quedara así: NTSTATUS HookedDeviceControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) Siendo DeviceObject el dispositivo al que se intenta acceder y Irp la estructura con los datos necesarios para la llamada. El valor devuelto será ACCES_DENIED si no se permite al proceso acceder a la red, por el contrario será llamada la función original y se devolverá su resultado. Ahora se verá como ver más detalladamente la información del Irp. 31 [Firewall: controlando el acceso a la red.] 4.1.1.2.2.1.3 Obtener Información de los Parámetros de la Llamada Lo primero será situarse en la pila de entrada y salida de información del Irp mediante: PIO_STACK_LOCATION IoStack=IoGetCurrentIrpStackLocation(Irp); Después se podrá interpretar a que subrutina intenta accede el proceso mediante la lectura del Minor Function y poder clasificar que tipo de acción pretende hacer el proceso sobre la red. switch(IoStack->MinorFunction) { case TDI_SEND: //Intenta enviar un paquete TCP case TDI_SEND_DATAGRAM: //Intenta enviar un paquete UDP case TDI_CONNECT: //Intenta realizar una conexión TCP … … De esta manera se podrá determinar qué tipo de acción pretende hacer un programa sobre la red, incluso definir reglas para cada tipo de acción, aunque en este proyecto se usará a modo informativo. 32 [Firewall: controlando el acceso a la red.] 4.1.1.2.2.1.4 Averiguar Quien Llama a la Función El objetivo de este proyecto es decidir qué programas pueden o no pueden acceder a la red. Por lo tanto, en algún momento habrá que localizar quien está efectuando la llamada al driver. Esto es relativamente fácil, ya que la función, al ser llamada por el proceso, se ejecuta en el contexto del proceso pudiendo obtener el PID (process number identification) de manera sencilla con la siguiente instrucción: PID = PsGetCurrentProcessId(); Se ha creado una tabla de PIDs con los permisos de acceso sobre la capa de red. Uno de los objetivos es que sólo se pregunte una vez al usuario si un programa puede acceder o no a la red, así que no sólo será necesario guardar el PID del programa, sino también su ruta y nombre para las siguientes ejecuciones del programa. Ésto ya no es tan trivial, no se pone a disposición del programador ningún tipo de API para tal cometido. He optado por definir una API no definida ni documentada para tal propósito. 4.1.1.2.2.1.4.1 Llamadas a API's no Documentadas ni Definidas Que una API no esté definida en las cabeceras incluidas en un compilador o un entreno de desarrollo y que la msdn de Microsoft no la documente no significa que no esté presente en el sistema, así que se realizará la definición y obtención de la API ZwQueryInformationProcess desde el driver. Para ellos habrá que definir un tipo de función con los parámetros necesarios: typedef NTSTATUS (*QUERY_INFO_PROCESS) ( __in HANDLE ProcessHandle, __in PROCESSINFOCLASS ProcessInformationClass, __out_bcount(ProcessInformationLength) PVOID ProcessInformation, __in ULONG ProcessInformationLength, __out_opt PULONG ReturnLength ); 33 [Firewall: controlando el acceso a la red.] Y definir el nombre de la función que representara al tipo creado: QUERY_INFO_PROCESS ZwQueryInformationProcess; Una vez definida la función con el tipo de parámetros necesarios será el momento de obtener la dirección donde se aloja dicha API y asociarla con la función definida: RtlInitUnicodeString(&routineName, L"ZwQueryInformationProcess"); ZwQueryInformationProcess = (QUERY_INFO_PROCESS)_ _MmGetSystemRoutineAddress(&routineName); De esta manera ya se podra hacer un uso normal de la API. Esta API pasándole el handle del proceso permite acceder a un conjunto de estructuras de información del proceso que nos brindara la ruta y el nombre del programa. hProcess ObtenerHandleProceso() ZwQueryInformationProcess(hProcess, ProcessBasicInformation, &ProcInfo,….) myPEB = ProcInfo.EstructuraPEB pupp = myPEB->ParametrosDelProceso Después de recorrer estas estructuras se obtiene la ruta de la siguiente manera: pupp->ImagePathName.Buffer Con esta información se podrá definir listas de permisos para cada proceso. 34 [Firewall: controlando el acceso a la red.] 4.1.1.2.2.2 Listas Encadenadas en Drivers Una lista encadenada es una de las estructuras de datos fundamentales, y puede ser usada para implementar otras estructuras de datos. Consiste en una secuencia de nodos, en los que se guardan campos de datos arbitrarios y una o dos referencias (punteros) al nodo anterior o posterior. El principal beneficio de las listas enlazadas respecto a los array convencionales es que el orden de los elementos enlazados puede ser diferente al orden de almacenamiento en la memoria o el disco, permitiendo que el orden de recorrido de la lista sea diferente al de almacenamiento. Una lista enlazada es un tipo de dato auto-referenciado porque contienen un puntero o link a otro dato del mismo tipo. Otra de sus ventajas es la asignación de memoria dinámicamente, por cada nuevo nodo se reserva una determinada cantidad de memoria. 4.1.1.2.2.2.1 Reserva de Memoria Como se ha comentado anteriormente la función filtro se ejecutara desde diversos contextos, eso podría ocasionar problemas a la hora de acceder a una lista encadenada. La manera de evitar conflictos será mediante la colocación de esas listas en una porción de memoria no paginada accesible desde cualquier contexto. A la hora de proceder a la declaración de un nuevo nodo se actuara de la siguiente manera: NuevoNodo = RserverMemoria(NoPaginada,TamañoNodo, ' Etiqueta'); *Anexo 3 La Etiqueta serviría en caso de error para poder debugear la memoria e identificar a que está asociada una porción de memoria. Esta lista será accedida desde diversos hilos de ejecución ya que la función filtro podrá ser llamada desde diversos procesos a la vez lo que provocara un intento de acceso a memoria compartida o una modificación simultáneamente, por lo tanto habrá que establecer mecanismos de control de acceso. Los elegidos han sido los mutexs. 35 [Firewall: controlando el acceso a la red.] 4.1.1.2.2.2.2 Mutexs Los algoritmos de exclusión mutua (comúnmente abreviada como mutex por mutual exclusion) se usan en programación para evitar el uso simultáneo de recursos comunes, como variables globales, conocidos como secciones críticas. La mayor parte de estos recursos son las señales, contadores, colas y otros datos que se emplean en la comunicación entre el código que se ejecuta cuando se da servicio a una interrupción y el código que se ejecuta el resto del tiempo. Se trata de un problema de vital importancia porque, si no se toman las precauciones debidas, una interrupción puede ocurrir entre dos instrucciones cualesquiera del código normal y esto puede provocar graves fallos. La técnica que se emplea comúnmente para conseguir la exclusión mutua es inhabilitar las interrupciones durante el conjunto de instrucciones más pequeño, que impedirá la corrupción de la estructura compartida (la sección crítica). Esto impide que el código de la interrupción se ejecute en mitad de la sección crítica. Por lo tanto se definirán dos mutexs uno para cada una de las dos colas que contiene el proyecto. Cuando se quiera acceder o modificar una cola el código de lectura y/o modificación ira entre una abertura de la exclusión y terminará con el cierre de ella. mutex1 = NuevoMutex() ActibarMutex(mutex1) //Tratamiento de la cola DesactibarMutex(mutex1) Existirán dos colas, la de trabajo y la de reglas definidas que serán protegidas de un acceso simultaneo mediante los dos mutexs. 36 [Firewall: controlando el acceso a la red.] 4.1.1.2.2.3 Cola de Trabajo Una de las listas encadenadas será una cola con la información a enviar al usuario. La cola contendrá la información en espera que el usuario la trate. Esa información será el nombre del programa que intenta acceder a la red, y la acción que intenta desarrollar en ese momento se estará a la espera de que el usuario permita o no a esa aplicación acceder a la red. En ese momento se pasara al siguiente nodo, en caso de no haber siguiente nodo se esperara a que lo haya. typedef struct _COLA_TRABAJO { char mensaje[500]; //Explicación de la acción que intenta hacer char path[500]; //Ruta y nombre del programa HANDLE pid; //PID del proceso struct _COLA_TRABAJO *siguiente; //Puntero al siguiente nodo } COLA_TRABAJO, *PCOLA_TRABAJO; La respuesta del usuario sobre una aplicación ira almacenada en la cola de reglas. 4.1.1.2.2.4 Cola de Reglas Definidas La repuesta del usuario sobre denegar o permitir a un programa el acceso a la red será guardada en una lista para ser recordadas. Cuando el filtro sea llamado por una aplicación comprobará la existencia de esa aplicación en la lista de reglas. Si no contiene ninguna entrada para esa aplicación, pasará a crear un nodo en la cola de trabajo para informar al usuario de la nueva aplicación, por el contrario si existe una regla definida la aplicara. Recorrer una cola en busca de reglas puede resultar costoso de tiempo, sobre todo para una aplicación que requiera mucha carga de trabajo con la red, ya que estará pasando continuamente por el filtro y recorrer la lista cada vez es lento e ineficiente. Por tanto antes de recorrer la lista de reglas se comprobara la existencia del proceso en una tabla de hashing. 37 [Firewall: controlando el acceso a la red.] 4.1.1.2.2.5 Tabla Hashing Una tabla hash o mapa hash es una estructura de datos que asocia llaves o claves con valores. La operación principal que soporta de manera eficiente es la búsqueda: permite el acceso a los elementos (teléfono y dirección, por ejemplo) almacenados a partir de una clave generada (usando el nombre o número de cuenta, por ejemplo). Funciona transformando la clave con una función hash en un hash, un número que la tabla hash utiliza para localizar el valor deseado. Figura 3. Tabla hash En el caso que nos ocupa la llave de la tabla será el PID del proceso. Se creará array unidimensional que servirá para alojar los PIDs y sus permisos. Los permisos pueden tomar tres valores: - El primero de los permisos tomará el valor '?' para designar un Pid que no tenga ningún tipo de regla definida. En este caso se recorrerá la lista de reglas y podemos encontrarnos con los siguientes dos supuestos: -Si existe una entrada para ese programa se añadirá la regla a la tabla de hash -Si no existe ninguna entrada significará que el programa es nuevo y por tanto deberá notificarse al usuario que es necesario definir una regla. Es importante indicar que mientras no se designe una regla la política por defecto será restrictiva y no se permitirá el acceso a la red al programa. 38 [Firewall: controlando el acceso a la red.] -Otro valor posible es ‘y’ para cual significa que ese proceso ya ha obtenido el acceso a la red y se le seguirá permitiendo devolviendo el control al Major original del driver TCPIP. - El tercer valor será ‘n’ y significara que ese proceso se le deniega el acceso a la red por tanto el filtro devolverá un ACCES_DENIED. El filtro, al ser llamado, comprobará si el Pid del proceso que lo llama está incluido en la tabla, lo que significará que ese proceso ya ha intentado acceder con anterioridad a la red y ya se ha definido una regla. Si no existe en la tabla, obtendrá la ruta y el nombre del programa y comprobara si está en la lista de reglas. Si esta significará que ese programa ya ha intentado acceder a la red y se ha definido una regla con anterioridad, pero desde una ejecución anterior del programa. Añadirá el proceso a la tabla de hash y aplicará la regla definida. Si no existe en la lista de reglas creara un nuevo nodo a la espera de respuesta por parte del usuario y denegará el acceso a la red al proceso. 4.1.1.3 Comunicación entre Modo kernel y Modo Usuario Para que el usuario pueda establecer las políticas de acceso de cada programa a tiempo real, mientras surgen los programas que desean acceder a la red, debe haber un método de comunicación entre el driver y el usuario. Para ello se creará un dispositivo accesible desde los procesos corriendo en modo usuario sobre el cual poder escribir y leer información para compartir con el driver. 4.1.1.3.1 Creación de Dispositivos Un driver puede crear diversos dispositivos asociados a él que pueden ser listados y accesibles por parte de las aplicaciones para interactuar con el driver y los servicios que ofrezca. Una vez creado el dispositivo, se creará un link para poder ser accedido desde modo usuario. 39 [Firewall: controlando el acceso a la red.] Dispositivo = NuevoDispositivo CrearDispositivo(NuevoDispositivo, nombre, permisos) CrearLink(nombre, nombreLink) 4.1.1.3.1.1 Estructura de un Dispositivo La estructura de un dispositivo es estándar y usada por el sistema operativo para representar el dispositivo, acceder a él y clasificar su tipo, ya que puede ser tanto virtual como ser un dispositivo físico conectado al sistema. typedef struct _DEVICE_OBJECT { CSHORT Type; USHORT Size; LONG ReferenceCount; struct _DRIVER_OBJECT * DriverObject; struct _DEVICE_OBJECT * NextDevice; struct _DEVICE_OBJECT * AttachedDevice; struct _IRP * CurrentIrp; PIO_TIMER Timer; ULONG Flags; ULONG Characteristics; __volatile PVPB Vpb; PVOID DeviceExtension; DEVICE_TYPE DeviceType; CCHAR StackSize; union { LIST_ENTRY ListEntry; WAIT_CONTEXT_BLOCK Wcb; } Queue; ULONG AlignmentRequirement; KDEVICE_QUEUE DeviceQueue; KDPC Dpc; ULONG ActiveThreadCount; PSECURITY_DESCRIPTOR SecurityDescriptor; KEVENT DeviceLock; USHORT SectorSize; USHORT Spare1; struct _DEVOBJ_EXTENSION * DeviceObjectExtension; PVOID Reserved; } DEVICE_OBJECT, *PDEVICE_OBJECT; Las partes que interesan para este proyecto son el puntero a IRP, el DriverObject y el campo Flags. Como se ha visto anteriormente el IRP nos servirá para la estructura de datos y el paso de información. 40 [Firewall: controlando el acceso a la red.] El DriverObject sirve al sistema para saber a qué driver pertenece el dispositivo y quien tiene el control sobre él. Mediante el campo Flags se especifica qué tipo de buffer tendrá el dispositivo para la entrada salida de información. En este dispositivo se a seteado el campo a Flags al tipo de I/O buffered debido a su facilidad para acceder desde el driver a la información, aunque por el contrario es una técnica que usa bastantes recursos. pDeviceObject->Flags |= DO_BUFFERED_IO; Con esta disposición de los elementos ya se podrá proceder a la lectura y escritura en el driver. 4.1.1.3.1.1.1 Lectura Mediante la lectura del driver se obtendrá la información que quiera pasarnos el driver, en este caso el programa que intenta acceder a la red y el tipo de acción que pretende hacer. Para leer el dispositivo se accederá desde modo usuario como si un fichero se tratase. hFile = CreateFileA("\\\\.\\Cerbero", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); Cerbero es el nombre del link al dispositivo y hFile es el descriptor que se obtiene para realizar operaciones sobre él. ReadFile(hFile, &szTemp, 500, &dwReturn, NULL); szTemp es el buffer donde se obtiene la información.500 el número de bytes máximos a leer y dwReturn el número máximo de caracteres a leer. Mediante una ventana emergente se muestra la información al usuario y se obtiene su respuesta para ser pasada al driver mediante la escritura en él. 41 [Firewall: controlando el acceso a la red.] 4.1.1.3.1.1.2 Escritura Una vez se obtiene la respuesta del usuario sobre si permite el acceso o no a la aplicación a la red se escribirá la respuesta al driver como si se tratase de un archivo con el descriptor obtenido al abrir el dispositivo. WriteFile(hFile, respuesta, sizeof(respuesta), &dwReturn, NULL); Respuesta es si se permite o no el acceso y dwReturn la cantidad de bytes escritos. La función del driver que recibe la escritura se encargará de obtener la información pasada por el usuario y añadir la regla a la tabla de hash y a la lista de reglas. Anexo 4. Función llamada al escribir sobre el dispositivo. La escritura del dispositivo se produce después de leer la información y esperar la respuesta del usuario. Pero, ¿cuándo se tiene que leer el dispositivo? ¿Cuándo se sabe cuándo hay información que leer? Se aprovechará la característica bloqueante de la función ReadFile para bloquear la función del driver que se encarga de recibir la lectura hasta que haya información que notificar mediante sincronismos. 4.1.1.3.2 Sincronismo El sincronismo se aplicará para informar al major IRP_MJ_READ cuando hay información que transmitir al usuario. Por lo tanto, la función enlazada a IRP_MJ_READ quedará bloqueada hasta que reciba la señal de desbloqueo, lo que significará que hay un nuevo programa sin regla definida, y hay que comunicárselo al usuario. Para este fin se usarán llamadas a eventos que pueden servir tanto para sincronizar funciones dentro del mismo driver como para sincronizar el driver con aplicaciones en modo usuario. 42 [Firewall: controlando el acceso a la red.] 4.1.1.3.2.1 Creación de Eventos El primer paso será inicializar el evento. syncEvent = IoCreateNotificationEvent(&eventName, &eventHandle); Una vez inicializado dispondremos de tres acciones sobre el evento. KeWaitForSingleObject(syncEventK,Executive,KernelMode, FALSE, NULL); Con esta instrucción se bloquea la ejecución hasta que se reciba la señal de desbloqueo. KeSetEvent(syncEvent, 0, FALSE); Con esta orden se envía la señal de desbloqueo al evento, por lo que el hilo bloqueado con KeWaitForSingleObject reanudara su ejecución. KeResetEvent(syncEventK); Resetea el evento de tal forma que al volver a ser llamada la función KeWaitForSingleObject el hilo quedara bloqueado, sino se resetea después de un KeSetEvent la función KeWaitForSingleObject pasara sin ser bloqueante ya que el evento estará desactivado. Con estos métodos de sincronización se controla cuando debe ser enviada información al usuario. Anexo 5. Función llamada al leer sobre el dispositivo. 43 [Firewall: controlando el acceso a la red.] 4.2 Esquema del Conjunto Programa Cargador: Ejecutar (ProgramaControlUsuario) AbrirManejadorServicios() CrearServicio(“Cerbero”, Cervero.sys //Cerbero.sys es el binario del driver AbrirServicio(“Cerbero”) IniciarServicio(“cerbero”) FinalizarCargador() Programa Control: evento = InicializarEvento() Esperar(evento) //Se activa el evento cuando se carga el driver y crea el dispositivo a leer/escribir handle = Abrir(“Cerbero”) Repetir mensaje = Leer(handle) PopUp(mensaje,”¿Desea permitir el acceso a la red a este programa?”, Si/No) Si ‘si’ Escribir(handle, “permitir”) Si no Escribir(handle, “denegar”) Fin si Fin Repetir 44 [Firewall: controlando el acceso a la red.] Driver: Al cargar el driver: CrearDispositivo(“Cerbero”) CrearLink(Cerbero) evento = InicializarEvento() Driver->MajorFunction[IRP_MJ_READ] = Leer(); Driver->MajorFunction[IRP_MJ_WRITE] = Escribir(); //Leer y Escribir son funciones propias detalladas posteriormente DefinirPermiso(“system”,permitir) DefinirPermiso(“lsass.exe”,permitir) DefinirPermiso(“svchost.exe”,permitir) DefinirPermiso(“RunDll32.exe”,permitir) DesprotegerMemoria() MajorOriginal = pDrv_tcpip-> MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] Substituir(pDrv_tcpip-> MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL],Funcion_filtro()) ProtegerMemoria() Función Leer: EsperarEvento(evento) AbrirMutex() LeerColaTrabajo() PrepararBuffer&Pila() EscribirMensaje(InfoColaTrabajo) CerrarMutex() 45 [Firewall: controlando el acceso a la red.] Función Escribir: PrepararBuffer&Pila() AbrirMutex() permiso = LeerPila() Si permiso = ‘permitir’ NuevaReglaEnLista(InfoColaTrabajo, permitir) Si no NuevaReglaEnLista(InfoColaTrabajo, denegar) Fin si InfoColaTrabajo = InfoColaTrabajo->siguiente Si InfoColaTrabajo = vacio ResetearEvento(evento) Fin si CerrarMutex() Función filtro: PID = ObtenerPID() Si TablaPids[PID] = ‘?’ AbrirMutex() nombre = ObtenerNombrePrograma(PID) permiso = RecorrerListaReglas(nombre) Si permiso = permitir TablaPids[PID] = permitir Devolver(MajorOriginal) Si permiso = denegar TablaPids[PID] = denegar Devolver(Acces_Denied) Si permiso = vacío mensaje = (nombre, ObtenerAccionProceso()) NuevoNodo(ColaTrabajo, mensaje) TablaPids[PID] = denegar 46 [Firewall: controlando el acceso a la red.] SetearEvento(evento) //Se informa que hay nuevas notificaciones CerrarMutex() Devolver(Acces_Denied) Fin Si Si TablaPids[PID] = permitir Devolver(MajorOriginal) Si TablaPids[PID] = denegar Devolver(Acces_Denied) Fin Si 47 [Firewall: controlando el acceso a la red.] Capítulo 5 Evaluación y Juego de Pruebas 48 [Firewall: controlando el acceso a la red.] En el CD se encuentran los diversos videos sobre las pruebas. Se puede observar también en los videos la aplicación DbgView mostrando la información que comunica el driver. De esta manera se sabe que está pasando en el driver. Video permitir_msn.avi Al ejecutar el programa Windows Messenger el firewall nos notifica el intento de la aplicación a acceder a la red. Mientras no seleccionamos ninguna opción el propio Messenger nos notifica que no puede conectarse a internet. Una vez permitimos el acceso a la red si pulsamos el botón de inicio de sesión del Messenger ya se puede acceder a la cuenta. Como se observa el firewall ha ejecutado bien el comportamiento deseado. Video denegar_msn.avi Al contrario que en la prueba anterior, esta vez se va a denegar el acceso, bloqueando cualquier conexión. Se procede como en la prueba anterior pero esta vez en la notificación del firewall se denegara el acceso a la red. Se puede ver que aunque intentemos conectarnos varias veces no se puede conectar a internet. Para comprobar el buen funcionamiento de la lista de reglas se cierra el Messenger y se vuelve a ejecutar. Se puede observar que en la nueva ejecución no se notifica la conexión al usuario ya que el firewall recorriendo la lista de reglas ha encontrado que debe restringir su acceso a internet. 49 [Firewall: controlando el acceso a la red.] Video permitir_navegador.avi Esta vez el programa objetivo es un navegador web, en concreto Google Chrome. Como se puede observar en el DbgView el firewall ya ha detectado y definido reglas antes de ejecutar el navegador. Al ejecutar el navegador, el firewall detecta el intento de acceso a la red y vuelve a ser notificado. Tras permitir la conexión ya se puede proceder a un uso normal del navegador. Video bloquear_navegador.avi Se procede como en el caso anterior pero en la notificación se bloque el acceso a la red. Como se ve aunque se insista o se cierre el programa y se vuelva a abrir se deniega la conexión a internet. Video permitir_servidor.avi En esta demostración se creara un servidor mediante la aplicación NetCat. Mediante el comando ‘nc –vlp 5050’ el programa se pone a la escucha en el puerto 5050. Se pueden observar los siguientes sucesos: -Tras la primera ejecución del programa se puede ver como el programa muestra el siguiente mensaje: Can’t grap with bind. Esto es debido a la política restrictiva del firewall. -Una vez permitido el acceso es necesario reiniciar el programa, esto es usual en las aplicaciones programadas secuencialmente. -Tras permitir el acceso las siguientes ejecuciones no son notificadas al usuario ya que la regla ha sido añadida a la lista de reglas. 50 [Firewall: controlando el acceso a la red.] Capítulo 6 Recursos Usados 51 [Firewall: controlando el acceso a la red.] 6.1 Bibliografía Se ofrece a continuación una relación de bibliografía que el autor ha encontrado útil para trabajar los temas que comprenden este proyecto. Rootkits: Subverting the Windows Kernel Autores: Greg Hoglund y Jamie Butler Páginas: 352 Editorial: Addison-Wesley Professional (Agosto 1, 2005) Los rootkits son backdoors que dan a un atacante el acceso permanente y prácticamente indetectable a los sistemas que explota. Ahora, dos de los principales expertos mundiales, Greg Hoglund y James Butler, han escrito su primera guía completa sobre rootkits: qué son, cómo funcionan, cómo construirlos y cómo detectarlos. En este libro revelan lo que nunca antes se dijo sobre los aspectos ofensivos de la tecnología rootkit. Con su lectura, podrás aprender cómo un atacante puede llegar a entrar y permanecer en un sistema durante años sin ser detectado. Hoglund y Butler muestran exactamente cómo destripar los kernels de Windows XP y Windows 2000, enseñando conceptos que también se pueden aplicar a prácticamente cualquier sistema operativo moderno, desde Windows Server 2003 hasta Linux y Unix. Usando una gran cantidad de ejemplos, se enseñan técnicas de programación de rootkits que pueden ser utilizados por un amplia gama de software, desde herramientas de seguridad hasta drivers y depuradores. The Windows 2000 Device Driver Book: A Guide for Programmers Autores: Art Baker y Jerry Lozano Páginas: 480 Editorial: Prentice Hall; 2 edición (Noviembre 30, 2000) 52 [Firewall: controlando el acceso a la red.] 6.2 Páginas Web www.rootkit.com Actualmente la web esta fuera de servicio, pero contenía información y artículos publicados por programadores de rootkits y técnicas que requieren acceso al kernel de Windows. www.elhacker.net Pagina española dedicada a la seguridad web y programación en general. http://msdn.microsoft.com MSDN es una fuente de información esencial para desarrolladores que utilizan herramientas, productos, tecnologías y servicios de Microsoft. MSDN Library incluye procedimientos y documentación de referencia, código de ejemplo, artículos técnicos, entre otros recursos. 6.3 Software Se ofrece a continuación una relación de programas que el autor ha necesitado para llevar a cabo la implementación del proyecto. 6.3.1 Visual Studio 2010 Microsoft Visual Studio es un entorno de desarrollo integrado para sistemas operativos Windows. Soporta varios lenguajes de programación tales como Visual C++, Visual C#, Visual J#, ASP.NET y Visual Basic .NET, aunque actualmente se han desarrollado las extensiones necesarias para muchos otros. Visual Studio permite a los desarrolladores crear aplicaciones, sitios y aplicaciones web, así como servicios web en cualquier entorno que soporte la plataforma .NET (a partir de la versión net 2002). Así se pueden crear aplicaciones que se intercomuniquen entre estaciones de trabajo, páginas web y dispositivos móviles. Para el proyecto que nos ocupa se ha usado para la programación en C del programa de control del firewall. 53 [Firewall: controlando el acceso a la red.] 6.3.2 Windows Drivers Kit Windows Driver Kit (WDK) versión 7.0.0 contiene las herramientas, ejemplos de código, documentación, compiladores, los encabezados y bibliotecas con las que los desarrolladores de software pueden crear controladores para Windows 7, Windows Vista, Windows XP, Windows Server 2008 R2, Windows Server 2008, y Windows Server 2003. Se ha usado para compilar el driver del firewall para la plataforma Windows 7 en su versión de 32 bits. 6.3.3 WinDbg Windbg es un completo depurador de Microsoft, tanto en modo usuario como en modo núcleo. El paquete Debugging Tools for Windows incorpora un buen número de depuradores y utilidades relacionadas. Se ha usado durante el proyecto para depurar y analizar pantallazos azules durante la ejecución del driver. 6.3.4 DbgView DebugView es una utilidad que deja supervisar y eliminar errores de salida en un sistema local, es capaz de mostrar la salida debug (el depurador) tanto en modo kernel (núcleo del sistema) como en modo usuario. Se ha usado para mostrar información del driver a través del debug para analizar su funcionamiento y valores obtenidos. 54 [Firewall: controlando el acceso a la red.] 7. Conclusiones En este PFC se ha analizado, diseñado y desarrollado un sistema de control de las aplicaciones que pueden acceder a la red. Las políticas de acceso las pone el usuario de manera interactiva cuando un nuevo programa intenta acceder a la red. Para tal fin, se ha tenido que dividir el proyecto en tres partes (cargador, driver y programa de control) con una implementación que suman alrededor de mil líneas de código. Cabe destacar que para la programación de este proyecto se ha tenido que conocer y estudiar a fondo las estructuras del núcleo de Windows y la API específica para programar drivers. La dificultad de programar drivers radica principalmente en unas estructuras complejas y en que el mínimo error puede provocar un reinicio del sistema (pantalla azul de Windows) o incluso corromper partes del sistema. 7.1 Comparación con Otros Productos Comparando el firewall que viene con cualquier Windows 7 de serie, se puede observar como el del proyecto evita fugas de información, mientras no es escogida una regla por el usuario se le deniega el acceso a la red. En cambio, en el de Windows se permite el acceso hasta que el usuario no lo deniega explícitamente. Imaginemos el siguiente suceso. Se introduce un pendrive, infectado con un troyano o un malware que obtenga las contraseñas almacenadas y las envié por correo, en un ordenador. El usuario se aleja del pc o está pendiente de otras cosas (televisión, revistas, hablando…) y el autorun ejecuta el malware. El usuario no vera la alerta del firewall de Windows y mientras no cancele los permisos, el malware podrá enviar los datos recolectados sin impedimento alguno. Ver video ‘comparación’ en el CD. 55 [Firewall: controlando el acceso a la red.] Comparado con productos de pago como Kaspersky o Zone Alarm, por nombrar algunos, queda manifiesta la superioridad de estos debido a sus capacidades para definir más reglas (aceptar conexiones entrantes pero no salientes o viceversa…).El otro punto que distancia la calidad de los productos nombrados con el proyecto es la inexistencia de necesitar reiniciar el programa para aplicar las políticas de seguridad. En el caso del proyecto determinados programas deben ser reiniciados para que se aplique la regla de poder acceder a la red. Aparte de su superioridad en entorno gráfico y poder visualizar y modificar reglas definidas o añadirlas visualmente. 7.2 Posibles Mejoras Se podría implementar un entorno grafico que quedara minimizado en el system tray (iconos del lado del reloj de Windows) y con él poder listar, modificar y agregar reglas. También se podría implementar una pequeña base de datos con las reglas y cargarlas al inicio del driver para ser recordadas en posteriores cargas del firewall. Una pequeña base de datos con una pequeña estructura que entendiese el driver sería suficiente. El fichero contenedor de la base de datos se mantendría cifrado en disco para evitar que posibles programas malintencionados se agregasen a sí mismos a las excepciones del firewall. 7.3 Como Proseguiría con el Proyecto Si se contemplase la posibilidad de proseguir con el proyecto los principales objetivos a conseguir serian inviolabilidad del firewall y mayor información. La inviolabilidad haría referencia a evitar que programes malintencionados alterasen el funcionamiento normal del firewall. Unos ejemplos de ello serian por ejemplo hookear la API ZwOpenProcess para evitar que modificasen o cerrasen el programa de control. También comprobar cada cierto tiempo que el major substituido del driver TCPIP sigue apuntando a la función filtro del driver del firewall. Podría darse el caso que un malware 56 [Firewall: controlando el acceso a la red.] siguiendo las técnicas expuestas en este proyecto quitara el hook de el firewall y dejara a la aplicación ciega ante conexiones de red. La información que se podría recolectar seria tratando la estructura Irp de las llamas al filtro para obtener información como a que dirección intenta conectarse el programa, el puerto destino, el puerto origen… Teniendo información como la anterior se podrían definir ciertas reglas según el destino de la conexión o a los servicios que va destinado (por ejemplo denegándolo todo menos el puerto 80 solo se permite acceso a servidores web). 57 [Firewall: controlando el acceso a la red.] Capítulo 8 Planificación Temporal 58 [Firewall: controlando el acceso a la red.] 59 [Firewall: controlando el acceso a la red.] El tiempo dedicado ha resultado una media de cuatro horas a la semana durante los ocho meses dedicados al proyecto. 60 [Firewall: controlando el acceso a la red.] Capítulo 9 Manual de Uso 61 [Firewall: controlando el acceso a la red.] Dentro del CD se encuentra diverso material como la documentación, binarios, proyectos, planificación temporal y videos de prueba. Los binarios incluidos están compilados para una plataforma Windows 7 de 32 bits. Se puede lanzar el firewall pulsando el botón derecho del ratón sobre el archivo ‘loader.exe’ y pulsando ‘Ejecutar como administrador’. A partir de entonces cuando un programa intente comunicarse con la red aparecerá lo siguiente en pantalla: Si se pulsa ‘no’ no se permitira el acceso a la red por parte del programa mostrado, por el contrario si se pulsa ‘si’ se permitira su acceso. Dependiendo de cómo este implementado sera necesario reiniciar el programa en cuestion. También se incluye el programa DbgView que ejecutándolo en modo administrador nos permitirá ver los mensajes del driver hacia el debug. Eso nos permitirá saber que esta ocurriendo en cada momento. Ver video Cargar_firewall.avi. Dependiendo de la plataforma debería ser recompilado para un correcto funcionamiento, se ha de recordar que un mal funcionamiento significaría una panta azul y reinicio del sistema y una posible corrupción del sistema. Para compilar el driver desde la consola de Windows Drivers Kits se accede al directorio de las fuentes mediante el comando ‘cd ruta’ y se compila con ‘build –cZ’. Los otros dos programas se pueden compilar mediante el IDE de Visual Studio 2010. 62 [Firewall: controlando el acceso a la red.] Capítulo 10 Anexo: Partes del Código Fuente 63 [Firewall: controlando el acceso a la red.] Anexo 1. __asm { push eax //se guarda el valor de EAX mov //se mueve el registro a EAX eax, CR0 and eax, 0FFFEFFFFh de escritura //se desactiva el bit de protección mov CR0, eax //se escribe el registro pop eax //se restablece el valor de EAX } //Modificaciones de memoria a hacer. __asm { push eax //se guarda el valor de EAX mov eax, CR0 //se mueve el registro a EAX eax, 0FFFEFFFFh //se activa el bit de protección de mov CR0, eax //se escribe el registro pop eax //se restablece el valor de EAX and escritura } 64 [Firewall: controlando el acceso a la red.] Anexo 2 NTSTATUS InstalarTCPDriverHook(){ NTSTATUS ntStatus; //Estado de las operaciones UNICODE_STRING deviceTCP; //Nombre del dispositivo TCP en unicode WCHAR deviceTCPNombre[] = L"\\Device\\Tcp"; //Nombre del dispositivo TCP pFile_tcp = NULL; //Inicializacion del objeto TCP pDev_tcp = NULL; //Inicializacion del dispositivo TCP pDrv_tcpip = NULL; //Inicializacion del driver TCP RtlInitUnicodeString (&deviceTCP, deviceTCPNombre); //Paso de la cadena a unicode ntStatus = IoGetDeviceObjectPointer(&deviceTCP, FILE_READ_DATA, &pFile_tcp, &pDev_tcp); //Obtengo el puntero al inicio del dispositivo TCP if(!NT_SUCCESS(ntStatus)) mal se sale //Si algo a ido return ntStatus; pDrv_tcpip = pDev_tcp->DriverObject; puntero al driver TCP //Obtengo el OldIrpMjDeviceControl = pDrv_tcpip>MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL]; //Guardo la direccion del IRP_MJ_INTERNAL_DEVICE_CONTROL del driver __asm 65 [Firewall: controlando el acceso a la red.] { push eax //se guarda el valor de EAX mov //se mueve el registro a EAX and escritura eax, CR0 eax, 0FFFEFFFFh //se desactiba el bit de protección de mov CR0, eax //se escribe el registro pop eax //se restablece el valor de EAX } if (OldIrpMjDeviceControl){ //Si se obtiene bien InterlockedExchange ((PLONG)&pDrv_tcpip>MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL], (LONG)HookedDeviceControl); //Substituyo la direccion de memoria de la funcion IRP_MJ_INTERNAL_DEVICE_CONTROL del driver por mi propia funcion. } __asm { push eax //se guarda el valor de EAX mov //se mueve el registro a EAX and escritura eax, CR0 eax, 0FFFEFFFFh //se actiba el bit de protección de mov CR0, eax //se escribe el registro pop eax //se restablece el valor de EAX } return STATUS_SUCCESS; } 66 [Firewall: controlando el acceso a la red.] Anexo 3 nuevo = (PCOLA_TRABAJO)ExAllocatePoolWithTag(NonPagedPool, sizeof(COLA_TRABAJO), ' pxE'); Anexo 4 NTSTATUS Escribir(PDEVICE_OBJECT DeviceObject, PIRP Irp) { NTSTATUS NtStatus = STATUS_SUCCESS; PIO_STACK_LOCATION pIoStackIrp = NULL; //Puntero a la pila actual PCHAR entrada; //Puntero a la cadena donde se alojara el contenido escrito PCOLA_PATH nuevo; __try{ nuevo = (PCOLA_PATH)ExAllocatePoolWithTag(NonPagedPool, sizeof(COLA_PATH), ' pxE'); KeWaitForMutexObject(&mutex, Executive, KernelMode, FALSE, NULL); pIoStackIrp = IoGetCurrentIrpStackLocation(Irp); obtiene el puntero a la actual pila //Se if(pIoStackIrp) //Si se a obtenido el puntero a la pila correctamente... { entrada = (PCHAR)Irp->AssociatedIrp.SystemBuffer; asocia un buffer con la pila if(entrada) //Si se ha asociado correctamente... { 67 //Se [Firewall: controlando el acceso a la red.] if(StringTerminada(entrada, pIoStackIrp>Parameters.Write.Length)) //Se comprueba si tiene caracter termiandor { nuevo->siguiente = NULL; if(entrada[0] == 'y'){ PIDS[(int)primer_trb->pid] = 'y'; nuevo->permitido = 1; RtlStringCbPrintfA(nuevo->path, 500 * sizeof(char),"%s",primer_trb->path); nuevo->siguiente = NULL; DbgPrint("[CERBERO] Se permite: %s\n",nuevo->path); } else{ PIDS[(int)primer_trb->pid] = 'n'; nuevo->permitido = 0; RtlStringCbPrintfA(nuevo->path, 500 * sizeof(char),"%s",primer_trb->path); nuevo->siguiente = NULL; DbgPrint("[CERBERO] NO Se permite: %s\n",nuevo->path); } ultimo->siguiente = nuevo; ultimo = nuevo; if(primer_trb->siguiente) primer_trb = primer_trb->siguiente; else{ primer_trb = NULL; ultimo_trj = NULL; } if(primer_trb == NULL) KeResetEvent(syncEventK); 68 [Firewall: controlando el acceso a la red.] } } } KeReleaseMutex(&mutex,FALSE); }__except(EXCEPTION_EXECUTE_HANDLER){ NtStatus = GetExceptionCode(); DbgPrint("[CERBERO] Exception escribir: %d.\n", NtStatus); } return NtStatus; } Anexo 5 NTSTATUS Leer(PDEVICE_OBJECT DeviceObject, PIRP Irp) { NTSTATUS NtStatus = STATUS_BUFFER_TOO_SMALL; memoria disponible por defecto //No hay PIO_STACK_LOCATION pIoStackIrp = NULL; //Puntero a la pila actual UINT longitud; //Longitud del mensaje a enviar UINT longitudLeida = 0; //Longitud del total de bytes anviados PCHAR bufferLectura; //Puntero a la cadena compartida con el usuario __try{ KeWaitForSingleObject(syncEventK,Executive,KernelMode, FALSE, NULL); KeWaitForMutexObject(&mutex, Executive, KernelMode, FALSE, NULL); RtlStringCbLengthA(primer_trb->mensaje, 500* sizeof(char), &longitud); //Obtengo la longitud de la cadena mensaje 69 [Firewall: controlando el acceso a la red.] pIoStackIrp = IoGetCurrentIrpStackLocation(Irp); //Puntero a la actual pila if(pIoStackIrp) //Si se a obtenido el puntero a la pila correctamente... { bufferLectura = (PCHAR)Irp->AssociatedIrp.SystemBuffer; //Se asocia un buffer con la pila if(bufferLectura && pIoStackIrp->Parameters.Read.Length >= longitud) //Si se a asociado correctamente y se dispone de espacio para copiar el mensaje... { longitud); RtlCopyMemory(bufferLectura, primer_trb->mensaje, //Se copia el mensaje al buffer de lectura longitudLeida = longitud; //Bytes a enviar NtStatus = STATUS_SUCCESS; //Todo correcto } } Irp->IoStatus.Status = NtStatus; //Todo correcto Irp->IoStatus.Information = longitudLeida; bytes a leer //Numero de IoCompleteRequest(Irp, IO_NO_INCREMENT); completada //Rutina KeReleaseMutex(&mutex,FALSE); }__except(EXCEPTION_EXECUTE_HANDLER){ NtStatus = GetExceptionCode(); DbgPrint("[CERBERO] Exception leer: %d.\n", NtStatus); } return NtStatus; //Devuelve el estado } 70 [Firewall: controlando el acceso a la red.] 71