残念ながら、バイナリ オブジェクト、ダイナミック リンカ、またはダイナミック ローダーを変更しないと、これを達成することはできません。とにかく、これは非常に難しい作業です。
オプション 1 - ELF 操作
各 ELF 実行可能ファイルは、実際のコード/データ/シンボル文字列/... を含むセクションと、ローダーがコードをメモリ内のどこにロードするか、この ELF が公開するシンボル、必要なシンボルなどを決定するのに役立つセグメントから作成されます。他の場所、特定のコード/データをロードする場所など
次のように入力して、バイナリのセグメントを観察できます。
readelf -l [あなたのバイナリ]
出力は次のようになります (バイナリとして ls を選択しました)。
[ishaypeled@ishay-dev bin]$ readelf -l --wide ./ls
Elf file type is EXEC (Executable file)
Entry point 0x4048bf
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000040 0x0000000000400040 0x0000000000400040 0x0001f8 0x0001f8 R E 0x8
INTERP 0x000238 0x0000000000400238 0x0000000000400238 0x00001c 0x00001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x01b694 0x01b694 R E 0x200000
LOAD 0x01bdf0 0x000000000061bdf0 0x000000000061bdf0 0x000864 0x0016d0 RW 0x200000
DYNAMIC 0x01be08 0x000000000061be08 0x000000000061be08 0x0001f0 0x0001f0 RW 0x8
NOTE 0x000254 0x0000000000400254 0x0000000000400254 0x000044 0x000044 R 0x4
GNU_EH_FRAME 0x01895c 0x000000000041895c 0x000000000041895c 0x00071c 0x00071c R 0x4
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
GNU_RELRO 0x01bdf0 0x000000000061bdf0 0x000000000061bdf0 0x000210 0x000210 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got
次に、この出力を調べてみましょう。
最初の表 (プログラム ヘッダー):
[タイプ] - セグメント タイプ、このセクションの目的
[オフセット] - このセグメントが始まるファイル内のオフセット
[VirtAddr] - プロセス アドレス空間でこのセクションをロードする場所 (このセグメントをロードする必要がある場合は、すべてがロードされるわけではありません)
[PhysAddr] - 私が遭遇した最新のすべての OS の VirtAddr と同じ
[FileSiz] - ファイル上のこのセクションの大きさ。これはセクションへのリンクです - 現在のセグメントは Offset から Offset+FileSiz
[MemSiz] の範囲のすべてのセクションで構成されています - 仮想メモリ内のこのセクションの大きさ (これはファイルのサイズと同じである必要はありません!ファイルのサイズを超える場合、超過分は 0 に設定されます)
[Flg] - 許可フラグ、R 読み取り E 実行 W 書き込み。[Align] - メモリ内で必要なメモリ アラインメント。
ここでは、タイプ LOAD (PT_LOAD) のセグメントに焦点を当てています。これらのセグメントは、セクションからデータをグループ化し、ローダーにそれらをプロセス アドレス空間のどこに配置するかを指示し、それらのパーミッションの指定を決定します。
セクションからセグメントへのマッピング表で、便利なセクションからセグメントへのマッピングを確認できます。
2 つの LOAD セグメント 2 と 3
を観察してみましょう。セグメント 2 には読み取り権限と実行権限があり、(とりわけ) .text セクションと .rodata セクションにまたがっていることがわかります。
したがって、ELF 操作を使用して目的を達成するには、次のようにします。
- ファイル内で関数を作成するバイナリ データを見つけます (readelf ユーティリティが役に立ちます)。
- ELF ヘッダーを変更することにより (これを自動的に行うツールを知りません。おそらく独自に作成する必要があります)、.text セクションを含むセグメントを 2 つの連続した LOAD セグメントに分割し、関数コードを除外します。
- ELF ヘッダーを変更して、2 つの関数のみを含む新しい LOAD セグメントを作成します。
- 古い関数の場所へのすべての参照 (存在する場合) を新しいものに更新します。
ここまで読んですべてを理解したなら、これが非常に退屈で、実際のケースではほぼ不可能な作業であることを理解する必要があります。
オプション 2 - 動的リンカー操作
上記の例のINTERP セグメント タイプに注意してください。これは、使用する動的リンカーを指定する ASCII 文字列です。
動的リンカーの役割は、セグメントを解析し、実行時のシンボルの解決、.so ファイルからのセグメントの読み込みなど、すべての動的操作を実行することです。
ここで可能な操作は、ダイナミック リンカ コードを変更して (注: これはシステム全体の変更です!)、関数のバイナリ データをプロセス アドレス空間の特定のメモリ アドレスにロードすることです。このアプローチにはいくつかの欠点があることに注意してください。
- 動的リンカーへの変更が必要です
- ELF ファイル内の関数へのすべての参照を更新する必要があります。
オプション 3 - 動的ローダー操作
オプション 2 とほぼ同じですが、動的リンカの代わりに ld ライブラリ機能を変更します。
結論
あなたがまさにやりたいことは非常に難しく、実に退屈な作業です。私は現在、既存の共有オブジェクト ファイルに任意の関数を挿入できるツールの開発に取り組んでおり、少なくとも数週間はうまく機能することを保証します。
あなたが望むものを達成する別の方法はありませんか?これら 2 つの関数が別のアドレスに必要なのはなぜですか? おそらくもっと簡単な解決策があります...