269 lines
7.0 KiB
Bash
Executable File
269 lines
7.0 KiB
Bash
Executable File
#!/usr/bin/sh
|
|
#
|
|
# shellsnoop - A program to print read/write details from shells,
|
|
# such as keystrokes and command outputs.
|
|
# Written using DTrace (Solaris 10 3/05).
|
|
#
|
|
# This program sounds somewhat dangerous (snooping keystrokes), but is
|
|
# no more so than /usr/bin/truss, and both need root or dtrace privileges to
|
|
# run. In fact, less dangerous, as we only print visible text (not password
|
|
# text, for example). Having said that, it goes without saying that this
|
|
# program shouldn't be used for breeching privacy of other users.
|
|
#
|
|
# This was written as a tool to demonstrate the capabilities of DTrace.
|
|
#
|
|
# $Id: shellsnoop 19 2007-09-12 07:47:59Z brendan $
|
|
#
|
|
# USAGE: shellsnoop [-hqsv] [-p PID] [-u UID]
|
|
#
|
|
# -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
|
|
# eg,
|
|
# shellsnoop # default output
|
|
# shellsnoop -v # human readable timestamps
|
|
# shellsnoop -p 1892 # snoop this PID only
|
|
# shellsnoop -qp 1892 # watch this PID data only
|
|
#
|
|
# FIELDS:
|
|
# UID User ID
|
|
# PID process ID
|
|
# PPID parent process ID
|
|
# COMM command name
|
|
# DIR direction (R read, W write)
|
|
# TEXT text contained in the read/write
|
|
# TIME timestamp for the command, us
|
|
# STRTIME timestamp for the command, string
|
|
#
|
|
# SEE ALSO: ttywatcher
|
|
#
|
|
# COPYRIGHT: Copyright (c) 2005 Brendan Gregg.
|
|
#
|
|
# CDDL HEADER START
|
|
#
|
|
# The contents of this file are subject to the terms of the
|
|
# Common Development and Distribution License, Version 1.0 only
|
|
# (the "License"). You may not use this file except in compliance
|
|
# with the License.
|
|
#
|
|
# You can obtain a copy of the license at Docs/cddl1.txt
|
|
# or http://www.opensolaris.org/os/licensing.
|
|
# See the License for the specific language governing permissions
|
|
# and limitations under the License.
|
|
#
|
|
# CDDL HEADER END
|
|
#
|
|
# Author: Brendan Gregg [Sydney, Australia]
|
|
#
|
|
# 28-Mar-2004 Brendan Gregg Created this.
|
|
# 21-Jan-2005 " " Wrapped in sh to provide options.
|
|
# 30-Nov-2005 " " Fixed trailing buffer text bug.
|
|
# 30-Nov-2005 " " Fixed sh no keystroke text in quiet bug.
|
|
# 30-Nov-2005 " " Last update.
|
|
#
|
|
|
|
|
|
##############################
|
|
# --- 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::exec:entry, syscall::exece:entry
|
|
/execname == "sh" || execname == "ksh" || execname == "csh" ||
|
|
execname == "tcsh" || execname == "zsh" || execname == "bash"/
|
|
{
|
|
child[pid] = 1;
|
|
|
|
/* debug */
|
|
this->parent = (char *)curthread->t_procp->p_parent->p_user.u_comm;
|
|
OPT_debug == 1 ? printf("PID %d CMD %s started. (%s)\n",
|
|
pid, execname, stringof(this->parent)) : 1;
|
|
}
|
|
syscall::exec:entry, syscall::exece: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::rexit:entry
|
|
{
|
|
child[pid] = 0;
|
|
|
|
/* debug */
|
|
this->parent = (char *)curthread->t_procp->p_parent->p_user.u_comm;
|
|
OPT_debug == 1 ? printf("PID %d CMD %s exited. (%s)\n",
|
|
pid, execname, stringof(this->parent)) : 1;
|
|
}
|
|
'
|