難読化の話(超!?入門編)|セキュリティごった煮ブログ

ネットエージェント
セキュリティごった煮ブログ

 コース:こってり

難読化の話(超!?入門編)

bubobubo

どーも、bubobuboです。

竹返し、投扇興、ヨーヨー、ディアボロ、チャッターリング、ボールジャグリング、アストロジャックス、ポイ、スターテンビリオン、けん玉、コマ、ハンドスピナーなど、いろいろなスキルトイやフィジェットトイに手を出しては長続きしませんでしたが、とうとう(今更?)ルービックキューブに手を出してしまいました。昔のルービックキューブはというと、硬くて回しにくくて、カラーシールがはがれて訳が分からなくなった個体をよく見かけた印象しかありませんでした。現在の世界記録は、100メートル走の記録を大幅に上回る4.591秒。

使用モデルはMoYu(魔域)のWeiLong(ウェイロン)GTS2M。コンマ秒の戦いを勝ち抜くためには、性能のいいルービックキューブが必要で、外見は全く変わっていませんが、中身はものすごくグレードアップしています。

私物のGTS2M。撮影のため職場で6面揃えました 私物のGTS2M。撮影のため職場で6面揃えました

2016年には、3つのルービックキューブをジャグリングしながら「完成」させるという動画が話題になりましたが、コンピュータグラフィックスによる非常によくできた合成、要はトリックだったことが明かされました。

しかし、スキルトイの世界では、ごくまれに現実がフィクションを超えることがあります(ヨーヨーが典型例)。2018年5月には、本当に3つのルービックキューブをジャグリング(投げ方は最も基本的なカスケード)しながら完成させる13歳の中国人が表れ、ギネス世界記録に認定されています。すごい。


閑話休題。

かい○ゃから何か書けと言われたので、クライアントプログラムのセキュリティの一分野である(とされている)、「プログラムの難読化」をテーマに片手間で作文を行いました。

■雑に学ぶ難読化

「プログラムの難読化」と一口に言っても、対象はソースコードなのか、中間コードなのか、あるいはネイティブコードなのかで、だいぶ技術要素は変わります。例えば、スクリプト言語を難読化するppencode、jjencode、aaencodeといったものや(ご存じない方は各自検索してみてください)、ネイティブコードを難読化(厳密にはパッカーやプロテクターによって行われる暗号化+難読化)するツールもありますが、ここでは主にJavaや.NETとその互換環境で使われる中間コードに対する難読化を説明します。

で、難読化って何よ? という問いにシンプルに答えるならば、読みにくいプログラム(いわゆるスパゲッティコード)を意図的に作りだして、人間視点での可読性を著しく下げる技術です。主な目的は、プログラムやアルゴリズムの隠ぺいです。どのような状況で使われるのかは、2つに分けられます。

  1. プログラムがリバースエンジニアリングされて「独自のアルゴリズム」を複製されたり、改造によりプログラムの機能制限を回避される事態を妨害するため
    (知的財産の保護)
  2. マルウェアの解析を遅らせるため、付随してツールによる自動検出を防ぐため
    (完全なる悪用)

注意したいのが、(主に)人間視点で読みづらくなるというだけであって、読めなくなるわけではありません。プログラムの構造は複雑にはなりますが、入出力が変わることはありません。したがって、時間が無尽蔵にあると仮定すれば完全解読は可能なので、程度問題になります。

例えば、Androidアプリで RootCheck() という名前の関数があった場合、その名前だけでもどういう処理が行われるかを類推することは容易です。これが a() とか b() とか、あるいは「文字が割り当てられていないUnicodeの私用領域」を使って変えてしまうと、解読を試みる側にとっては、少なくとも「一目見るだけで分かってしまう」ということにはならなくなります。理論上は。

他にも、"[%04d-%02d-%02d %02d:%02d:%02d] Security Log = %s\n" という意味ありげな文字列があった場合、この文字列が参照されている処理を参照すれば、どのような処理が行われているかを推測することが可能です。こういった文字列が平文で存在すると、grepをかけるだけで列挙することができてしまうので、簡単な方法でいいから暗号化をかけて一目見て意味のある文字列だとわからないようにすれば、少なくとも「一目見るだけで分かってしまう」ということにはならなくなります。理論上は。

Javaプログラムや.NETプログラムは、アーキテクチャの性質上、クライアントプログラムに埋め込まれている中間コードから可読性の高いソースコードを復元できるため、難読化の需要は一定数存在します。実際のところ、無償、有償を問わず、難読化ツールはJava対応、.NET対応のものに集中しています。

解読されにくくするために、手作業で読みにくいコードを書いたりするアプローチがありますし、そういった方法を記した書物も複数あります。しかし、その効果は極めて限定的です。というか、やるべきではない

「意図的にスパゲッティコードを作り出す」と言い換えると、開発効率を犠牲にしてまでやるべきことなのかという疑問も生まれるはずです。少なくとも、その場の思い付きで得たような方法はほとんど意味がありません。ボタンポンで難読化されたコードを出力できるよう自動化できるのなら、開発効率は犠牲にはならないので、アリだとは思います。難読化したコードがちゃんと動くものかどうか十分テストする必要はありますし、本当に良い見づらいコードになっているのかを確認する必要もありますが。話が戻ってしまいました。

今やどのアプリもクライアントサーバー型を採用したオンラインが前提のものばかりなので、本当に隠したい重要な処理はクライアントに持たせず、サーバーサイドで処理してしまうのが一番確実です。これなら、サーバーに脆弱性がないという前提が守られれば、重要な処理の内容を見られることはありません。ユーザーは入力と出力しかわかりません。

■結局、難読化は必須技術か?

筆者が難読化という技術を初めて知ってから15年ぐらい経ちますが、現時点での難読化技術、および難読化ツールは、難読化技術の提供者が謳う(=ソフトウェアを守りたいと考える開発者から期待されている)効果と、実際に得られる効果には大きな乖離があると筆者は考えています。

これは「費用」対「効果」で考えるとわかりやすいと思います。「費用」については、(「要お問い合わせ」とかではなく)金額が出ている都合、技術者でなくても明確にわかります。20ドルとか100万円とか無料とか。一方で、実際に得られる「効果」の方はというと、これが非常にわかりにくい。当然のことながら、難読化ツールを提供する側は、期待される効果を強調しているわけですが、一方でそれが真に効果があるものなのかを第三者が検証・検定できる枠組みがほぼないわけです。

RSAやAESといった現代暗号であれば、暗号の仕組み自体は全てオープンになっています。世界中の暗号学者からのツッコミや査読を受けてもなお、現実的な時間では破られることはないと判断されていることが、安全性の根拠となっているわけです。

しかし、難読化技術は違います。要素技術は箇条書きで説明されるか、当たり障りのない範囲で説明されることはありますが、核心に迫るトピック(例えば実装方法、難読化を実現する方法)あたりは、たいてい伏せられています。というのも、難読化技術は、仕組みがバレると、マジックの種明かしのように一瞬で無力化してしまったり、古典暗号やルービックキューブ(やっとフリが出てきた)のように「解法」が開発されて、その結果「一瞬で無力化」とはいかなくても強度が著しく落ちてしまうような要素を多く抱えているからです。

実際に「難読化」されたコードを読むことはありますが、「難読化ツールってこの程度かよ」と突っ込みたくなるような、時間稼ぎにすらなっていないようなものに遭遇することも稀によくあります。その難読化ツールが「無料」とか「数十ドル」ぐらいの値段ならあきらめもつきますが、100万円だったら下手すれば訴訟モノだと思うのですが...。筆者なら100万円あるならプログラマを教育する方に回すと思います。

難読化ツールは、オリジナルのプログラムをドラッグアンドドロップすると難読化されたプログラムが出力されるものもあるし、パラメータとして難読化のレベルを設定できるものもあります。難読化ツールの利用者は、内部ではどのような難読化が行われているかを知ることができません。

難読化のレベルを上げようとすると「関数名や変数名を無意味な文字列に変える」といった、シンプルかつ副作用がほぼないものだけでは大きな効果が見込めないので、「プログラムの構造を組み替える」といった、非常に特異な領域に踏み込まざるを得ません(コンパイラのコード最適化問題が技術分野としてはたぶん最も近い)。当然副作用のリスクも上がります。その結果「難読化のレベルを上げた結果、(相性が悪く)一部のプログラムが動かなくなった」といった深刻な問題に遭遇することがあります。開発者にとっては、さんざん動作確認を行って総仕上げとして難読化をかけるはずだから、納期ギリギリのタイミングで、難読化ツールのせいでプログラムが止まるようなことがあるとウルトラ迷惑なはずです。

難読化ツールもそのことは分かっていて、全ての難読化ツールをチェックしたわけではありませんが、難読化のレベルが複数段階ある場合、デフォルトでは最弱になっているはずです。難読化の強度よりもプログラムが止まらないことを優先している訳です。この状況で難読化ツールを使いたいと思っている開発者ができることは、まずは「難読化レベル」を最強にして難読化を行って、難読化されたプログラムの動作確認を行います。さらにはパフォーマンスの低下により本来の動作に影響が起こらないことも確認しなければなりません。最強設定で難読化されたプログラムが無事に動けばそれでいいでしょう(本来そうあるべきなのですが)。もし不具合が出た場合は、難読化のレベルを下げて難読化を行って、同じことを繰り返す訳です。難読化ツールとの相性が悪いと、難読化のレベルをどんどん下げざるを得なくなり、結局難読化とは何だったのかということもあるわけです。

難読化のメカニズムを自ら理解しないまま、あるいは第三者による検証が十分になされていない技術を取り入れるのは、かなり危険なことだと思います。難読化されたプログラムを一度は読んでみるべきです。難読化に限った話ではありませんが、「プロダクトを導入するなどしてセキュリティ向上に努めているが、攻撃する側も一枚上手である」とでも言えば、株主やステークホルダーを煙に巻く...納得させることはできても、ハッカーやクラッカーにとっては大して意味がないと思います。そこにテクノロジーは存在しません。

難読化ソリューションには様々な疑問点や矛盾している点があると思うのですが、今回はここまでにします。要望があれば第二回を書くかもしれませんし、謎の力学が働いたら別な形で発表するかもしれません。

■余談

最後に、余談ではありますが、難読化のヒントとしてIOCCC(国際難読化Cコードコンテスト)を参考にすると面白いかもしれません。人間にとって読みにくいコードを作り出すことに力を注いでいる、競技プログラミングという枠で見ても相当にイカレている「奇祭」あるいは「とんまつり」とも呼べるコンテストであります。

このIOCCCから得た「人間にとって読みにくいコード」を分析した資料が公開されており、C言語におけるバッドノウハウが凝縮された感のある内容で、見る価値があります。特に「Table1」が傑作。

現在のソフトウェア開発では「C言語(特にC99)は使うべきではない」という言説が常に存在します。IOCCCは例としては極端すぎますが、「C言語は(使わずに済むのであれば)使うべきではない」というのには同意します。

今回のエントリはここまで。

メルマガ読者募集 セキュリティを学ぶ

月別