パッカーを自作した -elfの勉強-
今まで普通に圧縮のアルゴリズムを学んでたのですが,今回少し趣向を変えて,パッカーというものを作りました.パッカーやその周辺の技術について詳しく解説されている文献が非常に少なかったため,「アナライジング・マルウェア」という書籍を大いに参考にさせていただいてます.丸写しなどは全く行なっておりません.また,この書籍にて解説されているパッカーのコードはwindowsで動くように書かれているのですが,勉強のためLinux(32bit)で動くよう書き換えていきます.
パッカーとは
パッカーは実行ファイル圧縮と呼ばれるもので,wikipediaには
実行ファイル圧縮(英: Executable compression)とは、実行ファイルを何らかの手段で圧縮し、それをデータとして伸張(解凍)用コードと共に1つの実行ファイルとすること。
圧縮された実行ファイルを実行する場合には、まず伸張(解凍)して本来の実行コードを取り出し、それを自動的に実行する。圧縮していないオリジナルの実行ファイルを実行したのと同じ効果が得られるので、一般ユーザーには区別がつかない。 "wikipedia 実行ファイル圧縮"
とあります.実行形式のファイルをパッカーで圧縮すると,圧縮したまま実行できるというものです.基本的には実行時にメモリに展開されるのですが,ハードディスクなどに小さいまま保存できるという利点があります.有名なオープンソースのパッカーにはUPXなどがあります.
本来,データ量を削減するために開発されたツールであると思うのですが,現在パッカーというと主にマルウェアを圧縮するために用いられるものとなっています.圧縮されるということは,保存されているコードが読める状態になっていないため,難読化する目的で利用されています.
パッカーの仕組み
圧縮されたコードと一緒にそれを展開するコードが埋め込荒れており,実行時にまず展開コードが実行されます.展開コードが実行されると,圧縮されたコードが展開され,展開先に実行が移ります.
パッカーの作り方
今回作るパッカーの作り方の手順を説明します.パッカーにも様々な種類がありますし,私自身初心者であるため,今回のものが全てではないことをご容赦ください.
xorエンコードは参考書籍で解説されていたコードがそうなっていたので,本当はせっかく学んだハフマン符号とかで実際に圧縮するところまでやりたかったのですが,力が及ばずできませんでした.なので,厳密にパッカーではないのですが,この辺をこんな感じで改造したら圧縮できそうだなーみたいな感じで読んでください.
実行ファイル
ここから実際に作っていきたいのですが,とにかく実行ファイルがどういう構造をしているかわからないと何もできないため,実行ファイルの解説をしたいと思います.
Linuxの実行ファイルは現在では,COFFというものを少し改造したELFというフォーマットで構築されています.ELFの構造を図にすると以下のようになります.
elf headerというヘッダが先頭に配置されており,program header tableがその次に配置され,コード領域が続いていき,一番下に,section header tableが配置される,という構造になっています.配置されている順序はこうなっていますが,実際にアクセスする場合は各ヘッダに保存されている値を用いてアクセスします.
program header table, section header tabeleは文字通り,各ヘッダのテーブルとなっており,ここに各領域の数だけヘッダが並べられています.
program headerの指す領域はセグメントと呼ばれ,section headerの指す領域はセクションと呼ばれています.
次に各ヘッダの解説をします.今回主に使うものだけにコメントをつけて軽く説明しています.
elf headerの中身は次のようになっています.
#define EI_NIDENT 16 typedef struct { unsigned char e_ident[EI_NIDENT]; //マジックナンバーなど uint16_t e_type; uint16_t e_machine; uint32_t e_version; Elf32_Addr e_entry; //エントリポイントのアドレス Elf32_Off e_phoff; //program headerのアドレスのオフセット Elf32_Off e_shoff; //section headerのアドレスのオフセット uint32_t e_flags; uint16_t e_ehsize; //elf header(このヘッダ)のサイズ uint16_t e_phentsize; //一つのprogram headerのサイズ uint16_t e_phnum; //program header の数 uint16_t e_shentsize; //一つのsection headerのサイズ uint16_t e_shnum; //section headerの数 uint16_t e_shstrndx; } Elf32_Ehdr;
このヘッダでは,プログラム全体の管理をしている感じですね.おそらく一番よく見ることになるのがe_entryです.プログラムが実行される際にどのアドレスから始めるのかが記録されています.
次にprogram headerです
typedef struct { uint32_t p_type; Elf32_Off p_offset; //セグメントのアドレスのオフセット Elf32_Addr p_vaddr; //セグメントの仮想アドレス Elf32_Addr p_paddr; //セグメントの物理アドレス uint32_t p_filesz; //セグメントのファイルイメージのサイズ uint32_t p_memsz; //セグメントのメモリ上のサイズ uint32_t p_flags; //セグメントの属性 uint32_t p_align; } Elf32_Phdr;
program headerは各セグメントに対する情報が保存されています. セグメントの属性は次のようなものがあります. - PF_X 実行可能セグメント - PF_R 読み取り可能セグメント - PF_W 書き込み可能セグメント
最後にsection headerです
typedef struct { uint32_t sh_name; uint32_t sh_type; uint32_t sh_flags; Elf32_Addr sh_addr; //セクションのアドレス Elf32_Off sh_offset; //セクションのアドレスのオフセット uint32_t sh_size; //セクションのサイズ uint32_t sh_link; uint32_t sh_info; uint32_t sh_addralign; uint32_t sh_entsize; } Elf32_Shdr;
このヘッダではアドレスとオフセットが重要です.
だいぶ雑ではありますが,このようにelfは出来上がっています.
まとめ
パッカーの自作というタイトルですが,いったん色々解説したタイミングで今回は終了します.私がパッカーを作った際に使用した部分のみの解説になってしまったので,詳しくは参考文献にあげた記事などを参考にしてもらいたいです.非常にわかりやすいです. また,section headerとprogram headerの違いが正直まだよくわかっていません.詳しくは次回以降に書きますが,よくわかっていないせいで,非常に悩んだ部分もありました.詳しい人がいたら教えてください... 次回,実際にコードを書いていきます.
参考文献
アナライジング・マルウェア ―フリーツールを使った感染事案対処 (Art Of Reversing)
- 作者: 新井悠,岩村誠,川古谷裕平,青木一史,星澤裕二
- 出版社/メーカー: オライリージャパン
- 発売日: 2010/12/20
- メディア: 単行本(ソフトカバー)
- 購入: 8人 クリック: 315回
- この商品を含むブログ (22件) を見る