応募を決めた理由

セキュキャンは一度しか参加できないので,割と慎重になっていたが,今回のキャンプに参加しても来年以降も参加できるとの事なので,参加しないのはもったいないと思った.

選択

Z-V Rustで作るLinux向けアンチウィルスソフトウェア

選択コースも面白そうなコースが多くて迷ったが,Rustに惹かれた.

Rust,AVの2つの単語で心が踊らない男はいない.

最近は基本的になんでもRustで書くタイプの人間をしているが,本を読むことも無くただひたすらに書いて覚えてきたので,一度人に教わりながらRustを書いてみたかった.

課題

課題を引用する


Z5. 「Rustで作るLinux向けアンチウィルスソフトウェア」

課題1,2は必須です。課題3は設計ドキュメントだけ、実装が途中 でもOKです。 なお、募集期限のうちは、ソースコードを公開リポジトリに置かないようにしてください。

課題1

  • ClamAVのclamonaccのソースコードを読んで、その機能、必要性を説明してください。またfanotifyがない古いカーネルでは、カーネルモジュール(clamuko)が必要でしたが、必要だった理由を考察してください。

課題2

  • Rustとyaraのbindingsを使い、UPXを検知するスキャナプログラムを作成してください。
    • 引数でスキャン対象のファイルとyaraのルールファイルを指定して実行するようなものを作成してください。
    • 加えて、ディレクトリをスキャンできるようにし、/usrをスキャンして計算時間を計測してください。

課題3

  • 課題2のスキャナを拡張し、アンチウィルスサービス(デーモン化)をRustで実装してください。
    • 並列スキャンができるように設計してください。
    • アンチウィルスサービスのIFは自由です。
    • スキャン処理だけでリソースが枯渇しないような処理の制限を設定できるようにしてください。

課題2

先に取り組んだのは課題2だった.

yara crateを使えば実装難易度はgrepくらいのものなので,割とすぐ終わった.

課題1

私には課題を期限ギリギリまでやらない悪い癖がある.

楽しい実装の課題だけやって,「おもしろそうだし他のトラックの課題もやってみよう」などと軽い考えで生活した結果,締切前日の夜中に夜行バスの中でソースコードを読むことになった.

割と読みやすい綺麗なソースコードだったのと,Linuxカーネルを読む経験があったので,解読自体は割と難しいものではなかった.

一方で,ソースコードを読んでプログラムの実装はなんとなく理解しても,その機能や必要性を言語化するのは少し難しかった(結果,かなり粗末な日本語を生成してしまった).

clamukoについては,カーネルモジュールのソースコードを読もうと思って気合を入れて探してみたら,蓋を開けたら中身はほぼ別のモジュール(dazuko)を使ってるだけだった(これはもしかしたら見るバージョンが違っていたのかもしれない).

課題3

1で作ったのはほぼgrepなので,まずは使いやすいようにファイル分けたりした.

並列処理については扱い慣れてないので,別プロジェクトで勉強したりしてる.

感想

後輩がどんどん通ってるのをTwitterで見て,連絡が無いので落ちたんだと思ってた.

やけになって別の事したりしてたけどちょっとしてから封筒が来てた.

通ったのは嬉しいので頑張る.

気になる点

なぜ結果が郵送なのか

いつ結果が決まったのかわからんし,時間差が出るのでつらい

一部の人はID発表より早く届いていたので,その人達のツイートを見て,「来てないって事は落ちたんだな」と思っていた.

よくわからないけど,ID発表→必要書類郵送の順番じゃいけない理由があるんだろうか.

期限が伸びた事がわかりづらい

期限が伸びたのなら,もう少し伝わりやすい方法で伝えてほしかった.

もとの期限の間に提出した人に改めてメールで伝えるとかしてくれるとありがたかった.Twitter見てなかったらヤバかったね.

特に自分のゼミの課題は,募集期間中はソースコードを公開してはいけないというルールがあるので,もし期限が伸びた事に気付かないで公開していたらと思うとこわい.

以下,提出した課題をそのまま載せます

課題1

機能

clamd本体がmaliciousだと判断したファイルについて,そのファイルへのアクセスをfanotifyを用いて検知,制限する目的で使用される.

必要性

clamd本体は,定義ファイルに基づいたファイルの検証を行う. しかし,悪意のあるファイルを発見した際に,そのアクセスを防ぐ手法は提供されていなかった.

clamuko

常駐監視の方法として,ファイルシステムイベントの監視.割り込みがfanotifyでできなかった頃.似たような機能を提供するdazukoというカーネルモジュールがあった. clamukoではこれを用い,ファイルへのアクセスを検知している.

実際,旧バージョンのclamavのソースコードを読んでみると, clamd/clamuko.cではdazukoへの登録,設定を主に行っていた.

課題2

課題3に向けて実装していたら少し余計な機能が増えてしまいましたが,そのまま載せます.

依存

[dependencies]
yara = "0.4.1"
clap = "2.33.1"
walkdir = "2.3.1"

src/main.rs

#[macro_use]
extern crate clap;

use std::fs;
use walkdir::WalkDir;
use yara::*;

mod scanner;

fn main() {
    let matches = clap_app!(watchmen =>
        (version:       crate_version!())
        (author:        crate_authors!())
        (about:         crate_description!())
        (@arg quiet: -q --quiet "quiet mode")
        (@arg verbose: -v --verbose "verbose mode")
        (@arg rule:     +required "yara rule file")
        (@arg target:   +required "Scan target file")
    )
    .get_matches();

    let mut compiler = Compiler::new().unwrap();

    compiler
        .add_rules_file(matches.value_of("rule").unwrap())
        .expect("Can't add rules");
    let rules = compiler.compile_rules().expect("Can't compile rules");

    let target = matches.value_of("target").unwrap();

    if fs::metadata(target).expect("Can't get metadata").is_dir() {
        for entry in WalkDir::new(target)
            .follow_links(true)
            .into_iter()
            .filter_map(|e| e.ok())
            .filter(|e| !fs::metadata(e.path()).expect("Can't get metadata").is_dir())
        {
            if !matches.is_present("quiet") {
                println!("scanning ... {}", entry.path().display());
            }
            scanner::scan(
                &rules,
                entry.path().to_str().unwrap().into(),
                matches.is_present("verbose"),
            );
        }
    } else {
        scanner::scan(&rules, target.into(), matches.is_present("verbose"));
    }
}

src/scanner.rs

use yara::*;

pub fn scan(rules: &Rules, path: String, verbose: bool) {
    match rules.scan_file(path.clone(), 5) {
        Ok(v) => {
            for rule in v {
                eprintln!("Rules {} matched! {}", rule.identifier, path);
                if verbose {
                    println!("  matched strings:");
                    for s in &rule.strings {
                        println!("    identifier:\t{}", s.identifier);
                        println!("    matches:");
                        for m in &s.matches {
                            println!("      offset:\t{}", m.offset);
                            println!("      length:\t{}", m.length);
                            println!("      %data:\t{:?}", m.data);
                        }
                    }
                }
            }
        }
        Err(e) => {
            if verbose {
                eprintln!("Error in {} -> {}", path, e);
            }
        }
    }
}
vagrant@ubuntu1804:~/work/watchmen$ cat sample_rule/upx.yara
rule upx {
    meta:
        block = false
        quarantine = false

    strings:
        $mz = "MZ"
        $upx1 = {55505830000000}
        $upx2 = {55505831000000}
        $upx_sig = "UPX!"

    condition:
        $mz at 0 and $upx1 in (0..1024) and $upx2 in (0..1024) and $upx_sig in (0..1024)
}
vagrant@ubuntu1804:~/work/watchmen$ ls sample_file/
helloworld.c  helloworld_elf  helloworld_elf.zip  helloworld_pe.upx
vagrant@ubuntu1804:~/work/watchmen$ ./target/release/watchmen -v sample_rule/upx.yara sample_file/
scanning ... sample_file/helloworld_elf
scanning ... sample_file/helloworld_pe.upx
Rules upx matched! sample_file/helloworld_pe.upx
  matched strings:
    identifier: $mz
    matches:
      offset:   0
      length:   2
      %data:    [77, 90]
    identifier: $upx1
    matches:
      offset:   376
      length:   7
      %data:    [85, 80, 88, 48, 0, 0, 0]
    identifier: $upx2
    matches:
      offset:   416
      length:   7
      %data:    [85, 80, 88, 49, 0, 0, 0]
    identifier: $upx_sig
    matches:
      offset:   501
      length:   4
      %data:    [85, 80, 88, 33]
scanning ... sample_file/helloworld.c
scanning ... sample_file/helloworld_elf.zip
vagrant@ubuntu1804:~/work/watchmen$ find /usr |wc -l
99338
vagrant@ubuntu1804:~/work/watchmen$ time sudo ./target/release/watchmen -q sample_rule/upx.yara /usr

real    0m4.500s
user    0m3.482s
sys     0m1.009s

課題3

並列化は std::thread,常駐化は https://github.com/knsd/daemonize を使って行います.

2回目以降は,https://github.com/notify-rs/notify を用いて,監視対象ディレクトリ下で変更のあったファイルのみスキャンする予定です.

https://github.com/n01e0/watchmen (現在はprivate)にて開発を進める予定です.