いつもどおり、唐突に思いつく
前回の記事で、ランサムウェアの暗号化・復号化のキモになっている箇所を見つけはしましたが。
コードを頑張って読んでも、何の暗号化方式か分からないんですよ。 orz
Ghidraで逆アセンブルしても計算式が良く分からない、という始末でして。
なんとなくブロック暗号臭くはあるんですが、ラウンド数から少なくともAESではなさそうですね・・・。
こんなコードからでも見抜ける神の目がほすぃ・・・。
とまあ、無いものねだりしても仕方ないので、次の手段としては、「パラメータが分かってるんだから、色んな暗号化プログラムで手あたり次第計算させてみる」です。
・・・。
もう、技術、知識もへったくれもありませんw
やぶれかぶれの力技ですwww
でもまあ、そうなると暗号化・復号化するツールが必要でして。
とりあえずCyberChefにあるのはダメだったし、他に使えそうなのは・・・ということで、WindowsのCryptAPIでコードを書いてみよう、と思い立ちました。
どうせひな形作っておけば、後々で色々つかえるしw
また、CryptAPIも、CNG(Cryptography API: Next Generation)版の使い方をこの際試して習得してしまおう、そうすれば新しい暗号もライブラリが使えて楽なはずだ!と安直に思ったワケです。
そこで、以前旧バージョンで作ったAES暗号を、CNGで作ってみました。
今回は、ここにソースを書かず、以下のGithubにアップしています。
自身のバックアップを当然兼ねていますよw
コードの解説
今回は、AES暗号のECBモードとCBCモードを実装しました。
CCM、GCMも使えそうなので実装したいところですが、これらは「認証付き暗号化モード」のため、特有のオブジェクトを作成して実装する必要があります。
今のところ、その実装コードの勉強が間に合ってない(サンプルソースが見つからない・・・)ため、2021年11月7日時点では未実装でとりあえずGithubにアップしました。
コードの流れの概要
CNGを利用したAES ECB、CBCモードのコードの流れは以下の通りで上手く行きました。
- プロバイダオブジェクトの取得(BCryptOpenAlgorithmProvider)
- 暗号化モード等を設定(BCryptSetProperty)
- 鍵をインポート(BCryptImportKey)
- 暗号化・復号化(BCryptEncrypt、BCryptDecrypt)
- オブジェクトの開放(BCryptCloseAlgorithmProvider、 BCryptDestroyKey)
少なくとも、ECB、CBCモードでは、暗号化と復号化の処理以外は同じ流れで問題なく実行できました。
1. の「プロバイダオブジェクトの取得」では、暗号化・復号化をしてくれるプロバイダのハンドルを取得するために呼び出します。
もし別の暗号化方式(例えばRSA暗号)を利用したい場合は、それに対応したプロバイダを指定します。
プロバイダは、Microsoftのマニュアルに記載されています。
2. の「暗号化モード等を設定」では、暗号化のモードのほか、設定すべきプロパティがあればそれを指定します。
また、逆にBCryptGetPropertyで現在のパラメータを確認することもできます。
参照/設定できるパラメータは、Microsoftのマニュアルに記載されています。
3. の「鍵をインポート」では、既に定義されている鍵をインポートします。
これにより、鍵のハンドルを取得することができます。
暗号化・復号化は、この鍵のハンドルを使用することになります。
この方法を取る理由は、パスワードハッシュを元にした鍵や、マルウェア解析で見つかったバイナリ値が鍵であるケースが多く、検証ではインポートできるようにした方が都合がいいという、極めて個人的な理由によります。
なお、暗号化の際には、鍵を作成する「BCryptGenerateSymmetricKey」を使用し、それをExportする方法もあります。
今回は、こちらの方法は使っていません。
その代わり、「BCryptGenRandom」を使って乱数を発生させて自分で鍵を作る関数を別途実装しています。
4. の「暗号化・復号化」では、3.で作成された鍵のハンドルを用いて暗号化・復号化を行います。
コードの実装上だけでいえば、ECBとCBCは初期化ベクトルの有無だけが違います。
現実的には、暗号の強度に差が出ますので注意が必要です。
初期化ベクトルも「BCryptGenRandom」を使って乱数を発生させて作る関数を別途実装しています。
5. 使用したプロバイダオブジェクトや鍵のハンドルを開放します。
こちらは単純な後始末です。
以上のような流れで、大分すっきりしており、それほど難しくなく実装できました。
個々の処理では色んなパラメータの設定などがあってごちゃっとすることもあるとは思いますが、以上の5項目のうち、今は何をしているのか、を意識しておけば、それほど難しいものではありません。
デバッグも、それぞれのどこでエラーになっているかを把握し、段階的に開発や修正を進めていけば、実装自体はすぐにできるでしょう。
今回の制限事項
Githubのreadmeにもかいてありますが、以下が制限事項です。
- エラー処理は十分ではない。(あくまでCNGの動作を確認するためのサンプルコード)
- 大きなサイズのファイル/データについては考えていない。
- BCryptGenerateSymmetricKeyを使ったロジックまでは作っていない。(気が向いたら作るかもしれない。)
また、一通りの動作確認はしていますが、細かいテスト項目を作ってデバッグしているわけではありません。
そのため、どこかにバグがあるかもしれません。
発見した方は、こそっと教えてくれると嬉しいです。
(恥ずかしいので、こっそり直してアップします)
さいごに
ライブラリとしては、CNGは大分使いやすい方だとは思いました。
暗号化といっても色んな種類がある以上、これ以上の簡略化は難しいのではないかな、といったところです。
一方で、「暗号化の実装」については、やはり作成者が暗号についてある程度知識がなければ、どんなにライブラリが良くても脆弱なものになってしまいます。
今回の例でいえば、暗号鍵が容易に推測できるものであれば、いかに強力な暗号で良いライブラリであっても解読されてしまいます。
また、インポート/エクスポートされる鍵が、前後の処理で他者に知られる可能性のある実装にしている場合、そこから危殆化する恐れがあります。
暗号化モードも、今回はECBも実装しましたが、ECBは推奨されなくなってきています。
CBCについては初期化ベクトルがありますが、これがオール0という実装を見かけます(特にランサムウェア)。
「初期化ベクトルがオール0だと何が問題か?」ということを理解しておく必要はあるでしょう。
なお、初期化ベクトルの理解が薄かった私の恥ずかしい記事が過去にあるので、もし「初期化ベクトルってなんぞ?」という方がいたら、恥をかく前に見ておいたほうがいいかもしれませんよ。
(こうして、また自分で自分の恥をばら撒くという・・・。)
今回は、AES128に関して2モード実装してみた、というものです。
検証用に、関数をちょいちょい追加していきたいなぁと思っているところです。
(「今時C++?w」とかイワナイ)