#!/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,v 1.1.1.1 2015/09/30 22:01:06 christos Exp $ # # 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::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; /* debug */ this->parent = (char *)curthread->td_proc->p_pptr->p_comm; OPT_debug == 1 ? printf("PID %d CMD %s exited. (%s)\n", pid, execname, stringof(this->parent)) : 1; } '