Estas aquiContenido / Análisis del exploit vmsplice del kernel linux

Análisis del exploit vmsplice del kernel linux


Poradmin- Publicado el14 Febrero 2008

McAfee Labs acaba de publicar un detallado informe técnico sobre el funcionamiento del peligroso exploit que permite lograr acceso root en ciertos kernel linux no parcheados.

El análisis concluye que para lograr su objetivo el exploit utiliza un cóctel de desbordamiento de buffer, desbordamiento de enteros y punteros NULL.

Aunque la mejor solución es parchear el kernel (o instalar una nueva versión parcheada), si eso no resulta posible se apunta también a la posibilidad alternativa de utilizar un módulo que deshabilita la llamada del sistema a sys_vmsplice.

Etiquetas

Comentarios

Selecciona arriba tu forma preferida de visualizar los comentarios y pulsa el botón para guardar tu elección para próximas visitas (sólo si eres usuario registrado).

Ultimamente he estado trabajando con modulos y con la tabla de llamadas al sistema, vi por muchos sitios que siempre se intenta encontrar esta tabla con la tecnica de:

unsigned long ptr;
extern unsigned long loops_per_jiffy;

for (ptr=(unsigned long)&loops_per_jiffy ; ptr<(unsigned long)&boot_cpu_data ; ptr+=sizeof(void *))
{
p = (unsigned char **)ptr;
if (p[__NR_close] == (unsigned char *)sys_close)
{
return (void **)p;
}
}

Con este codigo me asaltan dudas, la primera es ¿por que se usa ptr y p, no seria mas sencillo quitar p y usar solo ptr? La segunda duda es si realmente esto funciona, lo que se hace es ir desde la direccion de loops_per_jiffy hasta la de boot_cpu_data mirando si en cada incremento a lo que apunta p (y consecuentemente ptr) es la llamada al sistema sys_close. No se si se supone que la sys_call_table tiene que estar entre esas dos variables y por que todos estos ejemplos usan sys_close... ¿hay algun motivo? ¿porque no usan por ejemplo sys_restart_syscall que es la primera :s, porque se usa la sexta llamada al sistema (sys_close) :s?

He aqui informacion en mi sistema. Me voy a /proc/kallsyms y busco estas variables:

c03af1e8 D loops_per_jiffy
...
c03dac00 D boot_cpu_data
...
c02fc540 R sys_call_table

Podemos ver claramente que sys_call_table NO esta entre loops_per_jiffy y boot_cpu_data... ¿alguien podria explicarme entonces por que esa tecnica es tan popular? Lo unico que se me ocurre es que la sys_call_table tenga varios punteros apuntando a ella... alguno entre esas dos variables... De todas formas yo probe en mi sistema a ejecutarlo usando solo ptr y quitando p y no encontraba la tabla...

Fíjate en que ptr es "unsigned long" mientras que p es "unsigned char **".

Al acceder a p[__NR_close] no se accede a la misma posición si el desplazamiento se calcula en "long" que en "char **".

Hasta hoy siempre he pensado lo mismo, "los punteros no son mas que un indice a algun lado de la memoria". No comprendo a que te refieres con "si el desplazamiento se calcula..." ¿que desplazamiento se calcula para acceder a un puntero?.

Siempre he entendido que si un puntero contiene el numero que sea se accede a la posicion de memoria de ese numero sin mas, si tiene 0x08b7ad04 pues se accede ahi, sin hacerle operaciones. Me habia fijado en los tipos con los que se declara, pero me parece irrelevante, se podria haber declarado ptr como unsigned char ** y no como unsigned long y en la inicializacion del for poner un simple cast ptr = (unsigned char **)&loops_per_jiffy y asunto zanjado, los cast tampoco realizan operaciones sobre el contenido (salvo truncamiento). Como ultimo detalle por si acaso unsigned long y unsigned char ** pesan en memoria lo mismo, 4 bytes, asi que tambien me parece irrelevante los tipos que se usen en las declaraciones mientras tengan ese tamaño o mas...

¿Que opinais?

Te veo un poco confundido ...

Un puntero contiene una dirección de memoria, así que si incrementas en uno el puntero, accederas a la posición de memorias siguiente. Hasta aquí bien.

Ahora hablemos de arrays. Si tienes un array, por ejemplo de char, a[0] corresponde al primer elemento, a[1] al segundo, etc. Lo que significa que a[0] corresponde a la posicion p, a[1] a la posición p+sizeof(char), a[2] a la posición p+sizeof(char)*2, etc.

Resumiendo, que tu problema és que usas el puntero como si fuese un array, por lo que la indexación no va de uno en uno, si no que va de "tamaño de dato" en "tamaño de dato".

Para muestra un botón:

int main()
{
int a[] = {1 ,2 ,3 ,4 ,5 ,6 ,7 ,8, 9, 10};
char *p = a;
int *ptr = a;

printf("%d\n", p[8]);
printf("%d\n", ptr[8]);

}

$ gcc main.c
$ ./a.out
3
9

Saludos.

Gracias, eso lo se, no ando tan despistado con los punteros, sabia que los desplazamientos que les sumes al puntero son relativos al tamaño del tipo que guarden, pero a eso iba antes. Tanto unsigned long como unsigned char ** ocupan lo mismo 4 bytes en memoria y por eso me parece irrelevante usar por un lado ptr y por otro p.

ptr[__NR_close] y p[__NR_close] deberian ser iguales debido a que sizeof(unsigned char **) y sizeof(unsigned long) son iguales (4 bytes).

De todas maneras esa es la menor de mis dudas, usar una variable mas o una variable menos da casi igual... 4 bytes arriba o abajo en la pila en principio es practicamente irrelevante. La verdadera duda que tenia es por que se usa tanto la tecnica de buscar entre loops_per_jiffy y boot_cpu_data.

Fíjate:

#include
int main()
{
unsigned long ptr;
unsigned long loops_per_jiffy = 0;

ptr=(unsigned long)&loops_per_jiffy;
unsigned char **p1;
unsigned char **p2;

p1 = (unsigned char **)ptr;
p2 = (unsigned char **)&loops_per_jiffy;

printf("p1: %d, ", p1);
ptr += sizeof(void*);
p1 = (unsigned char **)ptr;
printf("p1+4: %d\n", p1);

printf("p2: %d, ", p2);
p2 += sizeof(void*);
printf("p2+4: %d\n", p2);

return 0;
}

$ gcc main.c
$ ./a.out
p1: -1075259352, p1+4: -1075259348
p2: -1075259352, p2+4: -1075259336

Como ves en el primer caso el desplazamiento es de 4, mientras que en el segundo es
de 4*4.

Al sumarle 4 bytes a p1 te desplazas 4 bytes en memoria, dado que el tamaño del dato
es de 1 byte. Al sumarle 4 bytes a p2 te desplazas 16 bytes en memoria, dado que el
tamaño del dato es de 4 bytes.

Saludos.

No estoy de acuerdo con como lo has explicado, entiendo el por qué del resultado, ¡ya he dicho que sí me manejo con punteros!.

El programa utiliza un truco bastante guarro xD, el hecho que ya comentamos de que al sumarle X a un puntero no se suma solo X sino X*sizeof(dato que guarda el puntero) y que es de lo que se vale tu programa. Yo lo entiendo!

printf("p1: %d, ", p1);
ptr += sizeof(void*);
p1 = (unsigned char **)ptr;
printf("p1+4: %d\n", p1);

Trucazo! xDD. ptr es de tipo unsigned long por lo tanto en esa suma de sizeof(void *) solo se suma 4.

printf("p2: %d, ", p2);
p2 += sizeof(void*);
printf("p2+4: %d\n", p2);

Y aqui esta el ocultamiento. p2 es un puntero a char **. O sea, que apunta a elementos de 4 bytes (apunta a punteros :s) por lo tanto la suma es X*sizeof(dato que apunta) = sizeof(void*)*sizeof(unsigned char **) = 4*4.

YO YA SE MANEJAR PUNTEROS!!! xDD. No me lo expliquen mas, porque ademas sigo planteando la misma situacion. En el codigo:

unsigned long ptr;
extern unsigned long loops_per_jiffy;

for (ptr=(unsigned long)&loops_per_jiffy ; ptr<(unsigned long)&boot_cpu_data ; ptr+=sizeof(void *))
{
p = (unsigned char **)ptr;
if (p[__NR_close] == (unsigned char *)sys_close)
{
return (void **)p;
}
}

Podria dejarse de usar p y usar solo ptr asi

unsigned long ptr;
extern unsigned long loops_per_jiffy;

for (ptr=(unsigned long)&loops_per_jiffy ; ptr<(unsigned long)&boot_cpu_data ; ptr+=sizeof(void *))
{

if (((unsigned char **)ptr)[__NR_close] == (unsigned char *)sys_close)
{
return (void **)ptr;
}
}

Avanza de 4 en 4, no de 16 en 16. PERO LA VERDADERA DUDA NO ES ESA! Sino por qué se utiliza tanto este código para buscar la tabla de símbolos si no es seguro que funcione. De hecho en mi ordenador no funciona. Miren, he hecho este codigo que en principio si encuentra la sys_call_table.

90 /* Try to find the sys_call_table. */
91 static void ** get_sys_call_table()
92 {
93 void *system_call, **ptr;

114 struct idtr
115 {
116 unsigned short limit;
117 unsigned int base;
118 }__attribute__((packed)) idtr;
119

140 struct idtr_entry
141 {
142 unsigned short low;
143 unsigned short selector;
144 unsigned char none, flags;
145 unsigned short high;
146 }__attribute__((packed)) entry;
147

152 __asm__ __volatile__ ("sidt %0": "=g" (idtr));
153
154 /* Read syscall entry information. */
155 memcpy(&entry, (void *)(idtr.base+sizeof(struct idtr_entry)*0x80), sizeof(struct idtr_entry));
156 system_call = (void *)(entry.high << 16 | entry.low);
157
158 ptr = (void **)memmem(system_call, 100, "\xff\x14\x85", 3);
159 if (ptr == NULL)
160 {
161 printk(KERN_ALERT "Cannot find call instr\n");
162 return NULL;
163 }
164 else
165 {
166 printk(KERN_ALERT "Call inst founded, checking sys_call_table\n");
167 ptr = (void **)*((int *)(((char *)ptr)+3));
168
169 if (ptr[__NR_close] == (void *)sys_close)
170 printk(KERN_ALERT "Seems to be sys_call_table\n");
171 else
172 {
173 printk(KERN_ALERT "Are you sure system_call has only one call instr?\n");
174 return NULL;
175 }
176 }
177
178 printk(KERN_ALERT "Sys_call_table is at 0x%p\n", ptr);
179 return ptr;
180 }

Se que es un poco guarrete, pero funciona.

No creo que sea justo llamarlo un "Buffer overflow". No hay nada de eso. Lo único que sucede es que, en la mayoría de sistemas es posible adivinar la posición en memoria que en teoría debe tener la función "vm_splice", y ésta no autenticaba la llamada.

Es un problema de no autenticación, y no un "buffer overflow". Para atacar la vulnerabilidad, se deduce la posición en memoria y se intenta ejecutar el código "a lo bruto", sin saber exactamente qué hay allí. Eso no es un buffer overflow. Es un programa que intencionadamente cede el control fuera de su rango de programa.

Buffer Overflow es cuando, dada una entrada (input) determinada a un programa, recurso o función, logra que ésta manipule mal la memoria.

Sí era un buffer overflow; entendí mal lo que estaba pasando. El fallo sí viene de una falta de comprobación de un puntero en vmsplice como bien indica en la última entrada sobre el asunto que tiene Kriptópolis.

¿2.6.24 Ya lo contempla?

Patrocinadores

Kriptópolis alojado en
Zilos-Veloxia Network

Tu mejor defensa:
Bufet Almeida