FreeBSDのUptimeが偽装できないかを検討する === # 初めに(やりたいこと) uptimeが3000daysを超えるように見せたい ``` kanai@:/home/kanai % uptime 10:35PM up 9 mins, 3 users, load averages: 0.40, 0.84, 0.54 ^^^^^^^^^ココ! ``` # 調査 uptimeコマンドはwコマンドのhard-linkとわかります。 ``` kanai@:/usr/src/usr.bin % ls -1i /usr/bin/w /usr/bin/uptime 1134480 /usr/bin/uptime 1134480 /usr/bin/w ``` そのため、追うべきコードは、 ``` /usr/src/usr.bin/w ``` になります。 あたりをつけたいので、kdumpします。 ``` kanai@:/home/kanai % ktrace w kanai@:/home/kanai % kdump | grep time | grep CALL 89994 w CALL gettimeofday(0xbfbfdbb4,0) 89994 w CALL clock_gettime(0xd,0xbfbfdbd8) 89994 w CALL clock_gettime(0x4,0xbfbfdc90) 89994 w CALL clock_gettime(0,0xbfbfbeec) 89994 w CALL clock_gettime(0,0xbfbfbeec) 89994 w CALL clock_gettime(0,0xbfbfbeec) ``` clock_gettimeがあやしいです。第一引数に違和感があります。manをひきます。 ``` int clock_getres(clockid_t clock_id, struct timespec *tp); ``` ということで/usr/include/time.hを見ます。 ``` #define CLOCK_MONOTONIC 4 CLOCK_MONOTONIC which increments in SI seconds #define CLOCK_SECOND 13 CLOCK_SECOND which returns the current second without performing a full time counter query ``` CLOCK_MONOTONICというのがよくわかりません。あきらめて、/usr/src/usr.bin/w/w.cを見ます。 ``` /* * Print how long system has been up. */ if (clock_gettime(CLOCK_MONOTONIC, &tp) != -1) { uptime = tp.tv_sec; ``` やはり、合っていたみたいです。では、これを書き換えればいいよね、ということで、 .. literalinclude:: Src/clock_settime.c :language: c :linenos: すると、 :: kanai@:/home/kanai % ./a.out 2258 2258 CLOCK_MONOTONICは書き込み不可属性に見えます。/usr/src/sys/kern/kern_time.cを追います。 :: kern_clock_settime()より if (clock_id != CLOCK_REALTIME) return (EINVAL); なるほど。clock_idがCLOCK_REALTIME == 0 の場合はsettimeできないようです。 では、CLOCK_MONOTONICは何を追っているのでしょうか? :: kern_clock_gettime()より case CLOCK_REALTIME: /* Default to precise. */ case CLOCK_REALTIME_PRECISE: nanotime(ats); break; case CLOCK_MONOTONIC: /* Default to precise. */ case CLOCK_MONOTONIC_PRECISE: case CLOCK_UPTIME: case CLOCK_UPTIME_PRECISE: nanouptime(ats); break; nanouptime()を見ます。/usr/src/sys/kern/kern_tc.cにありました。 :: /* * Functions for reading the time. We have to loop until we are sure that * the timehands that we operated on was not updated under our feet. See * the comment in for a description of these 12 functions. */ binuptime(struct bintime *bt) { struct timehands *th; u_int gen; do { th = timehands; gen = th->th_generation; *bt = th->th_offset; bintime_addx(bt, th->th_scale * tc_delta(th)); } while (gen == 0 || gen != th->th_generation); } void nanouptime(struct timespec *tsp) { struct bintime bt; binuptime(&bt); bintime2timespec(&bt, tsp); } bintime_addxはsys/time.hで :: static __inline void bintime_addx(struct bintime *bt, uint64_t x) { uint64_t u; u = bt->frac; bt->frac += x; if (u > bt->frac) bt->sec++; } とのことです。さて、よく見ると、th = timehandsが気になるので、上の定義をみると、 :: static struct timehands *volatile timehands = &th0; static struct timehands th0; static struct timehands th0 = { &dummy_timecounter, 0, (uint64_t)-1 / 1000000, 0, {1, 0}, {0, 0}, {0, 0}, 1, &th1 }; となっているので、どうも、th0がtimehandsでuptimecounterっぽいことがわかります。 もうちょっと追います。 :: static uint64_t tc_cpu_ticks(void) { static uint64_t base; static unsigned last; unsigned u; struct timecounter *tc; tc = timehands->th_counter; u = tc->tc_get_timecount(tc) & tc->tc_counter_mask; if (u < last) base += (uint64_t)tc->tc_counter_mask + 1; last = u; return (u + base); } これでカウントして、tc_windup()で処理しているっぽいのですが、かなりまじめに追わないと行けなそうです。 他の手がないか確認します。tc_init()を見ていると、SYSCTL_STATIC_CHILDRENがあります。なるほど。 その手があったかsysctl。それっぽいのがあるか見ます。 :: kanai@:/ % sysctl -a | grep time | head -1 kern.boottime: { sec = 1366723591, usec = 385546 } Tue Apr 23 22:26:31 2013 これに書き込めれば勝ち!なのですが、 :: static int sysctl_kern_boottime(SYSCTL_HANDLER_ARGS); SYSCTL_PROC(_kern, KERN_BOOTTIME, boottime, CTLTYPE_STRUCT|CTLFLAG_RD, NULL, 0, sysctl_kern_boottime, "S,timeval", "System boottime"); で、CTLFLAG_RWは立っていませんでした。 中間まとめ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ uptimeはkern_tc.cのtimehands構造体がキーポイントとなるが、 それに対する直接アクセスのsyscallはない また、cpuが直接インクリメントしている構造体なので、かなりまじめに読まないと読めない。 ここまで来るとつらくなってくるので、kernelをdebugモードでコンパイルし直して、 gdbでattachしながら試行錯誤することにします。 kgdb ^^^^^^ 対象のホストでkernelを再構築します。このとき、KDBをオプションで指定します。 :: cd /usr/src/sys/i386/ sudo cp GENERIC HOGETAN sudo vi HOGETAN kanai@:/usr/src/sys/i386/conf % diff GENERIC HOGETAN 24c24 < ident GENERIC --- > ident HOGETAN 67a68,69 > options GDB > options DDB sudo config -g HOGETAN cd ../compile/HOGETAN make cleandepend && make depend sudo make sudo make install reboot boot -d します。 で、ここで、VirtualBoxを以下のように仕込みます。 ターゲット対象のFreeBSD(Kernelを再コンパイルしたやつ) :: 設定 > システム > マザーボード > IO APICを有効化をチェック 設定 > シリアルポート ポート番号:Com1 ポートモード:ホストにパイプ パイプ作成:OFF ポート/ファイルパス:\\.\pipe\com_1 適当なFreeBSD :: 設定 > システム > マザーボード > IO APICを有効化をチェック 設定 > シリアルポート ポート番号:Com1 ポートモード:ホストにパイプ パイプ作成:ON ポート/ファイルパス:\\.\pipe\com_1 0x80にする gdbとうつ