SecHack365 3rdとして一年間活動しました

以前,このような記事を書きました.

実はこのDepthの開発活動はSecHack365という 人材育成プロジェクト に参加した中で作成した成果物であり,
またその成果物のうちごく一部を簡単に取り上げたもの,になります.
今回はSecHack365という活動がどういうものかをシェアしていけたらな,と思っています.

本記事は以下のようにして構成されています.

SecHack365の活動を振り返る

Sechack365とは

SecHack365について簡単に述べるとするならば,
NICTが主催する通年の人材育成プロジェクト です.

SecHack365の理念・概要については上記公式サイトを参照していただくとして,
まずは私が参加した理由・参加時点のモチベーションからお話したいと思います.

参加の動機

私は学内の IPFactory というサークルに所属しているのですが,
その サークルの先輩 が実際に昨年度(SecHack365'18)参加していました.
先輩から色々お話を伺ったり,公式サイトを色々と調べているうちに

  • トレーナーの方にフィード・バックを頂きながら開発できる
    • システムプログラミングについて詳しいトレーナーの方がいることを期待した
  • 1年間の開発の中で, 沢山の能動的インプット ができるのではないかと考えた
    • 自分にとってハードルの高い課題 を設定し, 知らないことをガンガン勉強できる のではと思った

というモチベーションが湧いてきて,応募することを決意しました.

応募時点でやりたかったこと

私がやりたかったことは 実行バイナリ生成までをサポートするような"コンパイラドライバ"のスクラッチ実装 です.
具体的には,

  • 自作言語 -> x64 assemblyのコンパイラ
    • 自作言語であることにこだわりはあまりなくて,別に既存言語の処理系実装でも良かった
  • x64 assembly -> ELF relocatable object file の変換を担うアセンブラ
  • シンボル解決などを行い単一の ELF executable file を生成するリンカ

が含まれます.

これは応募時点での目標であり,
最終的にはユーザスペースのローダやLLVMパス等多くの機能を追加しました.

ここで述べておきたいのが,
応募時点では コンパイラを作ったことなどほぼなかった ということです.
厳密には RuiさんのCコンパイラブック を少し読んだことがある程度でした.
なので, 漠然と コンパイラって面白そうだよね というお気持ちで応募した感じになります.
この約一年間を通じて コンパイラが大好きな人 になることが出来ました.やったね.

応募したコース

上記公式サイトを見ていただければわかるのですが,
SecHack365は今年度, 5つのコース に分かれており,
自分が応募したいと思うコースに応募する,というようになっていました.

私は以下の理由から 学習駆動コース坂井ゼミ を志望しました.

  • 応募時点で作りたいものが明確に定まっていたが, 開発に対する具体的なイメージが湧いていなかった
    • なので,最初は技術書などでインプットなどを行って行きたかった
  • 成果物以外の勉強もガンガンやっていきたかった
    • 具体的にはLinuxOSのカーネルについての勉強やx64アーキテクチャの勉強など
    • コンパイラ開発に役立つかどうかは別
    • 学習駆動コースの 付加学習 という考え方にとても共感した
  • あの 坂井さん に一番質問しやすいポジションであった
    • システムプログラミングをテーマに開発したい私にとってこれは大事

最終的に達成したこと

基本的にはこちらのスライドを見ていただければと思います.

  • 自作言語からx64アセンブリに変換するようなコンパイラを実装した
    • LLVM IRのパスも存在し,殆ど全ての言語機能をコンパイル出来る
    • 約5k行,(コンパイラ開発の本質に関わるような)ライブラリ未使用
  • x64アセンブリ(Intel記法)から再配置可能オブジェクトファイルを生成するアセンブラ
    • 約1.2k行
  • 単一ファイルのスタティックリンカを実装
    • (ELFバイナリを触るAPIを隔離し,リンクの動作のみを抽出すると)120行程度
    • 大体以下のようなことをやる
      • シンボルテーブル上のエントリに決め打ち+累計オフセットを割り当てる
      • 再配置テーブルを操作し, bind 値を見てシンボルテーブルからアドレスを取得,割当
      • 実際に r_offset から始まる機械語を上書き
      • 単一のセグメント/プログラムヘッダを構築
      • セクションのアライメントとかを調整
  • ユーザ空間のローダを実装
    • とてもシンプルかつ超ミニマム実装
  • checksecを実装
    • ELFバイナリのフラグを見て,セキュリティ機構が有効化された状態でコンパイルされているか見る
    • Depthコンパイラのバイナリを見るのではなく, gccclang によって生成されたバイナリが対象
  • readelf
    • 出力を出来るだけ見やすくするように意識
    • かなり大きなバイナリを食べさせても正しく動く(ニッチな属性値の出力はサポートしていないかも)

各集合回のまとめ

SecHack365では,
集合回 <オンライン期間> 集合回 <オンライン期間> ... というように,
年間のうち何度か集合回が開催され,間はオンラインで開発するスケジューリングになっています.

  • 集合回…トレーナー/トレーニー同士でアウトプットし合ったりコミュニケーションを取り合う場
    • 定期的にアウトプットを 強制 されるので,独学だと勉強スピードが遅くなりやすい人にとってはありがたい
    • 私は一人で勉強し続ける場合でも比較的一定のペースを維持できるので,これは心配していなかった
    • 開発方針を大きく改善できるきっかけが生まれたりするので,ここでのコミュニケーションは非常に大事
  • オンライン期間…トレーナーの方と連絡を取りつつ,各々が開発する時間
    • 集合回で得たアイデア/アドバイスを元に,やりまくるという感じ
    • この期間にどれだけ本気を出したかで, 集合回の密度に大きな差が生まれる

という事で,私はオンライン期間の開発にかなり注力していました.
以下,時系列に沿って各集合回周辺の進捗やモチベーションを紹介していきます.

応募時点~神奈川回

採択されたのが5/2なのですが,
採択された時点で既に開発をやり始めていました.
最初は先程紹介した RuiさんのCコンパイラブック やgccの出力を参考にしながら,
Golang で実装していました.

Depthプロジェクトは何度かソースコードを全削除する,みたいなことをやっています.
現在は全削除を2回程度+構成要素ごとに何度か削除を行ったものが存在します.
Golang実装はそのうち最も初期のものになります.

その時のツイートを見ると懐かしくなりますね.

このときはどのようにアセンブリ言語がアセンブルされるのかがよくわかっていなかったので,
頑張って自作コンパイラが吐いたコードを機械語に変換し,
それを 標準出力に文字列で 出して確認していました.

このときはシステムプログラミングそのものもよく理解していなかったので,
機械語列がどうやって実行できるのか,OSがどうやってメモリに展開しているのか もわかっていませんでした.

自作言語に自動変数を実装したときのスクショです.
コンパイラ自作自体が初だったのですが,初めて実装できたので凄い嬉しかったです.

第一回目の集合回である神奈川では,SecHack365自体のガイダンスや顔合わせ等を行いました.
かくいう私はこの集合回でも 自分の実装ばかり やっていましたね.
もっとコミュニケーションを取っていればよかったと,反省しています.

北海道回まで

北海道回までは Golang実装を全てRustに置き換える みたいなことをやっていました.
理由としては以下の通りで,

  • Rustをやってみたかった
  • Version1の実装が単純に良くなかった
    • 自動変数のスタックアロケートをパース時にやっていたり

等ですね.
結果,神奈川回でのGolang実装が実現していた言語機能とほぼ同等のものがRustで実装できました.

福岡回まで

ここでは, Rust実装(Version2)も"全削除"しました .
なんでそんな事をやったのかについてなんですが,

こちらの記事に示した, セキュリティ・キャンプ の経験が大きく関係しています.

私はseccamp2019でCコンパイラゼミに参加したのですが,
ここで講師の方から インクリメンタルに開発する事の重要性 を学びました.
私が身につけたプログラミングに関する技術で最も重要なものだったと思います.

小さい規模からコードを書き始め,必要になったらときに初めて追加する.
例えばCコンパイラなら最初から字句解析器を書きまくるようなことをしないで,
加算がコンパイルできる最も小さいコード みたいなのをできるようにする.
少しずつ,少しずつ規模を大きくしていくような感じです.

それ以降のプログラミングにおいても,seccamp2019での経験は生きていると思います.

Hikaliumさん,Ruiさん,本当にありがとうございました.

宮城回まで

この間で, 実際に 実行プログラムを生成する ことが出来ました.
これが9/21時点の話になります.
SecHack365に採択されてから4ヶ月とちょっとぐらいでしょうか.

最初は 人のコンパイラのコードを真似する ことしか出来なかった私が,
4ヶ月間コンパイラについて調べ,実践し,失敗しては削除しを繰り返す事で
なんとかgcc等の既存ツールに頼らない実行バイナリ生成を達成できました.

アセンブラ実装中,機械語とオブジェクトファイルの概形を生成できるようにした様子です.
まだ.textセクションしか存在しないので,GNU ldに通したりすることは出来ません.

実際に自作コンパイラ,自作アセンブラが協調動作して,
GNU ldがリンク可能な(実行バイナリ生成に必要な最小構成の)オブジェクトファイルを生成できた様子です.

x64の機械語/ELFについてそれまで身につけてきた知識が報われた気がして,
とても嬉しくなりました.

ここからはリンカ実装の為に,
バイナリに埋め込む情報を追加したりしています.

9/21,実行プログラムが生成出来た瞬間です.
ここまででやっと SecHack365応募時の大きな目標の一つ が達成されたことになります.

再度コンパイラのバックエンド部を全削除して,
IRを3番地コードで実装している様子です.

それまでは独自の適当に考えたIRを用いていたんですが,
ドラゴンブックを読みながら3番地コードに変換していたら意外とうまくいきました.

CFGを構築して,DOT言語を吐き出しているようすです.
DOT言語を吐き出す部分も勿論スクラッチでやっています.

結果的にこんぐらいの変換になりました.

愛媛回まで

ここからは スクラッチな実行プログラム生成基盤を使ってなにか出来ないかな…? というのを考えていました.
アイデアを色々考えていたので実装はあまり出来ていませんね….

やったこととしては,
実行プログラム生成基盤に LLVM IRを吐くパスを追加 していました.

自動変数のサポートです.
勿論IR生成にLLVMのRustバインディング等は用いていません.
LLVMの構成要素を全て定義して,スクラッチでIRBuiderみたいなのを作っていました.

静的なサイズの配列を実装している様子です.
これ,結構大変だったんですよねー.

沖縄回まで

主に以下のものを実装しました.

  • GNU readelfとほぼ同等の機能を持つ解析ツールの実装
  • checksec( Canaryが存在するかとか等. 実行バイナリのセキュリティ)
  • 独自セクション の実装

readelfっぽいものを作っている様子です.

自作のデバッグセクションみたいなのを実装して,
自作readelfを用いると 自作言語にある型・シンボル情報 を取得できているサンプルです.

独自セクションその2です.
関数定義時にコメントを付属すると,
バイナリに自然言語のコメントを埋め込めて,
自作readelfを用いる事で解析できるようになっています.

全体を通して

私が個人的にSecHack365で学んだことは,以下の3つにまとめられるかな,と思います.

  • システムプログラミングの考え方,重い実装に対するモチベーション
  • 実際の知識(特にOSがバイナリを実行するまでの流れ,x64機械語,ELF)
  • 「凄い事をやる」という漠然とした表現を実現する方法のいくつか

付加学習・SecHack365以外の活動

SecHack365に参加してきたこの一年間ですが,
実はSecHack365に費やして来た時間というのはそこまで多くないんですよね.

私は SecHack365の成果物以外にもいろいろな勉強をやって,
それが結局SecHack365の為になることもあるかな,という期待から.

自作OS

個人的にちょっとずつ頑張っていこうと思っている自作OSです.
これは愛媛回から沖縄回までの期間にやっていました.

[https://twitter.com/drumato/status/1211575163499606021?s=20:embed]

SDKを使わずにUEFIアプリケーションでブートローダを記述し,
OSカーネル(raw binary)をメモリ上に載っけて制御を移しています.

カーネルではシリアルポートの初期化とバイト列の送信を行っていて,
QEMUの -serial stdio によって表示している,みたいな感じです.

川合さんの緑本を読んだことはあったんですがあれはLegacy BIOSを用いたブートだし,
あれは写経していただけなので,自分で書いたことはないです.
具体的には何人かの実装を参考にしたんですが.

自作OSについてはまだまだ勉強不足なので,少しずつやっていきたいと思っています.

c–

Cコンパイラドライバの実装もはじめました.
1/1から作り始めたので,これも愛媛-沖縄間にやりはじめました.

Depthプロジェクトにはバイナリ生成まで1ヶ月かかっていますが,
その知識を生かして 10日程度で実行可能なバイナリを生成できた のが嬉しいですね.

[https://twitter.com/drumato/status/1215876625947971585?s=20:embed]

野良のCプログラムを投げてもうまく動くぐらいに育てていきたいと思います.

まとめ

今回SecHack365'19に参加する事で,

  • コンパイラを作ったことがない状態から,実行プログラム基盤を作成できるようになった.
  • 活動をトリガーに多くの能動的学習を行うことが出来た

等の恩恵が得られました.
これからの活動をもっといいものにできるよう,頑張っていきたいと思います.