【Rev・バイナリ解析関係】ELFパーサをつくろう:ELFフォーマット3/4

前書き

このところは関東の暑さに圧倒されています。
前回の記事 に引き続きELFフォーマットについて確認していきます。
今回はいよいよセクションの記事になります。バイナリ解析やCTFでpwnなどをしていると、.textセクションや、本質ではないとスルーしつつも、.initセクションなどを目にしたことがあると思います。今回は、ELF形式の実行ファイルに一般的に存在するセクションについて確認していきます。

ELFフォーマット

ELFフォーマットの概要
セクションは、今まで確認してきたELFヘッダやセクションヘッダとは異なり、セクション間で、共通の決まったフォーマットを持ちません。セクションの中身は、文字列であったり実行可能コードであったりと様々です。今回については、以下のセクションについて、その内容と役割などについて確認していきます。

  • .init/.finiセクション
  • .textセクション
  • .bss/.data/.rodataセクション
  • .plt/.got/.got.pltセクション
  • .rel.*/.rela.*セクション
  • .dynamicセクション
  • .init_array/.fini_arrayセクション

.init/.finiセクション

.initセクションおよび.finiセクションは、プログラムが実行または終了する際に、初期化処理及び終了処理を行う実行可能コードを格納しています。実行可能コードが含まれているため、sh_typeフィールドはもちろんPROGBITが設定されています。言うなればプログラムのコンストラクタとデストラクタにあたるセクションですが、ソースコードに現れるコンストラクタとデストラクタがこのセクションに格納されているわけではありません。コンストラクタとデストラクタは、それぞれプログラムの__libc_csu_init__run_exit_handlersから呼び出されます。

.textセクション

.textセクションは、皆さんご存知の通り、プログラムのメインコードやframe_dummyなどのプログラムの実行において緊要な役割を果たすコードを格納しています。ユーザ定義でないコードには今回は触れませんが(というか理解が追いついていないのでまだ触れられませんが)、参考になりそうなページがあったので、自分のメモの意味も含めて貼っておこうと思います。

dbp-consulting.com

.bss/.data/.rodataセクション

.bss/.data/.rodataセクションは、それぞれ異なるタイプのデータを格納します。これら3つのセクションの違いは、セクションに対する書き込みの可否と、データの初期値の違いにあります。.bssセクションに初期化されていないデータが格納されるのに対し、.data/.rodataセクションには初期化されたデータが格納されます。また、.rodataセクションのsh_flagsにはSHF_WRITEが設定されていないのに対して、.data/.bssセクションのsh_flagsにはSHF_WRITEが設定されています。例えば、こんなプログラムを考えてみます。

#include<stdio.h>

int a;
int b=0;
int c=1;

int main(void)
{
    a=17;
    b=34;
    c=51;
    return(0);
}

gccのバージョン11.3.0でこのコードをコンパイルすると.data/.bssセクションにそれぞれ変数cと変数aおよびbのための領域が確保されます。gdbなどのデバッガで実行を追っていくことで.data/.bssセクションの中身が逐次書き換えられていくことが分かります。また、printfなどに渡される文字列データ等があれば、これらは.rodata領域に格納されます。

.plt/.got/.got.pltセクション

まず、.gotセクションと.pltセクションについて解説します。gotはGlobal Offset Tableの頭文字を取ったものです。共有ライブラリをリンクする際に、解決を行うシンボルを一ヶ所に集約する目的のために存在し、実体はシンボルへのポインタの配列です。pltはProcedure Linkage Tableの頭文字を取ったものです。プログラムから共有ライブラリ内のシンボルを呼び出す際には、直接共有ライブラリ内のシンボルを呼び出すのではなく、.got.pltセクションに格納されているアドレスを参照してシンボルを呼び出します。この時に、.got.pltセクションへに格納されているアドレスを参照するために.pltセクションを利用します。
遅延バインディングという言葉が出てきたので少し説明しようと思います。共有ライブラリ内のシンボルを呼び出す際、シンボルの解決はそのシンボルが初めて呼び出される際に行われます。すべてのシンボルの解決を一度に行うと時間がかかる場合があるからです。遅延バインディングにおいては、まずプログラムからシンボルが呼び出されると、実行が.pltセクションの各シンボルに対応した命令に移され、そこから.got.pltセクションに格納されているシンボルのアドレスを参照します。ここから先、初めてシンボルが呼び出された場合と2回目以降の呼び出しで実行される命令が変わります。 初めてシンボルが呼び出されると、.got.pltセクションには、参照元の.pltセクションの命令の、次の命令が記録されているアドレスが格納されているため、実行をそのまま.pltセクションに返します。各シンボルに対応した部分は任意の値をスタックにプッシュした後、デフォルトスタブと呼ばれる.pltセクション内の命令群に実行を移し、そこから動的リンカを呼び出してシンボルを呼び出すとともに、.got.pltセクションを書き換えます。
2回目以降のシンボルの呼び出しでは、.got.pltセクション参照ののちに.pltセクションに実行が移ることはありません。最初のシンボルの呼び出しの際に、動的リンカによって.got.pltセクションの中身が直接共有ライブラリのアドレスを示すように書き換えられるためです。
ところで、ここでは何の説明もなしに.gotセクションと.got.pltセクションを混在させていましたが、.gotセクションと.got.pltセクションの違いは何なのでしょうか?明確に言及している文献が得られなかったため憶測にはなってしまいますが、コンパイルオプションとして-z nowを指定すると.got.pltセクションは出現せず、代わりに.gotセクションに動的リンカによって解決された後のシンボルのアドレスが出現します。-z nowオプションは、遅延バインディングを無効化するため、.got.pltセクションは遅延バインディングが行われる際にのみ使用されるセクションなのだと思います。

.rel.*/.rela.*セクション

.rel.*/.rela.*セクションは、リンカによる再配置の際に利用されるセクションです。.relと.relaの違いは、再配置情報の格納方法にあります。.rel.*セクションによって再配置情報が管理されている場合は、再配置される場所にあらかじめベース値が設定されているのに対し、.rela.*セクションでは、.rel.*セクションで再配置情報を管理しているときと異なり、ベース値が.rela.*セクションのフィールドに格納されています。
.rel.*および.rela.*セクションは、elf.hにおいて以下のような構造体として定義されています。

typedef struct {
        Elf32_Addr      r_offset;
        Elf32_Word      r_info;
} Elf32_Rel;
 
typedef struct {
        Elf32_Addr      r_offset;
        Elf32_Word      r_info;
        Elf32_Sword     r_addend;
} Elf32_Rela;

r_offsetは再配置が行われる場所を表し、オブジェクトファイルと実行形式で意味合いが異なります。オブジェクトファイルの場合は、再配置が行われる場所の、セクションの先頭からのバイトオフセットが格納され、実行形式の場合は、再配置によって値が埋め込まれた場所の、仮想メモリのアドレスを格納します。r_infoは、上位24bitで再配置されるシンボルを現し、下位8bitで再配置の種類を表します。再配置されるシンボルは、シンボルテーブルのインデックスを確認することで識別することができます。 .rela.*セクションに特有のr_addendは、.rel.*セクションによって再配置情報が管理される際に、再配置される場所に設定されているベース値が格納されています。

.dynamicセクション

.dynamicセクションは、動的リンカによって動的リンクの初期の段階で参照されます。.dynamicセクショは、Elf64_Dynの配列となっていて、Elf64_Dynはelf.hにおいて下記の様に宣言されています。

typedef struct
{
Elf64_Xword d_tag;
        union
        {
                Elf64_Xword d_val;
                Elf64_Addr  d_ptr;
        }d_un;
}Elf64_Dyn

d_tagに紐付けられた値がd_unに格納されていて、動的リンカはこれらの値を参照することで動的リンクを行います。

.init_array/.fini_arrayセクション

.init_array及び.fini_arrayには、それぞれコンストラクタとデストラクタへのポインタが格納されています。コンストラクタやデストラクタが存在しない場合にはframe_dummy及び__do_global_dtors_auxへのポインタが格納されます。

今後

前回の更新からだいぶ時間が経ってしまいましたが、どうにかセクション関連の記事を出すことができました。次はとうとうプログラムヘッダになりますが、年内には出したいと考えています…。
勢いで命名したSagradaFamiliaの方も時間を見つけてはちょこちょこ進めてますが、一体いつになるのやら…。