リバースエンジニアリングツールによる静的解析の実践

コラム概要

■こんな方におすすめ

  • アプリのセキュリティについて基本的な内容を知りたい
  • 解析がどのように行われるか/リバースエンジニアリングについて知りたい
  • 開発/技術担当の初心者の方

■難易度:★☆☆
■内容: 静的解析の実演(Ghidraによるcrkme07.exeの静的解析)

※環境:

  • Windows10 Pro (x64)
  • Ghidra v9.1

はじめに

こんにちは。研究開発部です。
皆さんはアプリへの攻撃としての静的解析(以下、単に静的解析)について、どのくらいご存知でしょうか?
今回は、無防備なプログラムに対する静的解析を通して、改めてアプリを静的解析から守るための基礎的な対策を解説したいと思います。

前置きとして、静的解析の目的は、アプリを起動させずにロジックを把握することによって、ロジックを盗んだり、クラッキングを行うことです。

かつては、静的解析はアセンブラの知識が必要とされる、ある種職人技の領域でした。しかし近年は、リバースエンジニアリングツールの性能向上や無料配布等によるアクセシビリティの増加によって、よりカジュアルなものになってきています。
以下に実例を示します。

静的解析の実践

1.動作確認とリバース、判定部分の発見

以下、eagle0wlさんのcrackme VOL.01 ver1.02(2002-2006)のcrkme07.exeについて、Ghidraによる静的解析とその解説を行います。

まずはじめに、何が要求されているのかを把握するために、crkme07.exeを起動してみます。

テキストボックスが4つあります。入力した後に登録を押してみても反応がありません。

正しい入力を与えれば何かしらの反応があると推測します。

さて、Ghidraでリバースしてentryから読み進めていきます。
順番に確認していったところ、関数FUN_00401099の内部に入力値の判定を行っている部分を発見しました。

(この部分をロジカルに発見するためには、WindowsのGUIアプリに関する知識が必要ですが、ややマニアックなので割愛します。)

2.入力値の条件と判定箇所の特定

50行目のpuVar10はマウスオンで「2バイト長の未定義データへのポインタ」と表示されます。
直後のdo…while文でnIDDlgItem = 100, 101, 102, 103 からGetDlgItemIntという関数で整数値を取得しています。
100~103はおそらくテキストボックスの識別子です。(左から順番でしょうか)
do…while文中で、下図1のように入力値が配置されていきます。

(図1)

この図から分かることを整理すると下表の通りになります。

よって、61行目、65行目、69行目の条件式から、
「テキストボックス100に入力する数値は9990~9999であればよい」…①
「テキストボックス101に入力する数値は9999以下であればよい」…②
「テキストボックス102に入力する数値は9999以下であればよい」…③
ということが分かります。

※逆アセンブル結果から、DAT_0040311f.2_2がDAT_00403121であることが確認できます。

よって、73行目から始まる条件式は、テキストボックス103に適切な数値が入力されているかどうかを確認していると考えられます。

3.判定ロジックの解読

73行目から始まる条件式で、テキストボックス103に適切な値が入力されているかどうかを判定しています。
テキストボックス101から取得した入力を「sInput1」、
テキストボックス102から取得した入力を「sInput2」とおくと、
その右辺の意味する所は、
「0040312dから00403190 の100byte(画像下段のウインドウを参照)をshort型の配列(要素数100)とみなして、
(sInput2 + 0x8d + sInput2 / 100 + sInput1) % 100で得られる添え字で要素にアクセスしたものを100倍し、
0x26(= (unsigned short)DAT_0040312d)を加えたもの」…④ です。

DAT_0040312d へのアクセスはReadのみです。
実行中に書き換えられることがないので、初期値のまま計算してよいということが分かります。

4.クラック成功

さて、すべての条件が分かったので、判定を突破してみましょう。

①~④より、
(テキストボックス100, テキストボックス101, テキストボックス102, テキストボックス103)
= (9999, 0, 0, 238)
で判定の突破に成功するはずです。
テキストボックスが左端から識別子で昇順に並んでいると仮定して入力してみましょう。

画像では変化が分かりにくいのですが、「未登録」という表示が「登録済」という表示に変化しました。
クラック成功です。

まとめ

ご覧いただいた通り、無防備なプログラムは、逆コンパイラによっていとも容易くC言語までリバースされてしまい、
そのロジックが把握されてしまいます。

一方、暗号化されたバイナリは、復号を行う部分のみがリバースされます。プログラム本体の解析を行うには、自力で復号するか、動的な手法を用いる必要があります。
また、逆コンパイル結果が、例えば所謂スパゲッティ・コードだったらどうでしょうか。さらにダミーの計算や関数、ループが大量に混じっていたらどうでしょう。条件分岐が山のようにあって、すべての定数が、変数が、難解な計算式で表されていたら…。

難読化は、攻撃者に大きな解読コストを強いることによって、ロジックを保護します。
現実的な時間で解読不能なほどに難読化できるのが理想ですが、現状はそこまでのものではありません。
基本的には暗号化と併用するべきでしょう。

さて、静的解析対策の必要性をお伝えすることができたでしょうか。動的にバイナリを保護していても、静的にクラックされてしまってはほとんど意味がありません。もちろん同様の理屈で動的解析対策も重要です。

バイナリレベルの静的解析対策として、暗号化と難読化を行いましょう。


引用:eagle0wlさんのcrackme VOL.01 ver1.02 (2002-2006)のcrkme07.exe
(http://www.mysys.org/eagle0wl/ )