読者です 読者をやめる 読者になる 読者になる

ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

Cレベルのライブラリコールを“Hijack”してみる その1。

Rubyの、 Time.now は最終的には clock_gettime(3) を呼び出しているそうな。

  • masterの当該箇所
  • Time.nowTime.new を無引数で呼び出しているに過ぎない
  • ここで、 Time#initialize の実態は time_init
  • time_init は引数がない場合 time_init_0 を呼ぶ
  • time_init_0 は timespec 構造体のポインタを引数に rb_timespec_now を呼ぶ
  • rb_timespec_nowclock_gettime(3) が使えるシステムであればそれを呼ぶ。そうでない場合 gettimeofday(2) らしい…

ここで、以下のような小さなCのコードを書く。

/* man になかったけどこの宣言がないと RTLD_NEXT が定義されなかった */
#define _GNU_SOURCE

#include <time.h>
#include <dlfcn.h>

int clock_gettime(clockid_t clk_id, struct timespec *tp){
  int (*real_clock_gettime)(clockid_t, struct timespec *);

  /* 本来定義されるはずだった clock_gettime 関数を持ってくることができる */
  /* RTLD_NEXT はライブラリ検索順序の次の場所を示している。 super みたい */
  real_clock_gettime = dlsym(RTLD_NEXT, "clock_gettime");

  /* 影響範囲をなるべく狭めるよう、 CLOCK_REALTIME でないものを指定された場合は、本来の関数を呼ぶようにする */
  if(clk_id == CLOCK_REALTIME) {
    tp->tv_sec = 0;
    tp->tv_nsec = 0;
    return 0;
  } else {
    return real_clock_gettime(clk_id, tp);
  }
}

共有ライブラリを作る。 TLPI に書いてある通りの手順。

$ gcc -g -c -fPIC -Wall jack_clock_gettime.c
$ gcc -g -shared -o libjack.so jack_clock_gettime.o
$ file libjack.so
libjack.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=a0e098a9ff43bf30a7260ffffb8d39abb5a2a97e, not stripped

この共有ライブラリがあると…

$ ruby -e "p Time.now"
2017-02-13 15:53:03 +0900
$ LD_PRELOAD=./libjack.so ruby -e "p Time.now"
1970-01-01 09:00:00 +0900

この通り、 Time.now の結果をUNIXエポックにすることができた。モノによるけれど、ライブラリコールを一つ上書きことは実はそんなに難しくはないことがわかる。


LD_PRELOAD は強力な仕組みである。本質的にこの仕組みを利用した MySQLのCVE-2016-6662 は記憶に新しい。

他にも、これは直接 LD_PRELOAD を利用しているわけではないが、例えば valgrind においては、 free/malloc などを差し替え、それらの発行回数を数えて、メモリリークの検知に使われている 参考1 参考2 参考3 *1

ということで、これをじゃな…

その2に続く(本当?)。


参考

*1:ということなんだろうけどコードが複雑で追いきれず、動的ロードに詳しいフレンズの情報があると嬉しい…