LD_PRELOADで関数フックしてみよう!

 「たのしいバイナリの歩き方」の4章でWindows環境におけるAPIフックについて解説しました。今回はLinux環境についても解説しましょう。

 Linux環境(Ubuntu 12.04.2)ではLD_PRELOADという環境変数を使うことで似たようなことが可能です。LD_PRELOADでググってみるとサンプルコードがいくつも見つかります。

 試しにwriteをフックする例を示しましょう。

// test.c
// $ gcc test -o test.c

#include <string.h>
#include <unistd.h>

int main()
{
    char s[] = "Hello World!\n";
    write(1, s, strlen(s));
    return 0;
}
$ gcc test.c -o test
$ ldd test
    linux-gate.so.1 =>  (0xb7771000)
    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75c1000)
    /lib/ld-linux.so.2 (0xb7772000)
// hook.c
// $ gcc -fPIC -shared -o hook.so hook.c -ldl

#include <unistd.h>
#include <dlfcn.h>
#include <stdio.h>

int g_first = 0;
void *g_h;
ssize_t (*g_f)(int, const void *, size_t);

ssize_t write(int fd, const void *buf, size_t count)
{
    if(g_first == 0){
        g_first = 1;
        g_h = dlopen("libc.so.6", RTLD_LAZY);
        g_f = dlsym(g_h, "write");
    }
    printf("call write\n");
    return (*g_f)(fd, buf, count);
}
$ gcc -fPIC -shared -o hook.so hook.c -ldl
$ LD_PRELOAD=./hook.so ./test
call write
Hello World!

 LD_PRELOADに設定した.soファイルが持つ同名関数は優先して実行されます。なのでlibc.so.6が持つwriteを無視してhook.soのwriteが実行されました(結果call writeが表示)。そのhook.soのwrite内であらためてlibc.so.6のwriteを呼び出せばフック完了です(通常どおりHello World!が表示)。

 このようにLinuxのLD_PRELOADはWindowsよりシンプルにAPIフックを実現します。