謎の値「Z」の計算処理の追跡
前回記事で、「謎の値『Z』を解明できれば、現在解析しているランサムウェア(REvilらしい)によって暗号化されたファイルを復号化できる!」というところまでが分かりました。
では、今回は「謎の値『Z』はどこから得ているのか?」ということを追跡したいと思います。
当該コードは以下のようになっていました。
解析の結果、0x00403070関数内の「シードとなる64バイトのデータ」を作成する関数の直前の0x004067FB関数で計算され、第3引数の領域に出力されていました。
そして、この関数では、第1引数(謎の値「A」)、第2引数(謎の値「B」)の2つのパラメータを元に計算していることが分かります。
(コードを追っていくと、この計算式を移植するのは面倒そうではあるけど、時間をかければ可能なレベル。)
しかし、ここで引数となる謎の値「A」、謎の値「B」と、値が増えてしまいました。
しかも、結論からいうと、それぞれ32バイトの値です。
調査しなければならない値が64バイトまで増えてしまいました・・・。
その時のコード実行時のパラメータをトレースしてみました。
謎の値「Z」を算出する0x004067FB関数のパラメータのトレース
0x004067FB関数の前後のパラメータを確認してみます。
関数のコール時のコードから、引数は3つあることが分かります。
引数はスタックに積まれています。
数値や関数内部での処理方法から、これらは全てデータの格納されているメモリのアドレスを示していることが分かります。
計算元になる第1引数、第2引数の値は以下のとおりです。
第1引数(0x0019FB08:謎の値「A」)
第2引数(0x00411E00:謎の値「B」)
この条件で0x004067FB関数を実行した結果、第3引数の領域には以下のようなデータが出力されていました。
第3引数(0x0019FAE8:謎の値「Z」)
この結果は、前回記事の「シードとなる64バイトのデータ」に出力されている謎の値「Z」と一致することが確認できます。
よって、第1引数の謎の値「A」、第2引数の謎の値「B」から0x004067FB関数を使うことで謎の値「Z」が求まることが確認できました。
謎の値「B」の正体
謎の値が2つに増えてしまったのですが、そのうち謎の値「B」は、実は既に我々が知っている値でした。
謎の値「B」を再掲します。
そして、このシリーズの最初の記事に掲載した、レジストリの値を再掲します。
これで気づかれたでしょうが、謎の値「B」は、レジストリに格納されている「WbTs」の値とまったく同じなのです。
実は、記事には記載していませんでしたが、この謎の値「B」は、レジストリに出力されている値であることは、解析で既に判明していたのです。
謎の値「Z」の算出のために2パラメータが必要になって解決が遠のいたように見えましたが、謎の値「B」はレジストリから取得できるので、謎の値「A」が分かれば謎の値「Z」を求めることができ、前回記事で説明した方法でファイルが復元できるようになる、ということが分かりました。
謎の値「A」の追跡
では、この謎の値「A」はどこから来たか?ということです。
結論は、既にこのページの最初の画像に書かれていますが、0x004064C9関数によって取得されます。
この関数のアウトプットのうち、第1引数のアドレスの領域にその値が出力されています。
また、第2引数のアドレスの領域に、謎の値「X」が出力されています。
(また値が増えた・・・だと・・・)
0x004064C9関数の内部を調べてみると、0x0040651C関数と0x004064ED関数をコールしていることが分かります。
解析の結果、0x0040651C関数ではスタティックな領域に格納されたランダムな数字を含むパラメータから、謎の値「A」を計算していることが分かりました。
また、0x004064ED関数では謎の値「A」から謎の値「X」を計算していることが分かりました。
そして、これらの結果を0x004064C9関数の返り値としていたのです。
0x004064C9関数の処理結果として得られた謎の値「A」、「X」は以下のとおりです。
謎の値「A」
謎の値「X」
当然のことながら、謎の値「A」は謎の値「Z」を計算したときと同じ値です。
そして、謎の値「X」は謎の値「A」に対し、0x004064ED関数で計算をした結果として出力されたものです。
またしても謎の値が増えてしまったのでしょうか?
しかし、この謎の値「X」は既知の値なのです。
最初の記事で、ファイルのレイアウトを示しましたが、赤枠で示した「暗号の鍵に使われる値『A』から算出される値『X』」と一致するのです。
つまり、謎の値「X」は、暗号化されたファイルから取得することが可能な値なのです。
謎の値「A」はどのように求めることができるか?
前回までの記事で、謎の値「Z」が求まれば、ファイルが復号化できることが判明していました。
そして、今回はその謎の値「Z」を求めるために、謎の値「A」が分かれば良いことがわかりました。
では、この謎の値「A」は、どのようにして求めることができるのでしょうか?
考えられる方法として、以下の2つを挙げます。
- 謎の値「A」から計算して得られる謎の値「X」から逆算する。
- 謎の値「A」の生成処理を分析し、推測可能かどうかを調べ、推測可能であればその方法を探す。
謎の値「X」からの逆算は、「数学的に解く」方法で、いわば正攻法となります。
ただし、今のところ謎の値「A」から「X」へ変換している計算方式が不明であり、したがって逆算が可能かどうかも不明です。
謎の値「A」の生成処理から推測可能かどうかを調べ、推測可能であればそれを利用するのは、「脆弱性を利用」するというサイドチャネル攻撃的な方法となります。
どちらの方法が良い、ということはなく、実現できればどちらでも良いと思います。
何故なら、最終目的はあくまで「ファイルの復号化」であり、謎の値「A」の取得方法は手段でしかないためです。
個人的には、今のところ謎の値「X」から謎の値「A」を求める方法が確実性がより高いと思います。
検証はできていないものの、謎の値「A」から得られる謎の値「X」は一意で、それの逆が成り立つから、復号のための情報としてファイルに残したのではないか、と推理しているためです。
もちろん、これは私の推理でしかないので、分析してみないと実際のところは分かりません。
もし謎の値「X」から一意に謎の値「A」が決まるなら、もっとも力技の方法として「レインボーテーブル」を作成すれば、確実に達成できるでしょう。
(もっとも、32バイトのデータのレインボーテーブルが現実的かどうかというのは別問題ですが・・・)
謎の値「A」の生成処理からの推測は、このデータを作成するための乱数作成処理があり、これに推測可能性があるかどうかを調べたり考察したりする必要があります。
私の経験・知識が不足しているという理由もあって、私にとっては困難な手法となっています。
謎の値「A」に関する追加情報と謎の値「X」への換算式のヒント
謎の値「X」から謎の値「A」を逆算する方法を見つけるには、そもそもの変換方式に目途をつけたいところです。
特に、既知の変換方式であれば、既に数式があるはずなので、その数式を適用すれば容易に解決するためです。
そのヒントになるかもしれない、という、奇妙な処理をみつけました。
以下にそのコードを示します。
謎の値「A」に対し、先頭1バイトの末尾3ビットを0 (AND 0x0F8)、末尾1バイトの先頭1ビットを0、先頭第2ビットを1(AND 0x3Fのあと、or 0x40を実施)にする処理が入っています。
(コードの見た目としては、謎の値「A」をコピーした領域に対して処理しているように見えますが、最終的に謎の値「A」の領域に書き戻しています。)
このため、これら5ビットは0または1で固定されていることが分かります。
また、先頭1バイトの末尾3ビット、末尾1バイトの先頭2ビットが対象になっていることが不自然に見えますが、これが「32バイトのリトルエンディアン値」としてみるならば、先頭の1バイト値の先頭2ビット、末尾の1バイト値の末尾3ビットが対象になり、辻褄が合うのではないか、と考えています。
問題は、「なぜこんなことをしているのか?」ということです。
データのうち、固定値が決められてしまっては、有効な鍵長が短くなり、暗号強度も低下するため、避けるべき事柄のはずです。
それをあえてしているのは、「そうしなければ都合が悪い何等かの理由がある」と推測できます。
例えば、謎の値「A」から謎の値「X」への変換の際、これらの値が失われてしまう、またはこれらの値が指定通りでないと一意にならない、という理由があったのではないか、と推測することもできるのではないでしょうか。
これは、解析のヒントになり得ます。
先に述べた通り、これが32バイトのリトルエンディアン値だとした場合、「先頭2ビット、末尾3ビットが固定値でないと換算が上手くいかない変換方式」を用いているのではないか、と考えられます。
このような特徴を持つ数式が既知で存在する場合、そんなに種類は無いのではないでしょうか。
ならば、「こういった条件」を元に合致する方式をピックアップし、実際に値を代入してみて検証すれば、比較的簡単に見つかる可能性があるのです。
こういう方法は数学的には美しくないかもしれませんが、先にも述べた通り、目標はあくまで「ファイルの復号化」です。
カッコよく解いて見せるというのは、ハッカーさんにお任せします!
(私は、ハッカーでもなんでもない、タダのエンジニアなので。)
また、「先頭の1バイトの末尾3ビット、末尾の1バイトの先頭2ビット、計5ビットが固定値」ということの判明は、ファイルの暗号化の強度に若干の低下を及ぼします。
つまり、32バイト=256ビットのうち、5ビットが既知の値であることから、暗号として有効なのは「251ビットに低下」するというわけです。
確かに、まだ微々たる差かもしれませんが、少なくとも総当たり攻撃の桁数は減りますし、こういった問題点の積み重ねが、鍵の強度の低下と解読可能性の向上につながるのでは、とも言えます。
ここまでで分かったことの小まとめ
前回同様、解析できたことを図で簡単にまとめました。
あとは、謎の値「A」をどうにかして求めることができれば、ファイルが復号化できるところまできました。
しかも、謎の値「X」というヒントがあることも判明しました。
正直言って、ここまでの解析はわりとトントン拍子できたのですが、ここからが大変そうなのです。
そのため、ここでギブアップの可能性もあるかもしれません。
ここからは、トライ&エラーを繰り返して分析し、成功したら続きを書こうか、と思っています。
さて・・・こんな記事でも、書くのには結構苦労しています。
今日はここいらで寝ちゃおうかなーと。
ウ〇娘のヴァルゴ杯でも、ウチのチームはBグループながら勝利してくれていました。
流石にガチ勢のAグループ勢には全く歯が立ちませんでしたが。
・・・え?
「お前、解析もせずにゲームの育成に熱中してたんじゃないのか?」って?
ななな・・・ナンノコトカナー??