1235368Sgnn#!/usr/bin/sh 2235368Sgnn# 3235368Sgnn# shellsnoop - A program to print read/write details from shells, 4235368Sgnn# such as keystrokes and command outputs. 5235368Sgnn# Written using DTrace (Solaris 10 3/05). 6235368Sgnn# 7235368Sgnn# This program sounds somewhat dangerous (snooping keystrokes), but is 8235368Sgnn# no more so than /usr/bin/truss, and both need root or dtrace privileges to 9235368Sgnn# run. In fact, less dangerous, as we only print visible text (not password 10235368Sgnn# text, for example). Having said that, it goes without saying that this 11235368Sgnn# program shouldn't be used for breeching privacy of other users. 12235368Sgnn# 13235368Sgnn# This was written as a tool to demonstrate the capabilities of DTrace. 14235368Sgnn# 15235368Sgnn# $Id: shellsnoop 19 2007-09-12 07:47:59Z brendan $ 16235368Sgnn# 17235368Sgnn# USAGE: shellsnoop [-hqsv] [-p PID] [-u UID] 18235368Sgnn# 19235368Sgnn# -q # quiet, only print data 20235368Sgnn# -s # include start time, us 21235368Sgnn# -v # include start time, string 22235368Sgnn# -p PID # process ID to snoop 23235368Sgnn# -u UID # user ID to snoop 24235368Sgnn# eg, 25235368Sgnn# shellsnoop # default output 26235368Sgnn# shellsnoop -v # human readable timestamps 27235368Sgnn# shellsnoop -p 1892 # snoop this PID only 28235368Sgnn# shellsnoop -qp 1892 # watch this PID data only 29235368Sgnn# 30235368Sgnn# FIELDS: 31235368Sgnn# UID User ID 32235368Sgnn# PID process ID 33235368Sgnn# PPID parent process ID 34235368Sgnn# COMM command name 35235368Sgnn# DIR direction (R read, W write) 36235368Sgnn# TEXT text contained in the read/write 37235368Sgnn# TIME timestamp for the command, us 38235368Sgnn# STRTIME timestamp for the command, string 39235368Sgnn# 40235368Sgnn# SEE ALSO: ttywatcher 41235368Sgnn# 42235368Sgnn# COPYRIGHT: Copyright (c) 2005 Brendan Gregg. 43235368Sgnn# 44235368Sgnn# CDDL HEADER START 45235368Sgnn# 46235368Sgnn# The contents of this file are subject to the terms of the 47235368Sgnn# Common Development and Distribution License, Version 1.0 only 48235368Sgnn# (the "License"). You may not use this file except in compliance 49235368Sgnn# with the License. 50235368Sgnn# 51235368Sgnn# You can obtain a copy of the license at Docs/cddl1.txt 52235368Sgnn# or http://www.opensolaris.org/os/licensing. 53235368Sgnn# See the License for the specific language governing permissions 54235368Sgnn# and limitations under the License. 55235368Sgnn# 56235368Sgnn# CDDL HEADER END 57235368Sgnn# 58235368Sgnn# Author: Brendan Gregg [Sydney, Australia] 59235368Sgnn# 60235368Sgnn# 28-Mar-2004 Brendan Gregg Created this. 61235368Sgnn# 21-Jan-2005 " " Wrapped in sh to provide options. 62235368Sgnn# 30-Nov-2005 " " Fixed trailing buffer text bug. 63235368Sgnn# 30-Nov-2005 " " Fixed sh no keystroke text in quiet bug. 64235368Sgnn# 30-Nov-2005 " " Last update. 65235368Sgnn# 66235368Sgnn 67235368Sgnn 68235368Sgnn############################## 69235368Sgnn# --- Process Arguments --- 70235368Sgnn# 71235368Sgnnopt_pid=0; opt_uid=0; opt_time=0; opt_timestr=0; opt_quiet=0; opt_debug=0 72235368Sgnnfilter=0; pid=0; uid=0 73235368Sgnn 74235368Sgnnwhile getopts dhp:qsu:v name 75235368Sgnndo 76235368Sgnn case $name in 77235368Sgnn d) opt_debug=1 ;; 78235368Sgnn p) opt_pid=1; pid=$OPTARG ;; 79235368Sgnn q) opt_quiet=1 ;; 80235368Sgnn s) opt_time=1 ;; 81235368Sgnn u) opt_uid=1; uid=$OPTARG ;; 82235368Sgnn v) opt_timestr=1 ;; 83235368Sgnn h|?) cat <<-END >&2 84235368Sgnn USAGE: shellsnoop [-hqsv] [-p PID] [-u UID] 85235368Sgnn shellsnoop # default output 86235368Sgnn -q # quiet, only print data 87235368Sgnn -s # include start time, us 88235368Sgnn -v # include start time, string 89235368Sgnn -p PID # process ID to snoop 90235368Sgnn -u UID # user ID to snoop 91235368Sgnn END 92235368Sgnn exit 1 93235368Sgnn esac 94235368Sgnndone 95235368Sgnn 96235368Sgnnif [ $opt_quiet -eq 1 ]; then 97235368Sgnn opt_time=0; opt_timestr=0 98235368Sgnnfi 99235368Sgnnif [ $opt_pid -eq 1 -o $opt_uid -eq 1 ]; then 100235368Sgnn filter=1 101235368Sgnnfi 102235368Sgnn 103235368Sgnn 104235368Sgnn################################# 105235368Sgnn# --- Main Program, DTrace --- 106235368Sgnn# 107235368Sgnndtrace -n ' 108235368Sgnn /* 109235368Sgnn * Command line arguments 110235368Sgnn */ 111235368Sgnn inline int OPT_debug = '$opt_debug'; 112235368Sgnn inline int OPT_quiet = '$opt_quiet'; 113235368Sgnn inline int OPT_pid = '$opt_pid'; 114235368Sgnn inline int OPT_uid = '$opt_uid'; 115235368Sgnn inline int OPT_time = '$opt_time'; 116235368Sgnn inline int OPT_timestr = '$opt_timestr'; 117235368Sgnn inline int FILTER = '$filter'; 118235368Sgnn inline int PID = '$pid'; 119235368Sgnn inline int UID = '$uid'; 120235368Sgnn 121235368Sgnn #pragma D option quiet 122235368Sgnn #pragma D option switchrate=20hz 123235368Sgnn 124235368Sgnn /* 125235368Sgnn * Print header 126235368Sgnn */ 127235368Sgnn dtrace:::BEGIN /OPT_time == 1/ 128235368Sgnn { 129235368Sgnn printf("%-14s ","TIME"); 130235368Sgnn } 131235368Sgnn dtrace:::BEGIN /OPT_timestr == 1/ 132235368Sgnn { 133235368Sgnn printf("%-20s ","STRTIME"); 134235368Sgnn } 135235368Sgnn dtrace:::BEGIN /OPT_quiet == 0/ 136235368Sgnn { 137235368Sgnn printf("%5s %5s %8s %3s %s\n", "PID", "PPID", "CMD", "DIR", "TEXT"); 138235368Sgnn } 139235368Sgnn 140235368Sgnn /* 141235368Sgnn * Remember this PID is a shell child 142235368Sgnn */ 143235368Sgnn syscall::exec:entry, syscall::exece:entry 144235368Sgnn /execname == "sh" || execname == "ksh" || execname == "csh" || 145235368Sgnn execname == "tcsh" || execname == "zsh" || execname == "bash"/ 146235368Sgnn { 147235368Sgnn child[pid] = 1; 148235368Sgnn 149235368Sgnn /* debug */ 150235368Sgnn this->parent = (char *)curthread->t_procp->p_parent->p_user.u_comm; 151235368Sgnn OPT_debug == 1 ? printf("PID %d CMD %s started. (%s)\n", 152235368Sgnn pid, execname, stringof(this->parent)) : 1; 153235368Sgnn } 154235368Sgnn syscall::exec:entry, syscall::exece:entry 155235368Sgnn /(OPT_pid == 1 && PID != ppid) || (OPT_uid == 1 && UID != uid)/ 156235368Sgnn { 157235368Sgnn /* forget if filtered */ 158235368Sgnn child[pid] = 0; 159235368Sgnn } 160235368Sgnn 161235368Sgnn /* 162235368Sgnn * Print shell keystrokes 163235368Sgnn */ 164235368Sgnn syscall::write:entry, syscall::read:entry 165235368Sgnn /(execname == "sh" || execname == "ksh" || execname == "csh" || 166235368Sgnn execname == "tcsh" || execname == "zsh" || execname == "bash") 167235368Sgnn && (arg0 >= 0 && arg0 <= 2)/ 168235368Sgnn { 169235368Sgnn self->buf = arg1; 170235368Sgnn } 171235368Sgnn syscall::write:entry, syscall::read:entry 172235368Sgnn /(OPT_pid == 1 && PID != pid) || (OPT_uid == 1 && UID != uid)/ 173235368Sgnn { 174235368Sgnn self->buf = 0; 175235368Sgnn } 176235368Sgnn syscall::write:return, syscall::read:return 177235368Sgnn /self->buf && child[pid] == 0 && OPT_time == 1/ 178235368Sgnn { 179235368Sgnn printf("%-14d ", timestamp/1000); 180235368Sgnn } 181235368Sgnn syscall::write:return, syscall::read:return 182235368Sgnn /self->buf && child[pid] == 0 && OPT_timestr == 1/ 183235368Sgnn { 184235368Sgnn printf("%-20Y ", walltimestamp); 185235368Sgnn } 186235368Sgnn syscall::write:return, syscall::read:return 187235368Sgnn /self->buf && child[pid] == 0 && OPT_quiet == 0/ 188235368Sgnn { 189235368Sgnn this->text = (char *)copyin(self->buf, arg0); 190235368Sgnn this->text[arg0] = '\'\\0\''; 191235368Sgnn 192235368Sgnn printf("%5d %5d %8s %3s %s\n", pid, curpsinfo->pr_ppid, execname, 193235368Sgnn probefunc == "read" ? "R" : "W", stringof(this->text)); 194235368Sgnn } 195235368Sgnn syscall::write:return 196235368Sgnn /self->buf && child[pid] == 0 && OPT_quiet == 1/ 197235368Sgnn { 198235368Sgnn this->text = (char *)copyin(self->buf, arg0); 199235368Sgnn this->text[arg0] = '\'\\0\''; 200235368Sgnn printf("%s", stringof(this->text)); 201235368Sgnn } 202235368Sgnn syscall::read:return 203235368Sgnn /self->buf && execname == "sh" && child[pid] == 0 && OPT_quiet == 1/ 204235368Sgnn { 205235368Sgnn this->text = (char *)copyin(self->buf, arg0); 206235368Sgnn this->text[arg0] = '\'\\0\''; 207235368Sgnn printf("%s", stringof(this->text)); 208235368Sgnn } 209235368Sgnn syscall::write:return, syscall::read:return 210235368Sgnn /self->buf && child[pid] == 0/ 211235368Sgnn { 212235368Sgnn self->buf = 0; 213235368Sgnn } 214235368Sgnn 215235368Sgnn /* 216235368Sgnn * Print command output 217235368Sgnn */ 218235368Sgnn syscall::write:entry, syscall::read:entry 219235368Sgnn /child[pid] == 1 && (arg0 == 1 || arg0 == 2)/ 220235368Sgnn { 221235368Sgnn self->buf = arg1; 222235368Sgnn } 223235368Sgnn syscall::write:return, syscall::read:return 224235368Sgnn /self->buf && OPT_time == 1/ 225235368Sgnn { 226235368Sgnn printf("%-14d ", timestamp/1000); 227235368Sgnn } 228235368Sgnn syscall::write:return, syscall::read:return 229235368Sgnn /self->buf && OPT_timestr == 1/ 230235368Sgnn { 231235368Sgnn printf("%-20Y ", walltimestamp); 232235368Sgnn } 233235368Sgnn syscall::write:return, syscall::read:return 234235368Sgnn /self->buf && OPT_quiet == 0/ 235235368Sgnn { 236235368Sgnn this->text = (char *)copyin(self->buf, arg0); 237235368Sgnn this->text[arg0] = '\'\\0\''; 238235368Sgnn 239235368Sgnn printf("%5d %5d %8s %3s %s", pid, curpsinfo->pr_ppid, execname, 240235368Sgnn probefunc == "read" ? "R" : "W", stringof(this->text)); 241235368Sgnn 242235368Sgnn /* here we check if a newline is needed */ 243235368Sgnn this->length = strlen(this->text); 244235368Sgnn printf("%s", this->text[this->length - 1] == '\'\\n\'' ? "" : "\n"); 245235368Sgnn self->buf = 0; 246235368Sgnn } 247235368Sgnn syscall::write:return, syscall::read:return 248235368Sgnn /self->buf && OPT_quiet == 1/ 249235368Sgnn { 250235368Sgnn this->text = (char *)copyin(self->buf, arg0); 251235368Sgnn this->text[arg0] = '\'\\0\''; 252235368Sgnn printf("%s", stringof(this->text)); 253235368Sgnn self->buf = 0; 254235368Sgnn } 255235368Sgnn 256235368Sgnn /* 257235368Sgnn * Cleanup 258235368Sgnn */ 259235368Sgnn syscall::rexit:entry 260235368Sgnn { 261235368Sgnn child[pid] = 0; 262235368Sgnn 263235368Sgnn /* debug */ 264235368Sgnn this->parent = (char *)curthread->t_procp->p_parent->p_user.u_comm; 265235368Sgnn OPT_debug == 1 ? printf("PID %d CMD %s exited. (%s)\n", 266235368Sgnn pid, execname, stringof(this->parent)) : 1; 267235368Sgnn } 268235368Sgnn' 269