0. Dirty COW이란 무엇인가?
Dirty COW은 간단히 말해 읽기 전용의 메모리 공간에 대해 쓰기 권한을 얻는 기법이다.
1. 기법 이해를 위해 필요한 사전 지식
Dirty COW는 단순한 메모리 버그가 아닌
다양한 운영체제의 메커니즘과 Race Condition이 충돌해서 생긴 결과이다.
따라서 다음 운영체제 메커니즘에 대한 이해가 필요하다:
- Virtual Memory
- Paging
- Page Table Entry (PTE)
- Page Fault
- Copy-on-Write (CoW)
- Demand Paging
- User & Kernel Space
- Memory-Mapped File
또한 Race Condition 취약점에 대한 이해가 필요하기 때문에 아래 문서를 먼저 읽고 오는 것을 권장한다:
2. 운영체제 관점에서의 기법 분석
Dirty COW는 읽기 전용의 메모리 공간에 대해 쓰기 권한을 얻는 기법이라고 하였다.
그렇다면 정확히 어떻게 얻는 것일까?
기법에 이름에 힌트가 있다.
Dirty (페이지에 쓰기가 발생한 적이 있음) + COW (Copy-on-Write) = Dirty COW
즉 해당 기법은 읽기 전용 메모리 공간(MAP_PRIVATE)의 CoW 과정에서 “Race Condition”을 사용해 읽기 전용 메모리 공간에 대해 쓰기 권한을 얻는 기법인 것이다.
본 문서에서는 이 Dirty COW 기법이 어떤 과정을 거쳐 권한 상승으로 이어졌는지 상세히 분석해보고자 한다.
Dirty COW는 근본적으로 “쓰기 불가능한 메모리 공간”에 “값 쓰기가 가능”해지는 기법이다.
그렇다면 이것이 어떻게 권한 상승으로 이어지게 될까?
방법은 널리 알려진 2가지가 있다.
1번째 방법은 /etc/passwd 파일 쓰기이다.
Linux 커널에서 유저 계정은 /etc/passwd에서 관리한다.
이때 커널의 관리자 권한을 갖는 root 계정에 대한 정보도 /etc/passwd에 포함되어 있으며, 보통 다음과 같이 적혀있는 것을 확인할 수 있다:
~$ cat /etc/passwd
root:x:0:0:root:/root:/usr/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
이때 왼쪽에서부터 차례대로
사용자 계정명:비밀번호:UID:GID:Comment:홈 디렉토리:로그인 쉘
의 구조이다.
/etc/passwd에 값을 쓸 수 있게 되면
- 이 구조를 따라 관리자 권한을 가진 계정을 만들어 끼워넣거나,
- 단순히
root:x:0:0..부분에서x를 지워su로 로그인
하는 식으로 권한 상승이 가능해진다.
2번째 방법은 SUID 파일에 쉘코드를 주입하는 방법이다.
SUID 파일은 파일을 실행시켰을 때, 파일을 실행시킨 사용자가 아닌 파일 소유자의 권한으로 실행되는 파일을 의미한다.
즉 SUID 파일 중 소유자가 관리자인 파일이 있다면, 해당 파일을 실행시킬 시 관리자 권한으로 실행되기 때문에 이 파일에 쉘코드를 주입해 관리자 권한을 얻는 방법이다.
이제 읽기 전용 메모리 공간에 쓰기 권한을 획득하는 것만으로도 권한 상승이 가능하다는 것을 알았으니, Dirty COW의 원리에 대해 자세히 분석해보자.
설명의 편의를 위해 나는 앞서 설명한 두 방법 중, /etc/passwd에서 root:x:의 x를 찾아 지우는 방법을 사용하겠다.
먼저 읽기 전용 파일(/etc/passwd)을 MMF로 프로세스의 가상 메모리에 매핑한다.
이때 MAP_PRIVATE로 선언하여 해당 파일에 대해 Copy-on-Write를 사용할 수 있게 한다.
정상적인 경우, MAP_PRIVATE로 매핑된 파일 페이지에 대한 쓰기는
반드시 Copy-on-Write를 통해 프로세스 전용 사본으로 분리한 뒤 이루어진다.
즉, 어떤 상황에서도 Page Cache(파일의 실제 내용) 자체는 쓰기 대상이 되지 않는다.
여기서 Race Condition이 치고 들어온다.
Copy-on-Write가 수행되는 도중,
쓰기의 대상이 사본 페이지로 완전히 전환되기 전에
쓰기 동작이 먼저 실행되도록 만들어 Page Cache 페이지를 직접 쓰기 대상으로 만든다.
그 결과, MAP_PRIVATE로 매핑된 읽기 전용 파일의 Page Cache가 오염된다.
다시 말해 파일 내용 자체가 변경되는 것이다.
정리하자면, Dirty COW는 Race Condition으로 원래는 쓰기 대상이 될 수 없는 Page Cache가 쓰기 대상으로 변하는 순간을 만들고, 그 결과 MAP_PRIVATE 매핑임에도 파일 내용을 변조할 수 있게 된다.
그렇다면 Page Cache를 쓰기 대상으로 만드는 핵심인 Race Condition은 어떻게 만들어야 할까?
방법이 여러가지가 있고, 기법을 여기까지 이해했다면
각 방법의 Race 원리를 쉽게 파악할 수 있을 것이기 때문에,
본 문서에서는 범용적으로 어떻게 Race를 만들면 되는지만 설명하고자 한다.
이 Race Condition은 CoW가 단계적으로 수행되는 도중에
페이지가 다시 Page Cache로 되돌아오는 순간이 생길 수 있는 어떤 경로에서든 만들어질 수 있다.
따라서 Race를 유도하려면, 아래 두 동작이 동시에 반복되도록 만들면 된다:
- Thread 1: 해당 가상 주소가 사본이 아닌 Page Cache를 다시 보도록 사본을 계속 무효화
- Thread 2: 같은 가상 주소에 대해 쓰기를 지속적으로 시도하여 쓰기 경로를 계속 트리거