💀

covert channel: キャッシュヒット/ミスを利用してこっそり情報伝達しちゃうぞ〜!👶

commits10 min read

この記事はセキュリティ Advent Calendar 2021の18日目の記事だZE!

https://qiita.com/advent-calendar/2021/security

本当はハイパースレッディング環境下におけるキャッシュタイミング攻撃でOpenSSLの鍵を特定するとかいうオモロ論文の解説を書くつもりでしたが、なかなか難しく、断念してしまいました……😭
↓がその論文。ちょっと古めだけどネ

"cache missing for fun and profit" https://www.daemonology.net/papers/htt.pdf

ただ、そうは言っても途中まで読んだ部分について、日本語の記事がなさそうだったから書いちゃいます!!🎉🎉

この記事を読むとわかること

  • 異なるプロセス同士が管理者の目をすり抜けて情報を共有するための手法の1つである"covert channel"というものの定義や例
  • キャッシュメモリを共有するアプリケーション間で構築できる、キャッシュヒット/ミスを利用したcovert channelの原理

いやまずcovert channelってなんすか!?

covert channelの定義おしえて!?

突然ですが、あなたは子どもと一緒に図書館にきています。(よくある話ですね😊)
その子はとても寂しがり屋であなたがいないとすぐ不安になります。なので、あなたは自分が一瞬トイレにいくことをどうしてもいま、その子に伝えなければいけません。(まあよくある話ですね😊)
ただし、そこは非常に厳格な図書館なので、喋った瞬間にイカゲームの機械で射殺されてしまうとしましょう。(これはあまりないかな?👶)
また、喋ることに留まらず、人間同士のあらゆる情報伝達が禁止されているとします。(あまりないシチュエーションですね👶)
その状況の中、あなたと子どもは普段使っている(理由は不明)モールス信号を用いて、下記のようにコミュニケーションすることで監視を回避しました。

  1. あなたは机に本を並べる。そのとき、ある本は開き、ある本は閉じて置いていく
  2. 調べ物をしているだけに見えるが、開かれた本をツー(ー)、閉じられた本をトン(・)と解釈することでモールス信号による解読ができる
    「・・-・・ ・- --- ・- ・・・- --・-」=「といれいくね」

さっさと図書館を出てコンビニのトイレに行けという意見もありますが、この話で重要なのはそこではありません。
ここで大事なのは、それ自体はコミュニケーションの道具ではないはずの本を、モールス信号という共通のコードを介して、情報伝達のチャネルとして利用したことです。
このように「情報を伝えるためのチャネルとして想定されていないものを用いて、こっそり構築された情報伝送の通路」を"covert channel"と呼びます。
covertとはcoverされている、隠されているというような意味です。本来は情報伝達に使えなさそうなものを使う以上、そりゃ気づかれないわけです。先ほどの本を使った情報伝送でも、並べられた本が情報を伝達しているという事実にあなたと子ども以外の第三者は気づきづらいですよね👾

定義だけじゃピンと来ないから例を出してくれよ!?

例も欲しがっちゃうなんて、欲しがりさんですね〜〜👶
私は軽く調べる中で下記を目にしましたよ(実際はもっと色々あるんだと思います)

  • ファイルを読み込む際の時間のかかり方でメッセージを伝える
  • ファイルサイズの大きさでメッセージを伝える
  • パケットの遅延度合いでメッセージを伝える

先ほどの例のように2種類の異なる情報を発信でき、かつモールス信号でもなんでもいいので何かしらのコードを互いが共有できていれば、理論的にはあらゆる情報が伝えられるはずです(なんか情報科学でもそんな話出てきますよね)。
つまり時間のかかり方であれファイルサイズであれ、遅い/早いもしくは小さい/大きいといった2種類の情報を作り出せるわけなので、covert channelを構築することが理論的にはできるはずです👶

ちなみにcovert channelとして代表的なのはストレージを使う方法タイミング(時間)を操作する方法の2つだそうです。TCSEC(米国防省のセキュリティスタンダード)とかいう偉い何かがそう言っているみたいです🍖

The TCSEC defines two kinds of covert channels:
-- Storage channels - Communicate by modifying a "storage location", such as a hard drive.
-- Timing channels - Perform operations that affect the "real response time observed" by the receiver.
https://en.wikipedia.org/wiki/Covert_channel#TCSEC_criteria より引用

covert channelの何が面白いの!?

covert channelは非常に見つけづらい!面白さはここに尽きるでしょう🏠
例えばwikipediaでも言われているように、プログラムがファイルを開いたり閉じたりすることで信号を送るようなtiming covert channelを作られたとして、なかなか気づかないわけです。
考えてみると、covert channelを常に監視し見つけ出そうとすることは原理的に難しいはずです。なぜならコミュニケーションしたいエージェント同士がモールス信号なり文字コードなり、共通のコードを持ってさえいれば、理論上はどんな類の活動でもcovert channelにできうるからです。さっきも話したように、2種類の情報さえ作ることができればいいんですからね🐧

(wikipediaから、deepL翻訳そのままコピペと原文)
慎重な設計と分析によって大幅に削減することはできますが、秘密チャンネルの可能性を排除することはできません
正規のユーザが制御したり調べたりすることのない通信媒体の特性を正規のチャネルに利用することで、秘密チャネルの検出をより困難にすることができます。例えば、あるプログラムが特定の時間パターンでファイルを開いたり閉じたりすると、別のプログラムがそれを検出し、そのパターンをビット列として解釈して秘密チャネルを形成することができます。正規のユーザーがファイルの開閉操作のパターンをチェックすることはまずないため、この種の秘密チャネルは長期間にわたって検出されないままとなる可能性がある
同様のケースにポートノッキングがある。通常の通信では、リクエストのタイミングは無関係であり、ウォッチされることはない。ポートノッキングでは、それが重要な意味を持ちます。
The possibility of covert channels cannot be eliminated, although it can be significantly reduced by careful design and analysis.
The detection of a covert channel can be made more difficult by using characteristics of the communications medium for the legitimate channel that are never controlled or examined by legitimate users. For example, a file can be opened and closed by a program in a specific, timed pattern that can be detected by another program, and the pattern can be interpreted as a string of bits, forming a covert channel. Since it is unlikely that legitimate users will check for patterns of file opening and closing operations, this type of covert channel can remain undetected for long periods.
A similar case is port knocking. In usual communications the timing of requests is irrelevant and unwatched. Port knocking makes it significant.
https://en.wikipedia.org/wiki/Covert_channel#Eliminating_covert_channels

全然イメージわかないんで実際の仕組みが知りたいんですけど……汗

ですよね!?私もこんな説明じゃ全然イメージできないと思います👶(分かる人はもう帰って頂いて大丈夫ですよ)
そこで、ここからは実際のcovert channelの仕組みを2種類紹介したいと思います。 2種類とはいえ、いずれもキャッシュメモリ、もっと言えばキャッシュヒットとキャッシュミスを利用してファイルの読み込み時間の長短を操作するものです。
ちなむと、ここでの説明は冒頭でも紹介した下記論文に基づいています。こちらの論文ではcovert channelをハイパースレッディングCPUのL1/L2キャッシュを利用して構築し、openSSLのキーを解読するとかいう謎のムーブが繰り広げられています。ぜひ興味のある人は読んでください!(わからなかった私向けに解説記事書いてください😭)

"cache missing for fun and profit" https://www.daemonology.net/papers/htt.pdf

ではここからさっそく、キャッシュヒットとキャッシュミスを利用するcovert channelの仕組みをみていきましょう!
と、その前にキャッシュミスとはなにかという話をするぜ。

そもそもキャッシュヒットとかキャッシュミスってなんやねん

ここから一旦キャッシュというもの自体の説明をはさむよ。知ってる人はとばしてね

さてさておれたちが今使ってるおパソの中には、データを溜め込んでおく場所ってのが妙にたくさんある!!そんで、それらはアクセス速度(単位時間で読み込める情報量)や価格(ここでは単位容量あたり価格)などの要素でキレイに序列を作っているわけだ(下図)

コンピューターにおける記憶媒体の階層構造
(2018年 サイボウズ技術顧問 武内覚 「[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識」 第6章より引用)

ストレージデバイスつまりHDDはめちゃくちゃ読み込みに時間がかかる代物で、そんなんいちいち使ってたらプログラムで掛け算しただけで日が暮れちまうかもしれねえ。だから、もっと速い(その代わり容量は小さい)記憶容量を使わないといけないわけだ。  
そこで、プログラムは自分が使うことになるデータをストレージデバイスからメモリという場所に置く。それからプロセッサとかいう働き者が処理できるようレジスタってところ(プロセッサのためのメモ置き場的な感じ)にデータが入っていくのよね
「は?じゃあキャッシュはどこに出てくんの😡😡」ってなると思うけど、キャッシュメモリってのはめちゃかんたんに言えばレジスタで一回使ったけどあとでまた使いそうなデータってのを置いておいて、処理速度を上げるためのものなわけ。さっき、ストレージデバイスだけじゃ遅すぎるからメモリに引っ張ってくるって話したけど、それでも遅いからもう一層、キャッシュというレイヤーを置くってワケ。

ここまでがキャッシュの説明。とばした人はここから読むの再開してね

ほんで、プロセッサがキャッシュを見たときにそこに欲しい情報がある場合をキャッシュヒットと呼んで、反対にキャッシュに欲しい情報がない場合をキャッシュミスと呼ぶわけ。

キャッシュミスとヒットのわかりやすい図
(Fujitsuのサイトより引用 https://www.fujitsu.com/jp/products/computing/servers/unix/term/cachehit/)

キャッシュヒット/ミスのアクセス時間の違い(これめちゃ大事!)

これめちゃくちゃ大事なんだけど、キャッシュヒットすれば短時間でデータをレジスタに引ける一方で、キャッシュミスの場合はメインメモリまでいってくるから時間がかかる!
ちなみに、その間はプロセッサーは待ち状態になって誰もいいことないです😭

「キャッシュミスによりCPUがメインメモリにアクセスすると、データがレジスタに乗るまでに数百クロックの間待たなければならない(ストール)。」
https://ja.wikipedia.org/wiki/ハイパースレッディング・テクノロジー

キャッシュミスとキャッシュヒットとでどんくらい時間が変わるかってのが気になるよね。たぶん20~100倍くらい違うのよ(迫真)
というのも、例えば下記の記事ではメモリごとのアクセス時間がまとめられているんだけど、L1キャッシュでは1~4ナノ秒であるのに対して、メインメモリは100ナノ秒ってことらしい。これは相当違うでしょ。で、この違いが今から紹介するcovert channelの仕組みでポイントになるのよ。

https://qiita.com/zacky1972/items/e0faf71aa0469141dede

さて、こんな感じでキャシュミスとキャッシュヒットがわかったところで、ようやくcovert channelの説明に入るぜ!

キャッシュヒット/ミスを利用したcovert channelの仕組み!!

ここからがようやくこの記事の本題だ!!「ウオーーーッ❗」(フロアが沸く音)
キャッシュヒットとキャッシュミスを利用したcovert channelの原理をみていってみよう。ここでもう一度covert channelの意味をおさらいすると、「情報を伝えるためのチャネルとして想定されていないものを用いて、こっそり構築された情報伝送の通路」のことだったヨネ。
要するに、いまから考えるのは「2つのアプリケーションがキャッシュヒットとキャッシュミスを利用して、こっそり情報を伝える方法」なわけだ。

ここで一つ大切な前提として、いまから話すことは2つのアプリケーションがキャッシュメモリを共有する場合の話だ。
こんな状況あるのかって?例えば、ハイパースレッディングだよ❗❗ハイパースレッディングってのは、要は1つのプロセッサーコアに複数のスレッドを載せることで、複数のコードの処理を1つのコアで同時にやれちゃうってやつね(厳密には同時ってより、片方が論理回路を使ってないときにもう片方が使うって感じのはずだけど)。このときキャッシュはスレッド間で共有される

おれたちが今から考えるのは、こんな風に2つのアプリケーションがキャッシュメモリを共有する場合に、キャッシュヒットとキャッシュミスを利用してこっそり情報を伝える方法なわけだ。パパっと話しちまいたいところだが、2つのアプリケーションが同じファイルの読み込み権限を持っている場合と持っていない場合とで話が変わってくるから分けて見ていこう!

A: into-memory戦略(2つのアプリケーションが同じファイルの読み込み権限を持っている場合)

まずは2つのアプリケーションが同じファイルの読み込み権限を持っている場合だ。ちなみに"into-memory"戦略って名前はおれが勝手に付けた名前だから気にしないでくれ。あとでそう呼ぶ理由をちゃんと説明するから!👶
さて、covert channelを用いて情報を送るアプリケーションを送りアプリ、受け取るアプリケーションを受け取りアプリとここでは呼ぶとして、この戦略は下記の通りだ。

  1. 送りアプリは受け取りアプリに伝えたいメッセージを[1,0]の文字列に変換する。これは一般的な文字コードを使うでもいいし、モールス信号を使うでもいい。とにかく2種類の信号で表すことが大事だ
  2. 送りアプリは受け取りアプリも読み込むことができるファイル("file.txt"としよう)を、伝えたい信号の長さと等しい数に区切り、各部分について、対応する信号が1なら読み込み、0なら読み込まないってことをする。例えば信号が10110(長さ5)だったとすると、ファイルを5つに分けたときに1、3、4番目になる部分だけを読み込む。2、5番目は読み込まない。
    →このとき、送りアプリが用いているキャッシュに1、3、4番目だけが保持される
  3. 2が終わり次第、受け取りアプリはfile.txtをすべて読み込む。このとき、信号の長さと等しい数にfile.txtを区切って、各部分の読み込みにかかった時間を計測する
    →このとき、受け取りアプリのキャッシュは送りアプリと共有されているため、1、3、4番目はキャッシュに存在してすぐに読み込まれる(=キャッシュヒット!)のに対して、2、5番目はキャッシュにないので読み込みには時間がかかる(=キャッシュミス!)
  4. 受け取りアプリはfile.txtの各部分について、読み取り時間が短かった部分を1、長かった部分を0に対応させることで信号を復元する

    上記手順の図解(時間なかったので手描きだけど許して!!!!👶👶)

さっきも言ったようにL1キャッシュとメインメモリとではアクセス時間が20~100倍は違うから、受け取りアプリが所要時間を計測して信号を判別するのは難しくないはず。
というわけでこれが1種類目のcovert channelね。キャッシュメモリにファイルを読み込んでchannelを作るから、into-memory戦略🐶

B: out-of-memory戦略(2つのアプリケーションが同じファイルの読み込み権限を持っていない場合)

次は2つのアプリケーションが同じファイルの読み込み権限を持っていない場合。名前の通り、キャッシュメモリからキャッシュを追い出してchannelを構築するよ。何言ってんだって思うかもしれないけど、下記のような感じ。

  1. 送りアプリは受け取りアプリに伝えたいメッセージを[1,0]の文字列に変換する。これは一般的な文字コードを使うでもいいし、モールス信号を使うでもいい。とにかく2種類の信号で表すことが大事だ
  2. 送りアプリと受け取りアプリのそれぞれが読み込むファイルを用意する(これらファイルの中身をやりとりしたいわけではない)。このとき、ファイルのサイズについてそれぞれはキャッシュ容量に収まり、合計はキャッシュ容量を超えるようにする
  3. 受け取りアプリは、自分のファイルの全体を読み取るという操作を一定周期で繰り返して行うようにする。そのとき、読み取りにかかる時間を毎回計測する
  4. 送りアプリは受け取りアプリの読み込み周期に合わせて、信号の1を送りたいときに自身のファイルを全部一括で読み込む。信号の0を送りたいときには自身のファイルを読み込まず、何もしない!
    →送りアプリと受け取りアプリの各ファイルのサイズ合計はキャッシュ容量を超えているため、送りアプリが自身のファイルを読み込んでキャッシュメモリにぶちこむとき、受け取りアプリのファイルキャッシュの一部はキャッシュから必ず追い出される。そのとき、受け取りアプリのファイル読み取りは平均より時間がかかることになる!
  5. 受け取りアプリは読み取り時間が長いトライアルを1、短いトライアルを0に対応させることで信号を受信できる

    上記手順の図解(時間なかったので手描きだけど許して!!!!👶👶)

こりゃまた面白い方法です。同じファイルを介さなくても、キャッシュメモリさえ共有していればこのようにしてcovert channelを構成できるわけです。すごいねぇ!?!?👾

おわりに言い残したことはあるかぁ!?

さてと、ここでcovert channelの仕組みはおしまいだ!
今回紹介したキャッシュヒット/ミスを利用したcovert channelの構成はなかなか面白い一方、割と限られた条件下でしか発動できなさそうではありますヨネ😭実際どうなんでしょう?
covert channelはトピックとしては地味なのか、記事が日本語だとあんまりなさそうだったので今回の記事がちょっとは役に立てたら嬉しいです!
なお、内容は基本的に下記論文によっているから気になる人は読んでみてね。

"cache missing for fun and profit" https://www.daemonology.net/papers/htt.pdf

みんなもcovert channelに興味持って色々わかったら、色々教えてくれよな!!!!!
じゃあね〜✌

GitHubで編集を提案

Discussion

ログインするとコメントできます