乱数が予測できるバーナム暗号は脆弱
前回、ファイルの暗号化に「バーナム暗号に似た、XORを使った暗号化」を利用していることを解析し、鍵となる乱数部分を計算できる「シードとなる64バイトのデータ」について内容を分析するのですが。
その前に、「なぜこのような方法をとるのか?」ということをちょっとだけ説明します。
もっとも、暗号に詳しい人なら自明の理なので、読み飛ばしちゃってください。
バーナム暗号とは、平文に対し「予測不可能な乱数」とXORを取ることで、暗号解読を困難にする方法です。
開発された当初は、その乱数を送信側、受信側でそれぞれ同じものを物理的に別送しておき、暗号通信の際に使用していたそうです。
ここで、バーナム暗号では「予測不可能な乱数」を用いることは必要条件なのです。
返して言えば、仮にこのプログラムの作成者がバーナム暗号のつもりで実装していたのならば、「脆弱なバーナム暗号の実装」ということになります。
このため、「この脆弱性を利用し、暗号化されたファイルを復号化できるのではないか?」と考えたことが本記事のそもそもの発端です。
本記事を書いている現在、実際のところまだ分かっていないパラメータがあります。
しかし、こういった問題点を発見すると、教訓も得られると思います。
「解析者」という立場でみれば、解析する時には、単純に命令の意味が分かるだけではダメで、OSの機能、仕組みだったり、このような暗号の知識といった「周辺の知識」を十分に身に着けておかなければ、正しい解析はできない、ということです。
もし、私がバーナム暗号に関する知識がなければ、脆弱性に気づかず、復号化できる可能性を見落としていたかもしれません。
「開発者」という立場でみれば、無知による脆弱性の作り込みが、どれほど危険なものか、ということに気づくケースではないかと思います。
これが、仮に真っ当なデータ暗号化ツールだった場合、ユーザが折角暗号化してデータを守ろうとしても、その暗号が破られてしまうことを意味します。
これでは、「暗号化によってデータを守る」というツールの根底が覆されてしまいます。
このケースでは暗号化ロジックの実装不備ですが、他にも通信プロトコルやデータ処理での判定・考慮漏れなど、脆弱性を作りこむ落とし穴は山ほどあり、現実にそれが今のサイバー犯罪被害に繋がっています。
「これからのIT技術者は、どの分野であっても、その分野での『専門家』と胸を張れるだけの知識と、その知識の内容が理解できるようにならなければならない。」
今回は、そんな感想を持った脆弱性の発見でもあります。
ITの技術者も、ITの技術者を雇う人も、そういった点に価値を見出してほしいものと思います。
「シードとなる64バイトのデータ」のレイアウト
さて、柄にもなく、「マルウェアの暗号化の脆弱性を突いて復号化してみせるぜ!」なんて大風呂敷を広げた訳ですが。
「シードとなる64バイトのデータ」があれば復号できることは前回記事で分かっているものの、このデータを復元できなければ意味がありません。
メモリ等に残存しているデータを「サイドチャネル攻撃」で狙ってみる、等のケースもあるかもしれませんが、今回はこのデータのレイアウトを分析し、このデータを自力で再構成できないか、を探ってみようかと思います。
解析した結果、「シードとなる64バイトのデータ」のレイアウトは以下の通りでした。
ピンク枠:「expand 32-byte k」を4バイトずつ分割して格納
赤枠:謎の値「Z」を前半、後半の16バイトずつ2分割して格納
オレンジ枠:暗号化されたファイルに格納されている値
青枠:カウンター
ピンク枠の「expand 32-byte k」は、もうただの固定の文字列です。
一応条件判定があり、条件によっては「expand 16-byte k」になる場合もあります。
「expand 16-byte k」になるケースでは、赤枠のデータは前半、後半とも同じ値が入るようにプログラムされています。
これは推測ですが、このマルウェアの赤枠の謎の値「Z」の桁数は16バイトと32バイトの2モードがあり、REvilのランサムウェアの実行形式ファイルを作成するときに選択できるような仕組みになっているのではないか、と考えます。
赤枠の謎の値「Z」は、今後の解析で更に追及する必要がある値となります。
オレンジ枠の「暗号化されたファイルに格納されている値」は、ランサムウェア「REvil」のファイルは復号化できるのか?(まえがき)で示した「暗号化されたデータのレイアウト」の内容のうち、紫枠の「「シードとなる64バイト」に含まれるランダムな値」と合致することが、解析および検証で確認されました。
このため、「ファイルを復号化」する場合は、素直にこの値を参照すればよいでしょう。
青枠のカウンターは、ランサムウェア「REvil」のファイルは復号化できるのか?(その1)で触れた0x00407EAD関数を実行後、1ずつ加算することで、計算結果が毎回変わるようにするための値です。
「ファイルを復号化」する場合、ファイルを暗号化するときには初期値1で始まっていることを把握していれば十分です。
(データを作った際には0スタートなのですが、ファイルを暗号化する前に0x00000000(4バイト)を暗号化する処理が入っており、その結果カウンターが1加算されているため、ファイル暗号化/復号化では初期値1となります。4バイトの0を暗号化した結果はファイルの末尾の緑枠に格納されていることから、チェックサム目的ではないかと推測します。)
前回記事で、「『シードとなる64バイトのデータ』が分かればファイルの復号化は可能」ということが分かりましたが、「シードとなる64バイトのデータ」のうち、32バイトの謎の値「Z」以外は既知の値またはファイルから取得できることが分かりました。
「シードとなる64バイトのデータ」を作成している処理
ここは、当該検体を検証される人への参考情報になります。
(そんな酔狂な人はいるんだろうか・・・?自分用メモとしても、もっとエグイくらいの細かい資料が手元にあるし?)
「シードとなる64バイトのデータ」を作成しているのは、0x00403070関数の中の処理です。
この関数内でも、「シードとなる64バイトのデータ」のデータ領域に直接パラメータを設定しているのは、0x00407E3E関数と0x00407E20関数です。
0x00407E3E関数はピンク枠と赤枠の値を設定しています。
0x00407E20関数はオレンジ枠の値を設定しています。
また、設定されるパラメータのうち、謎の値「Z」は0x004067FB関数、暗号化されたファイルに格納されている値は0x00406764関数で計算されます。
ファイルに格納されている値はファイルから読み込んでしまえばいいので、特に解析しなくても復号化はできるでしょう。
問題となるのは、「謎の値『Z』」の計算であり、これを次回以降の記事で追跡しようと思っています。
ここまでで分かったことの小まとめと感想
ここまでの解析の結果、「謎の値『Z』を求めれば、ファイルの情報と合わせて「シードとなる64バイトのデータ」を求めることができ、そのデータを特定の関数で計算することでファイルを復号化する鍵が得られ、その鍵でXORすることでファイルが復号できる。」ということがわかりました。
図にするとこんな感じですかね。
もし、これが「バーナム暗号」であったならば、鍵の長さはファイルのデータサイズと同じであり、それが強度になります。
しかし、「バーナム暗号」であれば乱数であるはずの鍵が、「シードとなる64バイトのデータ」による計算で求められることが解析で判明しました。
もし、バーナム暗号と同じ強度のつもりでこの方式を作っていたのであれば、不十分な知識で脆弱な実装をしてしまったことになります。
この実装の解析結果から、復号のためのハードルである鍵は、ファイルのデータサイズから暗号鍵の強度が32バイトまで低下した、と考えることもできます。
もっとも、32バイトの鍵というのは、比較的軽い計算処理とはいえ総当たり攻撃をするにしてはまだ十分長いと考えられます。
次回以降では、この「謎の値『Z』」をどのようにして求めているかを解析し、復号化方法を追ってみたいと思います。