TLS Callbacksを扱ったCTF問題のwriteup

先日、TLS Callbacksを扱ったCTF問題を解いたので、そのwriteup。
※このCTFチャレンジは個人的なツテで入手したもので、作問者からCTFのイベント名を公開しないことを条件にWrite Up記載の許可をもらっています。

サマリ

提供されたのは 32ビットのEXEファイルとテキストファイル数種類。テキストファイルは、arpコマンド、ipconfigコマンド、net localgroupコマンド、net userコマンド、netstatコマンドの出力結果と、Windowsのパッチ状況のログ。
EXE ファイルを起動すると、ユーザー名とパスワードを聞かれる。ログインに成功すると"Login successful?"というメッセージが表示されて終了。ログインに失敗すると"Login failed?"というメッセージが表示されて終了。
EXE ファイルにはダミーのフラグを生成するコードが記述されており、本物のフラグを取得するにはTLSセクションのコードを解析しなければならない。ファイルには謎のペイロードがRC4暗号化された状態で埋め込まれており、特定のコンピュータ名のSHA1ハッシュ値の先頭16バイトを復号鍵として用いている。復号鍵の生成に用いられているコンピュータ名は、先述したipconfigコマンドやnet localgroupコマンドの出力結果を見れば分かる。
謎のペイロードをRC4復号するとPNGファイルがメモリに書き込まれるので、これをダンプする。ダンプしたPNGファイルにはQRコードが載っており、これをスキャンするとフラグが出現する。

解析

※逆アセンブル内の変数名や関数名は一部解析に当たり、わかりやすいようにデフォルトのものから変更している。また解析効率向上のため、あらかじめCFFでファイルのASLRを無効化した。

EXE ファイルを眺めたところ、strcmpがインポートされていたので、strcmpを呼び出している前後のコードを解析したところ、あっさりフラグが取得できた。。と思ったのだが、これはダミーのフラグだった。

改めてファイルを眺めてみたところ、TLSセクションを発見した。

また、 ファイルをIDAで開いてCtrl + Eを押すとTLS Callbacksの存在が確認できた。

TLS Callbacksとは

ここで言うTLS とはネットワーク・プロトコルのTLS (Transport Layer Security)のことでなく、 Thread Local Storageのこと。通常、プログラムを実行するとデータは(OSやコンパイラによって自動的に割り当てられる)スタック領域に保持されるが、Thread Local Storage (以後、TLS)を利用するとデータはスレッド固有の静的記憶域に保持される。TLSにはTLSオブジェクトの初期化と破棄を行うためのコールバック関数が用意されている。これがTLS Callbacksである。

ちなみにオライリー・ジャパン発行 「BINARY HACKS ハッカー秘伝のテクニック100選」のP.5ではTLSを以下のように解説している。

どのスレッドも同じ名前の変数を使いながらも、実際に格納される値はスレッドごとに独立して保持できる領域のこと。GCCでは__threadというキーワードを用いて、TLSを使う。

TLSの詳細はリンクを参照してもらうとして、ここで重要なのはTLS Callbacksを利用するとPEヘッダで定義しているエントリーポイントよりも先にコードを実行できるという点である。通常、デバッガはまずPEヘッダで定義しているエントリーポイントまで処理を進めようとするので、普通にファイルをデバッグすると、エントリーポイントにたどり着く前に、TLS Callbacksのコードが実行されてしまう。
TLS Callbacksを利用するEXEファイルには.tlsセクションが追加される。Practical Malware Analysisによると、ファイルの中に.tlsセクションを発見した場合、通常のプログラムが.tlsセクションを利用することは稀なので、アンチ・デバッグの兆候と疑うべきとしている。

TLS Callbacksをデバッグするには普通にTLS Callbacksの中のコードにブレークポイントをセットすれば良い。(x32dbgの場合)

TLS Callbacksのコードを眺めていると、冒頭にアンチ・デバッグのコードを発見した。

上記のコードはPEBのBeingDebuggedフラグを確認する定型文である。ここより先の処理へデバッグを進めるにはファイルをパッチするなり、デバッグの際にcmp命令で比較されるレジスタの値を書き換えるなどしてチェックを突破しなければならない。

アンチ・デバッグのチェックを突破すると、以下の処理に突入する。

アドレス0x401449と0x401463で呼び出しているのはXOR演算を行う関数である。引数として受け取った文字列をXORする。XORの鍵は0x14。

XORの結果、Kernel32.dllとGetComputerNameAの文字列が生成されて、GetModuleHandleAおよびGetProcAddress関数に引数として渡される。これによりGetComputerNameA関数のアドレスが取得される。

上の画像より、アドレス0x401499でGetComputerNameA関数を呼び出してローカル・マシンのコンピュータ名を取得し、アドレス0x4014b8で呼び出されているSHA1ハッシュ化を行う関数に引数として渡す。
以下はSHA1ハッシュ化の処理の一部。先述したXOR関数でCryptAcquireContextA、CryptCreateHashなど暗号に関連する関数名を生成し、GetprocAddress関数でアドレスを解決し、関数を逐次実行する。


コンピュータ名のSHA1 ハッシュ値は後述するRC4の復号鍵の生成に用いられるのだが、正しく復号を行うためには正しいコンピュータ名と、その文字列サイズをSHA1 ハッシュ関数に渡さなければならない。GetComputerNameA関数はローカル・マシンのコンピュータ名、つまり現在解析に利用しているマシンのコンピュータ名を取得するが、これは当然正しいコンピュータ名ではない。ここで冒頭に述べたarpコマンド、ipconfigコマンド、net localgroupコマンド、net userコマンド、netstatコマンドの出力ファイルの存在に思い至る。ipconfig、net localgroup、net userコマンドの出力にはコマンドを実行したコンピュータ名が含まれている。結論から言うと、これらのコマンドの出力ファイルに含まれていたコンピュータ名がRC4復号鍵の生成に用いられていた。

さて、コンピュータ名のSHA1 ハッシュ値を取得すると以下のループ処理に突入する。

上記のループはコンピュータ名のSHA1 ハッシュ値の先頭16バイトを抽出してvar_14に格納している。つまり、SHA1 ハッシュ値をそのままRC4の復号鍵に するのではなく、ハッシュ値の先頭16バイトの値を復号鍵として使用するのである。
抽出が完了すると、いよいよ以下のRC4復号処理に進む。

アドレス0x401533にてRC4復号の関数を呼び出している。RC4復号の関数は以下の4つの引数を受け取る。

  • ファイルに埋め込まれているRC4暗号化されたペイロード
  • RC4暗号化されたペイロードのサイズ
  • RC4の復号鍵 (コンピュータ名のSHA1 ハッシュ値の先頭16バイト)
  • RC4の復号鍵のサイズ
  • 復号されたデータが書き込まれるメモリ領域へのポインタ

RC4復号の関数の実行後、復号されたデータが書き込まれたメモリ領域へのポインタは0x401542で呼び出されているfree関数によって解放される。free関数の直前にブレークポイントをセットしてEAXレジスタの内容をダンプしたところ、PNGファイルの存在を確認した。

ダンプしたPNGファイルにはQRコードが載っており、これをスキャンするとフラグが出現した。

参考
Practical Malware Analysis (by Michael Sikorski and Andrew Honig) P.359 - 361 (Using TLS Callbacks)
Practical Malware Analysis (by Michael Sikorski and Andrew Honig) P.353 - 354 (Checking the BeingDebugged Flag)

Leave a Reply

Your email address will not be published. Required fields are marked *