FreeBSDのUptimeが偽装できないかを検討する

Contents:

初めに(やりたいこと)

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;

やはり、合っていたみたいです。では、これを書き換えればいいよね、ということで、

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21

#include <stdio.h>
#include <time.h>

struct timespec tr; // read
struct timespec tw; // write

int main(){

 clock_gettime(CLOCK_MONOTONIC, &tr);
 printf("%d\n", tr.tv_sec);

 tw.tv_sec = 0;
 tw.tv_nsec = 0;
 clock_settime(CLOCK_MONOTONIC, &tw);

 clock_gettime(CLOCK_MONOTONIC, &tr);
 printf("%d\n", tr.tv_sec);

 return 0;
}

すると、

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とうつ