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 <sys/time.h> 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とうつ