BSD界隈四方山話

第91回DTraceの使い方 その11

セキュリティ

前回はシステムコールをフックしてsshのパスワード入力のようすをモニタリングするというサンプロを取り上げました。今回はそれをさらに進めて、シェルの実行をモニタリングして、そこに入力された文字列と、出力されたコマンド結果をモニタリングするサンプルを紹介します。

次のスクリプトがそれを実施するためのスクリプトです("DTrace Dynamic Tracing In Oracle Solaris, Mac OS X & FreeBSD", by Brendan Gregg and Jim Mauro"のP.878から掲載されているものです⁠⁠。シェルスクリプトとDTraceスクリプトの双方が使われていて、ソースコードのオリジナルはestibi/DTraceToolkit/Apps/shellsnoop|GitHubに掲載されています。このスクリプトのままだとFreeBSDでは実行できないので、FreeBSDで実行できるように一部内容を書き換えてあります。

リスト shellnoop
#!/bin/sh

##############################
# --- Process Arguments ---
#
opt_pid=0; opt_uid=0; opt_time=0; opt_timestr=0; opt_quiet=0; opt_debug=0
filter=0; pid=0; uid=0

while getopts dhp:qsu:v name
do
    case $name in
    d)    opt_debug=1 ;;
    p)    opt_pid=1; pid=$OPTARG ;;
    q)    opt_quiet=1 ;;
    s)    opt_time=1 ;;
    u)    opt_uid=1; uid=$OPTARG ;;
    v)    opt_timestr=1 ;;
    h|?)    cat <<-END >&2
        USAGE: shellsnoop [-hqsv] [-p PID] [-u UID]
               shellsnoop        # default output
                        -q        # quiet, only print data
                        -s        # include start time, us
                        -v        # include start time, string
                        -p PID        # process ID to snoop
                        -u UID        # user ID to snoop
        END
        exit 1
    esac
done

if [ $opt_quiet -eq 1 ]; then
    opt_time=0; opt_timestr=0
fi
if [ $opt_pid -eq 1 -o $opt_uid -eq 1 ]; then
    filter=1
fi


#################################
# --- Main Program, DTrace ---
#
dtrace -n '
 /*
  * Command line arguments
  */
 inline int OPT_debug     = '$opt_debug';
 inline int OPT_quiet     = '$opt_quiet';
 inline int OPT_pid     = '$opt_pid';
 inline int OPT_uid     = '$opt_uid';
 inline int OPT_time     = '$opt_time';
 inline int OPT_timestr    = '$opt_timestr';
 inline int FILTER     = '$filter';
 inline int PID     = '$pid';
 inline int UID     = '$uid';
 
 #pragma D option quiet
 #pragma D option switchrate=20hz
 
 /*
  * Print header
  */
 dtrace:::BEGIN /OPT_time == 1/
 { 
     printf("%-14s ","TIME");
 }
 dtrace:::BEGIN /OPT_timestr == 1/
 { 
     printf("%-20s ","STRTIME");
 }
 dtrace:::BEGIN /OPT_quiet == 0/
 {
    printf("%5s %5s %8s %3s  %s\n", "PID", "PPID", "CMD", "DIR", "TEXT");
 }

 /*
  * Remember this PID is a shell child
  */
 syscall::execve:entry
 /execname == "sh"   || execname == "ksh"  || execname == "csh"  || 
  execname == "tcsh" || execname == "zsh"  || execname == "bash"/
 {
    child[pid] = 1;
 }
 syscall::execve:entry
 /(OPT_pid == 1 && PID != ppid) || (OPT_uid == 1 && UID != uid)/
 {
    /* forget if filtered */
    child[pid] = 0;
 }

 /*
  * Print shell keystrokes
  */
 syscall::write:entry, syscall::read:entry
 /(execname == "sh"   || execname == "ksh"  || execname == "csh"  ||
  execname == "tcsh" || execname == "zsh"  || execname == "bash")
  && (arg0 >= 0 && arg0 <= 2)/
 {
    self->buf = arg1;
 }
 syscall::write:entry, syscall::read:entry
 /(OPT_pid == 1 && PID != pid) || (OPT_uid == 1 && UID != uid)/
 {
    self->buf = 0;
 }
 syscall::write:return, syscall::read:return
 /self->buf && child[pid] == 0 && OPT_time == 1/
 {
     printf("%-14d ", timestamp/1000);
 }
 syscall::write:return, syscall::read:return
 /self->buf && child[pid] == 0 && OPT_timestr == 1/
 {
    printf("%-20Y ", walltimestamp);
 }
 syscall::write:return, syscall::read:return
 /self->buf && child[pid] == 0 && OPT_quiet == 0/
 {
    this->text = (char *)copyin(self->buf, arg0);
    this->text[arg0] = '\'\\0\'';
 
    printf("%5d %5d %8s %3s  %s\n", pid, curpsinfo->pr_ppid, execname, 
        probefunc == "read" ? "R" : "W", stringof(this->text));
 }
 syscall::write:return
 /self->buf && child[pid] == 0 && OPT_quiet == 1/
 {
    this->text = (char *)copyin(self->buf, arg0);
    this->text[arg0] = '\'\\0\'';
    printf("%s", stringof(this->text));
 }
 syscall::read:return
 /self->buf && execname == "sh" && child[pid] == 0 && OPT_quiet == 1/
 {
    this->text = (char *)copyin(self->buf, arg0);
    this->text[arg0] = '\'\\0\'';
    printf("%s", stringof(this->text));
 }
 syscall::write:return, syscall::read:return
 /self->buf && child[pid] == 0/
 {
    self->buf = 0;
 }

 /*
  * Print command output
  */
 syscall::write:entry, syscall::read:entry
 /child[pid] == 1 && (arg0 == 1 || arg0 == 2)/
 {
    self->buf = arg1;
 }
 syscall::write:return, syscall::read:return
 /self->buf && OPT_time == 1/
 {
     printf("%-14d ", timestamp/1000);
 }
 syscall::write:return, syscall::read:return
 /self->buf && OPT_timestr == 1/
 {
    printf("%-20Y ", walltimestamp);
 }
 syscall::write:return, syscall::read:return
 /self->buf && OPT_quiet == 0/
 {
    this->text = (char *)copyin(self->buf, arg0);
    this->text[arg0] = '\'\\0\'';
 
    printf("%5d %5d %8s %3s  %s", pid, curpsinfo->pr_ppid, execname,
        probefunc == "read" ? "R" : "W", stringof(this->text));
 
    /* here we check if a newline is needed */
    this->length = strlen(this->text);
    printf("%s", this->text[this->length - 1] == '\'\\n\'' ? "" : "\n");
    self->buf = 0;
 }
 syscall::write:return, syscall::read:return
 /self->buf && OPT_quiet == 1/
 {
    this->text = (char *)copyin(self->buf, arg0);
    this->text[arg0] = '\'\\0\'';
    printf("%s", stringof(this->text));
    self->buf = 0;
 }

 /*
  *  Cleanup
  */
 syscall::exit:entry
 {
    child[pid] = 0;
 }
'

実行すると次のような結果が得られます。新しくシェル(sh、ksh、csh、tcsh、zsh、bash)を実行するとそのシェルをトレースし、そのシェルに対する入力と、そこからの出力をモニタリングしています。

 shellnoopの実行例
% sudo ./shellsnoop
  PID  PPID      CMD DIR  TEXT
14843   757       sh   W  $
14843   757       sh   R  d
14843   757       sh   W  d
14843   757       sh   R  a
14843   757       sh   W  a
14843   757       sh   R  t
14843   757       sh   W  t
14843   757       sh   R  e
14843   757       sh   W  e
14843   757       sh   R

14843   757       sh   W

14844 14843     date   W  Mon Mar 13 17:50:13 JST 2017
14843   757       sh   W  $
14843   757       sh   R  c
14843   757       sh   W  c
14843   757       sh   R  a
14843   757       sh   W  a
14843   757       sh   R  l
14843   757       sh   W  l
14843   757       sh   R

14843   757       sh   W

14845 14843      cal   W       March 2017
14845 14843      cal   W  Su Mo Tu We Th Fr Sa
14845 14843      cal   W            1  2  3  4
14845 14843      cal   W   5  6  7  8  9 10 11
14845 14843      cal   W  12 13 14 15 16 17 18
14845 14843      cal   W  19 20 21 22 23 24 25
14845 14843      cal   W  26 27 28 29 30 31
14845 14843      cal   W
14843   757       sh   W  $
14843   757       sh   R  l
14843   757       sh   W  l
14843   757       sh   R  s
14843   757       sh   W  s
14843   757       sh   R

14843   757       sh   W

14846 14843       ls   W  FREEBSD-20170306.tgz    sources            typescript.txt
14846 14843       ls   W  Makefile        typescript.gh        typescript.xml
14846 14843       ls   W  commands        typescript.html
14843   757       sh   W  $
14847  9165     bash   W  [daichi@virt ~/Documents/lwt/20170313]$
14847  9165     bash   R  p
14847  9165     bash   W  p
14847  9165     bash   R  w
14847  9165     bash   W  w
14847  9165     bash   R  d
14847  9165     bash   W  d
14847  9165     bash   R
14847  9165     bash   W

14847  9165     bash   W  /Users/daichi/Documents/lwt/20170313

14847  9165     bash   W  [daichi@virt ~/Documents/lwt/20170313]$
14847  9165     bash   R  a
14847  9165     bash   W  a
14847  9165     bash   R 
14847  9165     bash   W
14847  9165     bash   R  c
14847  9165     bash   W  c
14847  9165     bash   R  a
14847  9165     bash   W  a
14847  9165     bash   R  l
14847  9165     bash   W  l
14847  9165     bash   R
14847  9165     bash   W

14848 14847      cal   W       March 2017
14848 14847      cal   W  Su Mo Tu We Th Fr Sa
14848 14847      cal   W            1  2  3  4
14848 14847      cal   W   5  6  7  8  9 10 11
14848 14847      cal   W  12 13 14 15 16 17 18
14848 14847      cal   W  19 20 21 22 23 24 25
14848 14847      cal   W  26 27 28 29 30 31
14848 14847      cal   W
14847  9165     bash   W  [daichi@virt ~/Documents/lwt/20170313]$
14847  9165     bash   R  q
14847  9165     bash   W  q
14847  9165     bash   R  u
14847  9165     bash   W  u
14847  9165     bash   R  i
14847  9165     bash   W  i
14847  9165     bash   R  t
14847  9165     bash   W  t
14847  9165     bash   R
14847  9165     bash   W

14849 14847     bash   W  bash: quit: command not found

14847  9165     bash   W  [daichi@virt ~/Documents/lwt/20170313]$
14847  9165     bash   R  e
14847  9165     bash   W  e
14847  9165     bash   R  x
14847  9165     bash   W  x
14847  9165     bash   R  i
14847  9165     bash   W  i
14847  9165     bash   R  t
14847  9165     bash   W  t
14847  9165     bash   R
14847  9165     bash   W

14847  9165     bash   W  exit

14843   757       sh   R  e
14843   757       sh   W  e
14843   757       sh   R  x
14843   757       sh   W  x
14843   757       sh   R  i
14843   757       sh   W  i
14843   757       sh   R  t
14843   757       sh   W  t
14843   757       sh   R

14843   757       sh   W

^C

%

このサイズのスクリプトになってくるとぱっと見で何をしているか理解するのはちょっと手間取りますが、それほど複雑なことをしているわけではありません。捕捉対象となるシェルをプロセス番号で記録し、その番号を判別データとして使って対象となるプロセスのみをトレースしています。配列の使い方や収集したデータの出力方法など、実用的で参考になるスクリプトです。こうしたことができるということがわかると、なんとなくDTraceでできることが見えてくるんじゃないでしょうか。

ここに掲載したサンプルスクリプトは"DTrace Dynamic Tracing In Oracle Solaris, Mac OS X & FreeBSD", by Brendan Gregg and Jim Mauro P.878、P.879、P.880より抜粋したものです(FreeBSDで実行した結果を掲載しています⁠⁠。

勉強会

第61回 3月23日(木)19:00~FreeBSD勉強会:リキャップ・ザ・AsiaBSDCon 2017 ~日本語でふりかえるABC~

2017年3月9~12日まで東京でAsiaBSDCon 2017が開催される予定です。ぜひこのカンファレンスにご参加いただきたいわけなのですが、なかにはどうしても仕事の都合で参加できなかったとか、正直英語がよくわからなかったとか、そういった方もいらっしゃるのではないかと思います。

3月のFreeBSD勉強会では、AsiaBSDCon 2017のあとというこのタイミングを活かして、AsiaBSDCon 2017の発表内容を振り返ってみよう、というのをやってみようと思います。AsiaBSDConに参加しているにもかかわらず、これまで一度もプロシーディングを読み返したことすらないというあなた、ぜひプロシーディングを持参してご参加ください :) AsiaBSDConに参加できなかったというあなたも、この機会をお見逃しなく(できればAsiaBSDConそのものに参加した方が絶対的によいです、あしからず⁠⁠。

FreeBSD勉強会 発表者募集

FreeBSD勉強会では発表者を募集しています。FreeBSDに関して発表を行いたい場合、@daichigotoまでメッセージをお願いします。1時間半~2時間ほどの発表資料を作成していただき発表をお願いできればと思います。

おすすめ記事

記事・ニュース一覧