共有ライブラリ(.so) の関数呼び先をフック


前回の内容でとりあえずシンボル情報にアクセスする糸口がわかりました。
これを利用して、共有ライブラリの関数フックを考えてみたいと思います。

sample.app が共有ライブラリ libfunc.so と libbar.so を利用しているとします。そしてこれらが下記の図に示すような呼び出し関係を持っているとします。

original_call

しかし、このlibbar.so の関数にはバグがあるようで、しかも今すぐには修正できないような状況だったとします。自分ではなく他の人の作成したライブラリを使うような場合、こういうことってありますよね・・・。
 そこで、関数そのものを一時的であれ自分ハックをいれたようなものに置き換えて今をしのぐ、というようなことを考えます。ここで関数フックの登場となります。そして下記に示すような呼び出し関係を構築することを目標とします。

modified_call

前回の内容と対象のsoがロードされているベースアドレスから、soがもっているセクションヘッダを取得します。そしてそのセクションヘッダを解釈して、置き換えたい関数のPLTの場所を特定します。PLTの場所が判明し、そこの内容をフック先の関数アドレスを入れておけば、この図のような呼び出しのリダイレクトが実現できます。

※ ひとまず 32bit 実行環境であると想定して話を進めます。
また、共有.soについては PIC で生成されているものとします。

やり方としては、まず “.rel.plt”セクションのセクションヘッダを取得します。
このヘッダの sh_addr と 共有ライブラリのベースアドレスを加算したアドレスに、 Elf32_Rel構造体の情報が格納されています。個数は sh_size がバイトサイズなので、1要素単位で除算して求めておきます。
Elf32_Rel.r_info メンバを ELF32_R_SYM マクロを使って、シンボルのインデックスを取得することができます。シンボルのインデックスは Elf32_Sym 配列の序数として使えるので、関連するストリングテーブルを取得しておけば関数の名前が求まります。関数の名前を比較して、フックしたい該当関数かどうかを判定します。
 続いて、PLTの場所ですがこれは Elf32_Rel.r_offsetメンバを参照することでわかります。これもまたベースアドレスからのオフセットになっています。これでアドレスが特定できます。実際にそのアドレスの中身を見ると関数先頭のアドレスが格納されています。動的リンクではこのテーブルのアドレスを見て関数ジャンプという仕組みでおこなっているため、このアドレスを自分の関数をいれておけばOKという仕掛けでフックを実現します。

文章ではちょっとわかりにくさを感じたので、これらの位置関係を図示してみました。こんな感じです。
pos_rel_plt

通常PLT経由のジャンプにおいては、PLTの該当場所に記録されたアドレスを参照して jmp命令を実行するというコードになっています。初期状態ではこのPLTには jmp命令の次の行を示しており、そこから実際の関数はどこなのかを調査するコードが実行されます。このコードが実行されると、PLTの場所に見つけ出された関数のアドレスが記録されます。次回以降はこの調査コードが実行されずに関数を呼び出すことができるという仕掛けになっています。

このPLTジャンプの内容については、ここ(共有ライブラリーはどのように動作するか)がわかりやすいかも。

これが動作する秘密は、PLT(Procedure Linkage Table)と呼ばれるデータの塊、つまりプログラムが呼ぶ全てのファンクションをリストアップした、プログラム中のテーブルです。プログラムが開始する時に、ファンクションがロードされたアドレスをランタイム・リンカーに対して問い合わせるコードを、PLTは各ファンクションに対して持っています。そうするとプログラムは、テーブル中のそのエントリーを取り込み、そこにジャンプします。それぞれのファンクションが呼ばれるにつれ、それぞれのファンクションに対するPLTでのエントリーは、ロードされたファンクションへの直接ジャンプに単純化されます。

余談

関数フックについては、同じ関数名で実装した別の .so を作成し、これをLD_PRELOAD環境変数にセットして、プログラムを実行するという方法があります。Linuxでは割と定番の関数フックです。今回のような内容は、LD_PRELOADを使えば簡単にできるのですが、アプリを使う側への手間をかけたくない、コンパイル(&リンク)時に全ての解決をしてしまいたい、というポリシーのため調べてみたという経緯です。

スポンサーリンク

シェアする

  • このエントリーをはてなブックマークに追加

フォローする


コメント

  1. […] 前回は共有ライブラリ(.so)での関数フックの話でした。そしてこれはセクションヘッダがある前提で rel.plt セクションを見つけました。今回はセクションヘッダを参照しないでPLTの場所 […]