fr33f0r4ll

自分用雑記

パッカーについて調べてみた

マルウェアに使われるパッカーについて

マルウェアに使われるパッカーについてちょっと調べたのでまとめておく。参考にしたサイトは以下。 徐々に増やすのが目標。

パッカー、パッキングとは

プログラムを実行できる状態のまま圧縮、符号化することをパッキングといい、パッキングするためのソフトウェアやプログラムなんかをパッカーという。 本来はプログラムのサイズを小さくしつつ、いちいち解凍しなくても実行できるようにするために使われていたらしい。 いつからかプログラムが静的解析されるのを妨害する目的でも使われるようになり、だんだん解析を妨害することに特化したものが増えていった感じだろうか。 元々の意味はともかく現在の使われ方は難読化とあまり差がないような気がする。 調べてみてもその違いについて明確に述べているものは見つからなかった。 パッカーはRuntime packer, self-extracting archivesとも呼ばれ、通常のzipの圧縮なんかと区別されることがある。 パッキングされたプログラムは普通に圧縮されたプログラムとは違い、パッキングされた状態で実行することができてパッキングされる前と同じことができる。

難読化との違い

意味合いの上ではデータをまとめて圧縮、符号化、暗号化するために行われるのがパッキングであり、データの意味を解釈するのを困難にするために行われるのが難読化であるといえると思う、根拠はないが。 しかし攻撃者がマルウェアをパッキングする目的はプログラムの意味や挙動を読み取れなくすることなので、一概にパッキングと難読化を分けるのは難しい。 しかも単純にサイズの圧縮のためにUPXなどのパッカーが使われていることもあるため、使われた目的から用語を分けるのも難しそう。

ちなみに難読化をするプログラムはオブファスケイター(Obfuscater)というらしい。 protectorという用語もある(packerとほぼ同じような使い方をされているっぽい)。

個人的な観察の結果からすると難読化はソースコードレベル、中間コードレベルで行われるタイプのものに使われる傾向があるらしい。 例えば、スクリプト言語の解読を困難にするようなものにはObfuscaterという用語が使われている。 また、Wikiに掲載されている難読化されたCの例として掲載されているのはCのソースコードだった(つまりソースコードレベル)。 Javaや.NETなどの中間コードを生成するものは、リバースエンジニアリングを防ぐためにObfuscatorを使おう!みたいなのも見つかった。

一方バイナリレベルで難読化を行うもの、特にバイナリの状態のプログラムを難読化するものはパッキングと呼ばれる傾向にあった。 セキュリティ系のところでしかこの用語が登場しなかったせいかもしれない。 あるいは、古くからありそうなサイトも多かったので単に用語が古いだけかもしれない。 packingだと荷物のパッケージングみたいなのしか出てこないので検索性が悪い。

StackExchange ReverseEngineeringで検索した結果だと、ヒット率は以下のような感じになった。 + obfuscator: 500 + packer: 159 + protector: 65 + pack: 54 + obfuscate: 500

明確に圧縮目的のものであればpacker, packingでも良いが、ほとんどのケースではObfuscator, obfuscateの方が良いのかもしれない。 少なくとも、マルウェア解析において問題になるのは難読化の方がほとんどだと思う。 とりあえずこの記事ではパッカー、パッキングを使うことにする。

マルウェアに使われる理由

パッキングするとプログラムの動作を変えないままその形を変えることができるため、プログラムのハッシュ値やバイト列などが一致するかどうかなどで検知するシグネチャベースのIDS/IPSやアンチウイルスソフト(AV)では検知することが難しくなる。 そのため、マルウェアを検知されないように送りこむために使われることがある。 このようにパッキングされたマルウェアでも、サンドボックスで実行しその挙動を観察することで検知できたりもする。こういう解析は動的解析という。

検知システムの回避だけでなく、パッキングすることによってリバースエンジニアリングによるマルウェアの解析を妨害することもできる。 マルウェアがパッキングされていると逆アセンブルしてもパッカーによって追加された命令と符号化されたデータしか復元できないため、本来の命令が確認できず解析が困難になる。 その場合マルウェア本来の動作を解析するためにはアンパックして本来の命令を復元する必要がある。 しかしこれがすごく大変で、元のパッカーが未知のものだったりすると非常にめんどくさい。 よく知られているパッカーや単なる圧縮のためのパッカー(UPXとか)なら簡単に復号化できるスクリプトがあったりするが、高度な攻撃者だとオリジナルのものを使うため自分でアンパックする必要がある。 このような妨害効果を期待して、攻撃者は複雑なパッキングアルゴリズムマルウェアに適用することがある。

パッキング手法

3種類の手法が解説されていた、知っているのはひとつだけだった。 + 自己解凍方式 - 下で抜粋したコンプレッサーとクリプターがあてはまる + API難読化方式 - 抜粋した中にはないが、どちらかというとプロテクターっぽい + 仮想マシン方式 - 下のバーチャライザーが当て嵌る。 これらの呼び方は完全オリジナルなので注意、元のサイトには手法自体に名前はなかった。 下のやつがもっと良い。

https://reverseengineering.stackexchange.com/questions/1779/what-are-the-different-types-of-packers ここにある名前がそれっぽい、日本語にして抜粋。

パッカーのコードが追加されて実行されるタイプ extension + コンプレッサー(Compressor) - データサイズを減らすために使われる - 一般的なもの: aPLib(FSG, LZMA, NRV(UPX)) - その他: JCALG1, BriefLZ, LZMAT + プロテクター(Protector) - リバーシングを困難にする - アンチデバッグ (anti-debugging) - isDebuggerPresent, 時間測るやつ、ハッシュ使うやつ - アンチ仮想化 (anti-virtualization) - VMWareの検知とか、仮想マシン用の特別なポートとかを調べて検知する - アンチダンプ (anti-dumping) - メモリのヘッダを消して妨害 - アンチ改ざん (anti-tempering) - checksumを使う - 一般的なもの: rolling checksum, CRC32, md5, sha1, adler, md4 - その他: Tiger, Whirlpool, md4, adler + クリプター (cryptor) - 暗号化するもの - 一般的なもの: ビット演算子(XOR/ROL/...), LCG, RC4, Tea - その他: DES, AES, Blowfish, Trivium, IDEA, ElGamal

元のコードを書き換えるタイプ transformation + バーチャライザー (virtualizer) - 元のコードを組み込みの仮想マシンのコードに替えるやつ、仮想マシン方式はこれ + ミューテイター (mutater) - コードを改変するが、元の命令アーキテクチャのまま - reflowing, oligomorphism - 詳しく書いてなかったのでよく分からない、要調査 - 多分、途中でメモリ上の自分自信のコード領域を書き換えるタイプ

機能を追加するタイプ extra features + バンドラー - 複数のファイルを一つのファイルとして実行できるようにするらしい

自己解凍方式

この方式のパッカーは元のプログラムを符号化あるいは暗号化してデータとして格納し、実行時に復号化してメモリ上に配置し実行する。 暗号化と符号化の違いは鍵が必要かどうかで、暗号化の場合は鍵がないと復号化できず、実行できないということになる。 多分一般的な方式で、マルウェア解析の話で出てくるアンパッキングはだいたいこの手法を対象にしてるっぽい。

アンパックするには、パッキングされたプログラムが元のプログラムのコードをメモリ上に展開したあと、元のプログラムを開始するアドレス(Original Entry Point, OEPと呼ばれる)を特定すればいい。 特定したら、アンパックし終わったタイミングでそのアドレスからメモリをダンプすると元のプログラムがだいたい手に入る。

API難読化方式

この方式のパッカーは、WinAPIとかの呼び出しを難読化して、解析しにくくする。 IAT(Import Address Table)なんかを動的に解決することで、ライブラリの関数を呼び出しているのを分かりにくくする。 この方式が使われていると、単純にメモリをダンプしただけではプログラムが解析できなくなるので厄介。 多分他の方式と併用されることが多いんじゃないかな。

ライブラリの関数自体をフックしたりすると解析できそう。

仮想マシン方式

この方式のパッカーは元のプログラムをオリジナルの仮想マシンで動作する独自命令に完全に変換してしまう。 パッキングされたプログラムは、独自命令になったプログラムとその独自命令を実行する仮想マシンで構成される。 一番高度な手法で、作るのも難しそう。

解析するためにはオリジナルの仮想マシンを解析し、どのように動作するのかを突き止めるしかない。

その他

他にも、ブロックごとに暗号化して一命令ずつ復号化して実行するようなパッキング手法も提案されてたりする。