動的リンクされたプログラムでは、同じ関数が複数のライブラリで定義されている場合、最初に見つかった関数が利用される。
環境変数 LD_PRELOAD で指定した共有ライブラリは最優先で読み込まれるため、簡単にプログラムの挙動を変えることができる。
実験用のプログラム
まずは乱数を10個表示するだけの簡単なプログラム(random_num.c)を用意。
/* random_num.c */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
srand(time(NULL));
int i = 10;
while(i--) printf("%d\n",rand());
return 0;
}
実験用プログラムの実行
コンパイルする
$ gcc random_num.c -o random_num $ ldd random_num linux-vdso.so.1 => (0x00007fffbddff000) libc.so.6 => /lib64/libc.so.6 (0x00007f89557e8000) /lib64/ld-linux-x86-64.so.2 (0x00007f8955b84000) $ nm -D /lib64/libc.so.6 | grep rand$ 00000000000369d0 T rand 0000000000036490 W srand
GNU C Library(libc.so.6) とリンクされており、 実験用で利用する関数 rand もある。nm でシンボルを出力すると、 rand を確認できる。
実際に実行するとランダムに数字が10個表示される。
$ ./random_num 849453208 1166619329 1528092543 1204148216 947393683 1613042737 1981359507 1489207667 381293129 346436639
rand を上書きする
次に、本来はランダムな整数を返す rand 関数がつねに固定の整数 42 を返すように書き換え他プログラムを用意(unrandom.c)。
/* unrandom.c */
int rand(){
return 42; //the most random number in the universe
}
共有ライブラリとしてコンパイルする
$ gcc -shared -fPIC unrandom.c -o unrandom.so
$ nm -D ./unrandom.so
w _Jv_RegisterClasses
00000000002007c8 A __bss_start
w __cxa_finalize
w __gmon_start__
00000000002007c8 A _edata
00000000002007d8 A _end
0000000000000578 T _fini
0000000000000420 T _init
000000000000052c T rand
LD_PRELOAD でプログラムの実行
環境変数 LD_PRELOAD で作成した共有ライブラリ を指定し、同じプログラムを実行してみる。
$ LD_PRELOAD=./unrandom.so ldd random_num linux-vdso.so.1 => (0x00007fff54f86000) ./unrandom.so (0x00007f841064b000) libc.so.6 => /lib64/libc.so.6 (0x00007f84102af000) /lib64/ld-linux-x86-64.so.2 (0x00007f841084d000) $ LD_PRELOAD=./unrandom.so ./random_num 42 42 42 42 42 42 42 42 42 42
rand 関数が固定値を返す自作ライブラリの関数に書き換わっているため、プログラムの実行結果もランダム性がなくなっているのがわかる。
rand を上書きする(元の関数も実行)
先ほどの例では関数の中身を完全に書き換えていたので、もう少しソフトに、本来の関数を実行しつつその前後で追加処理を入れたのが次のプログラム(myrandom.c)。
/* myrandom.c */
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
typedef int (*orig_rand_f_type)();
int rand(){
printf("custom rand is called\n");
orig_rand_f_type orig_rand;
orig_rand = (orig_rand_f_type)dlsym(RTLD_NEXT, "rand");
return orig_rand();
}
dlsym(RTLD_NEXT, "rand") でデフォルト(RTLD_DEFAULT)の次に見つかる rand 関数を探し出し、実行している。
メッセージを出力している 箇所(printf("custom rand is called\n");)が追加処理。
同じようにコンパイルして実行してみる。
$ gcc -shared -fPIC myrandom.c -o myrandom.so -ldl $ LD_PRELOAD=./myrandom.so ./random_num custom rand is called 1096863217 custom rand is called 505523853 custom rand is called 1514541433 custom rand is called 474180237 custom rand is called 1994792782 custom rand is called 288074905 custom rand is called 1366621408 custom rand is called 1929084141 custom rand is called 1667463609 custom rand is called 1873885528
再びランダムな整数が表示されるようになり、また、追加したデバッグメッセージも表示されている。
References
- 実務家向けに必要なことはだいたいここに書かれている https://rafalcieslak.wordpress.com/2013/04/02/dynamic-linker-tricks-using-ld_preload-to-cheat-inject-features-and-investigate-programs/
- よりテクニカルな点は man を読む http://man7.org/linux/man-pages/man8/ld.so.8.html
- Linux.conf.au 2009 : Fun with LD_PRELOAD by Kevin Pulo http://lca2009.linux.org.au/slides/172.pdf