前回は共有ライブラリ(.so)での関数フックの話でした。そしてこれはセクションヘッダがある前提で rel.plt セクションを見つけました。今回はセクションヘッダを参照しないでPLTの場所を特定し、関数フックを実現する案を記載してみようと思います。
そもそも elf実行体がメモリにロードされて実行されている状況では、elf実行体そのものにセクションヘッダがあったとしてもメモリにロードされていません。実行体そのもののファイルを読み込んでセクションヘッダを解析して特定する方法もあるかと思います。前回のsoの場合においては、そのようにして手元ではサンプルを組んでいました。しかし、メモリにロードされている情報からうまく特定できるのであれば無駄なメモリを使うことなく、ディスクIOも減らせて何かと都合がよいです。またストリップ化されているsoについてはセクションヘッダなしで何とか特定しなければなりません。
つまり、セクションヘッダなしで前回と同じようなことを実現するのを目的としたいと思います。今回は下記のような呼び出しをフックする方向を考えています。
elf実行体からある共有ライブラリの関数を呼び出しており、その関数のフック先は別に自分自身の内部に用意してあるというものです。フック処理を適用して呼び出し先を変更します。
基本的にはローダーの仕組みを調査して、自分に必要となる部分だけ処理していく形となります。シンボルの情報も動的リンクに必要となるため、ローダーが必要とする情報のため、かならずメモリには乗ることになります。まずはElfヘッダを読み込みます。その後、ローダーが処理していくプログラムヘッダというものを自分で解析&処理していくことになります。
プログラムヘッダの中でも、今回必要となるのが PT_DYNAMIC のタイプで示されるデータになります。ここの中から、Elf32_Dyn構造体で表現されるデータにアクセスします。メモリにロードされている場合、PT_DYNAMICのプログラムヘッダ構造体のメンバ p_vaddr のアドレスに Elf32_Dyn の構造体リストが格納されています。
この Elf32_Dyn構造体リストを調査すると、この中に DT_*** といったタイプで示されるデータが格納されています。このDT_*** について今必要となるのは、DT_SYMTAB, DT_STRTAB, DE_PLTRELSZ,DT_RELENT, DT_JMPREL といったものです。これらの情報から、シンボルの情報を得ることができます。 DT_JMPREL の示すアドレスから Elf32_Rel構造体経由、Elf32_Sym構造体を参照、そしてシンボルのストリングテーブルアドレスにオフセットを加算して、シンボルの名前を知ることができます。
- DT_JMPREL から Elf32_Rel 構造体配列がわかる。
- Elf32_Rel 構造体の r_info からシンボルのインデックスが判明する。
- Elf32_Sym 構造体の st_name でシンボル名のオフセットが入っている。
ここまで来るとあとは前回と同様に .rel.plt のように関数アドレスが入っているべき場所が判明します。ここにフック先である関数アドレスを格納すれば、関数フック(リダイレクト)の機能が実現します。
まとめ
ローダーの仕組みがわかっている人や自力でローダーを作れる人には簡単な内容だと思います。自分では今回初めてこのあたりの仕組みをやりたいこと実現のために調べてたどり着きました。ですが、ローダーが何を処理して実際にアプリケーションが起動するのかの一部分でも垣間見ることができたのはとても勉強になりました。
これを読んで、やってみようと思う人には gdb で各アドレスを実際に覗きながら、また逆アセンブルなどを見ながら、作業していくことをおすすめします。いきなり組み上げて動かない場合にはどこが悪いのか見当がつきにくいと思います。