運用保証(2/2)
クライアント側の運用保証をほんの少し扱う時間です。 この章のコードをパッケージ化し、現場で「そのまま動く」ようにします。
私たちは、rcli ツールがほぼあらゆる Linux システム上で即座に動作するようにしたいと考えています。
単一の実行可能ファイルをコピーするだけでよいようにします。
セットアップも、OS 固有のパッケージマネージャーでライブラリを取り込むことも不要です。
すべてのユーザーに対して、毎回動作してほしいのです。
このセクションはレッドチームに関係しますか?
関係する可能性があります。 運用保証は、防御側と攻撃側が行う抽象的なゲームと考えることができます。 同様に、ネイティブ実行可能ファイルは異なる目的に役立ちます。
防御: 管理対象のさまざまなホスト(例: 「アセット」)向けの、高性能で信頼性の高いツール。
攻撃: 難読化に適したポータブルなプログラム1。被害者が所有するホスト(例: 「ターゲット」)向け。
自立したバイナリのビルド
静的バイナリは、プログラムとその依存関係をまとめるための実績ある方法です。 これは、実行時に依存関係を見つけてロードするデフォルトである動的リンクの代替手段を提供します。 その機械的な違いを簡単に可視化してみましょう。
動的リンクでは、複数のプロセスが共有依存関係(例: 共有ライブラリ)の同じコピーを使用します。 共有関数は実行時に「解決」されます(呼び出し先のアドレスが決定されます)。 通常、それはプロセスが共有関数を最初に呼び出したときですが、プロセスが最初に「ロード」されたとき(例: プログラムが起動されたとき)である場合もあります2。 共有ライブラリがシステムコール、つまりハードウェアとやり取りするための OS カーネルへのリクエストを行うことは一般的ですが、必須ではありません。 ファイルの読み取りや書き込みにはシステムコールが必要です。
静的リンクは、システムライブラリによって通常提供されるサービスを含め、プログラムに必要なすべての実行可能コードを取り込み、すべてを 1 つのより大きなファイルに組み込みます。 その結果、スタンドアロンのアプリケーションになります。 実行時に何かを解決する必要はありません。 システムコールは必要に応じて直接行われます。
私たちは運用上のトレードオフを行っているのでしょうか?
はい。 防御側にとって、静的リンクはパッチ適用を複雑にします。 通常、OS のパッケージマネージャーはシステムライブラリを最新の状態に保ちます。 また、個々のプログラムは、関連するライブラリの単一の新しいコピーにリンクできます。
静的リンクでは、依存関係を最新の状態に保つために、個々のプログラムをそれぞれ置き換える必要があります。 特定のコンポーネントの集中管理されたコピーを管理する能力を失います。
複数のプロセスが同じ依存関係に依存している場合、静的にリンクされたプロセスはコードの重複を意味することもあり、その結果 RAM 使用量が増える可能性があります。
しかし、静的リンクは移植性に優れており、多くのプログラミング言語ではすぐにサポートされているわけではありません。 では、Rust ではどのように行うのか見てみましょう。
まず、rcli がデフォルトでは動的にリンクされることを確認します。
crypto_tool/rcli ディレクトリから、次を実行します。
cargo build --release
ldd ../target/release/rcli
ldd は、共有ライブラリ依存関係、つまり OS ディストリビューションが通常管理するものを表示する Linux コマンドです。
したがって、2 番目のコマンドは次のような内容を出力します。
linux-vdso.so.1 (0x00007ffc0196f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f09369b9000)
/lib64/ld-linux-x86-64.so.2 (0x00007f0936c8e000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f0936996000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f093697b000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f0936975000)
各行は、rcli ツールが機能するためにファイルシステム上のどこかに存在すると想定している共有オブジェクト(.so ファイル)を表しています。
2 番目の項目(libc.so.6 で始まる行)は C 標準ライブラリです。
この章の導入で述べたように、私たちの rcli フロントエンドコードは libc の一部(例: 動的メモリ割り当て用)にリンクしています。
ただし、私たちの RC4 ライブラリはそうではありません(これは #![no_std] コンポーネントです)。
これらのライブラリの存在に依存しないようにするため、代わりに musl(小さな libc 代替3)を使用する静的バイナリをコンパイルできます。
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl
-
最初のコマンドは新しいコンパイルターゲット4を追加します。これは一般に
{Arch}-{Vendor}-{Sys}-{ABI}形式の「ターゲットトリプル」で指定されます。 -
2 番目のコマンドは以前と同様に
rcliをビルドしますが、今回はターゲットトリプルx86_64-unknown-linux-musl向けにビルドします。
では、今度は musl ターゲットのバイナリに対して、もう一度 ldd を試してみましょう。
ldd ../target/x86_64-unknown-linux-musl/release/rcli
出力は次のようになるはずです。
statically linked
rcli 実行可能ファイルの 2 回目のビルドは、任意の x86_64 Linux システムで「そのまま動く」はずです!
必要なのはバイナリをコピーすることだけです。
デバッグ情報の削除
この実行可能ファイルを配布したい場合、デバッグ情報(ソースコードとの対応付けを可能にするシンボルを含みますが、CLI のエンドユーザーが行う必要はないものです)を削除してサイズを減らすべきです。
ワークスペースの設定ファイル crypto_tool/Cargo.toml に次の release プロファイル設定を追加することで、この情報をバイナリから「削除」できます。
[profile.release]
strip = true
この設定は、フラグ --release(最適化を有効にします)付きでビルドされた任意のターゲットに適用されます。
スタンドアロンの Linux ユーティリティである strip5 を使うこともできましたが、ビルドパイプラインへよりきれいに統合するために cargo を活用しました。
muslの代替
muslを活用することは、やや小さめの静的バイナリをビルドする一般的な方法ですが、muslには癖があります。 特にパフォーマンスに関してです。代わりに、プラットフォームの標準Cランタイム(“CRT”)を静的リンクするには6:
RUSTFLAGS='-C target-feature=+crt-static' cargo build --release --target x86_64-unknown-linux-gnu警告:
muslを使う方法とは異なり、生成されるバイナリは依然としてvdsoのようなものに対して動的にリンクされる場合があります7。lddを使って検証できます。
要点
完全に自立したツールをビルドする方法を実演しました。 私たちのバイナリは、特定のOSおよびISAのほぼあらゆるクライアント上でネイティブに実行されます8。
これでソフトウェア保証の概観は終わりです! 次の章では、Rustそのものを掘り下げます。
-
ld.so。Linuxマニュアル(2022年アクセス)。Linuxでは、この挙動はLD_BIND_NOW環境変数を空でない文字列に設定することで有効化できます。共有関数の解決をロード時に行う利点は、実行時パフォーマンスがわずかに予測しやすくなることです。プロセスのデバッグにも役立つ場合があります。 ↩ -
Platform Support。The Rust Team(2022年アクセス)。 ↩
-
命令セットアーキテクチャ(Instruction Set Architecture)、例: x86_64。 ↩