shellsnoop revision 1.1.1.1 1 #!/bin/sh
2 #
3 # shellsnoop - A program to print read/write details from shells,
4 # such as keystrokes and command outputs.
5 # Written using DTrace (Solaris 10 3/05).
6 #
7 # This program sounds somewhat dangerous (snooping keystrokes), but is
8 # no more so than /usr/bin/truss, and both need root or dtrace privileges to
9 # run. In fact, less dangerous, as we only print visible text (not password
10 # text, for example). Having said that, it goes without saying that this
11 # program shouldn't be used for breeching privacy of other users.
12 #
13 # This was written as a tool to demonstrate the capabilities of DTrace.
14 #
15 # $Id: shellsnoop,v 1.1.1.1 2015/09/30 22:01:07 christos Exp $
16 #
17 # USAGE: shellsnoop [-hqsv] [-p PID] [-u UID]
18 #
19 # -q # quiet, only print data
20 # -s # include start time, us
21 # -v # include start time, string
22 # -p PID # process ID to snoop
23 # -u UID # user ID to snoop
24 # eg,
25 # shellsnoop # default output
26 # shellsnoop -v # human readable timestamps
27 # shellsnoop -p 1892 # snoop this PID only
28 # shellsnoop -qp 1892 # watch this PID data only
29 #
30 # FIELDS:
31 # UID User ID
32 # PID process ID
33 # PPID parent process ID
34 # COMM command name
35 # DIR direction (R read, W write)
36 # TEXT text contained in the read/write
37 # TIME timestamp for the command, us
38 # STRTIME timestamp for the command, string
39 #
40 # SEE ALSO: ttywatcher
41 #
42 # COPYRIGHT: Copyright (c) 2005 Brendan Gregg.
43 #
44 # CDDL HEADER START
45 #
46 # The contents of this file are subject to the terms of the
47 # Common Development and Distribution License, Version 1.0 only
48 # (the "License"). You may not use this file except in compliance
49 # with the License.
50 #
51 # You can obtain a copy of the license at Docs/cddl1.txt
52 # or http://www.opensolaris.org/os/licensing.
53 # See the License for the specific language governing permissions
54 # and limitations under the License.
55 #
56 # CDDL HEADER END
57 #
58 # Author: Brendan Gregg [Sydney, Australia]
59 #
60 # 28-Mar-2004 Brendan Gregg Created this.
61 # 21-Jan-2005 " " Wrapped in sh to provide options.
62 # 30-Nov-2005 " " Fixed trailing buffer text bug.
63 # 30-Nov-2005 " " Fixed sh no keystroke text in quiet bug.
64 # 30-Nov-2005 " " Last update.
65 #
66
67
68 ##############################
69 # --- Process Arguments ---
70 #
71 opt_pid=0; opt_uid=0; opt_time=0; opt_timestr=0; opt_quiet=0; opt_debug=0
72 filter=0; pid=0; uid=0
73
74 while getopts dhp:qsu:v name
75 do
76 case $name in
77 d) opt_debug=1 ;;
78 p) opt_pid=1; pid=$OPTARG ;;
79 q) opt_quiet=1 ;;
80 s) opt_time=1 ;;
81 u) opt_uid=1; uid=$OPTARG ;;
82 v) opt_timestr=1 ;;
83 h|?) cat <<-END >&2
84 USAGE: shellsnoop [-hqsv] [-p PID] [-u UID]
85 shellsnoop # default output
86 -q # quiet, only print data
87 -s # include start time, us
88 -v # include start time, string
89 -p PID # process ID to snoop
90 -u UID # user ID to snoop
91 END
92 exit 1
93 esac
94 done
95
96 if [ $opt_quiet -eq 1 ]; then
97 opt_time=0; opt_timestr=0
98 fi
99 if [ $opt_pid -eq 1 -o $opt_uid -eq 1 ]; then
100 filter=1
101 fi
102
103
104 #################################
105 # --- Main Program, DTrace ---
106 #
107 dtrace -n '
108 /*
109 * Command line arguments
110 */
111 inline int OPT_debug = '$opt_debug';
112 inline int OPT_quiet = '$opt_quiet';
113 inline int OPT_pid = '$opt_pid';
114 inline int OPT_uid = '$opt_uid';
115 inline int OPT_time = '$opt_time';
116 inline int OPT_timestr = '$opt_timestr';
117 inline int FILTER = '$filter';
118 inline int PID = '$pid';
119 inline int UID = '$uid';
120
121 #pragma D option quiet
122 #pragma D option switchrate=20hz
123
124 /*
125 * Print header
126 */
127 dtrace:::BEGIN /OPT_time == 1/
128 {
129 printf("%-14s ","TIME");
130 }
131 dtrace:::BEGIN /OPT_timestr == 1/
132 {
133 printf("%-20s ","STRTIME");
134 }
135 dtrace:::BEGIN /OPT_quiet == 0/
136 {
137 printf("%5s %5s %8s %3s %s\n", "PID", "PPID", "CMD", "DIR", "TEXT");
138 }
139
140 /*
141 * Remember this PID is a shell child
142 */
143 syscall::execve:entry
144 /execname == "sh" || execname == "ksh" || execname == "csh" ||
145 execname == "tcsh" || execname == "zsh" || execname == "bash"/
146 {
147 child[pid] = 1;
148
149 }
150 syscall::execve:entry
151 /(OPT_pid == 1 && PID != ppid) || (OPT_uid == 1 && UID != uid)/
152 {
153 /* forget if filtered */
154 child[pid] = 0;
155 }
156
157 /*
158 * Print shell keystrokes
159 */
160 syscall::write:entry, syscall::read:entry
161 /(execname == "sh" || execname == "ksh" || execname == "csh" ||
162 execname == "tcsh" || execname == "zsh" || execname == "bash")
163 && (arg0 >= 0 && arg0 <= 2)/
164 {
165 self->buf = arg1;
166 }
167 syscall::write:entry, syscall::read:entry
168 /(OPT_pid == 1 && PID != pid) || (OPT_uid == 1 && UID != uid)/
169 {
170 self->buf = 0;
171 }
172 syscall::write:return, syscall::read:return
173 /self->buf && child[pid] == 0 && OPT_time == 1/
174 {
175 printf("%-14d ", timestamp/1000);
176 }
177 syscall::write:return, syscall::read:return
178 /self->buf && child[pid] == 0 && OPT_timestr == 1/
179 {
180 printf("%-20Y ", walltimestamp);
181 }
182 syscall::write:return, syscall::read:return
183 /self->buf && child[pid] == 0 && OPT_quiet == 0/
184 {
185 this->text = (char *)copyin(self->buf, arg0);
186 this->text[arg0] = '\'\\0\'';
187
188 printf("%5d %5d %8s %3s %s\n", pid, curpsinfo->pr_ppid, execname,
189 probefunc == "read" ? "R" : "W", stringof(this->text));
190 }
191 syscall::write:return
192 /self->buf && child[pid] == 0 && OPT_quiet == 1/
193 {
194 this->text = (char *)copyin(self->buf, arg0);
195 this->text[arg0] = '\'\\0\'';
196 printf("%s", stringof(this->text));
197 }
198 syscall::read:return
199 /self->buf && execname == "sh" && child[pid] == 0 && OPT_quiet == 1/
200 {
201 this->text = (char *)copyin(self->buf, arg0);
202 this->text[arg0] = '\'\\0\'';
203 printf("%s", stringof(this->text));
204 }
205 syscall::write:return, syscall::read:return
206 /self->buf && child[pid] == 0/
207 {
208 self->buf = 0;
209 }
210
211 /*
212 * Print command output
213 */
214 syscall::write:entry, syscall::read:entry
215 /child[pid] == 1 && (arg0 == 1 || arg0 == 2)/
216 {
217 self->buf = arg1;
218 }
219 syscall::write:return, syscall::read:return
220 /self->buf && OPT_time == 1/
221 {
222 printf("%-14d ", timestamp/1000);
223 }
224 syscall::write:return, syscall::read:return
225 /self->buf && OPT_timestr == 1/
226 {
227 printf("%-20Y ", walltimestamp);
228 }
229 syscall::write:return, syscall::read:return
230 /self->buf && OPT_quiet == 0/
231 {
232 this->text = (char *)copyin(self->buf, arg0);
233 this->text[arg0] = '\'\\0\'';
234
235 printf("%5d %5d %8s %3s %s", pid, curpsinfo->pr_ppid, execname,
236 probefunc == "read" ? "R" : "W", stringof(this->text));
237
238 /* here we check if a newline is needed */
239 this->length = strlen(this->text);
240 printf("%s", this->text[this->length - 1] == '\'\\n\'' ? "" : "\n");
241 self->buf = 0;
242 }
243 syscall::write:return, syscall::read:return
244 /self->buf && OPT_quiet == 1/
245 {
246 this->text = (char *)copyin(self->buf, arg0);
247 this->text[arg0] = '\'\\0\'';
248 printf("%s", stringof(this->text));
249 self->buf = 0;
250 }
251
252 /*
253 * Cleanup
254 */
255 syscall::exit:entry
256 {
257 child[pid] = 0;
258
259 /* debug */
260 this->parent = (char *)curthread->td_proc->p_pptr->p_comm;
261 OPT_debug == 1 ? printf("PID %d CMD %s exited. (%s)\n",
262 pid, execname, stringof(this->parent)) : 1;
263 }
264 '
265