Skip to content

Commit 24c41c9

Browse files
committed
Land rapid7#6225, wall(1)/write(1) post module
2 parents 06a5b5b + 38ca943 commit 24c41c9

File tree

3 files changed

+167
-1
lines changed

3 files changed

+167
-1
lines changed

lib/rex/text.rb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,56 @@ def self.hexify(str, col = DefaultWrap, line_start = '', line_end = '', buf_star
11261126
return output
11271127
end
11281128

1129+
#
1130+
# Converts a string to one similar to what would be used by cowsay(1), a UNIX utility for
1131+
# displaying text as if it was coming from an ASCII-cow's mouth:
1132+
#
1133+
# __________________
1134+
# < the cow says moo >
1135+
# ------------------
1136+
# \ ^__^
1137+
# \ (oo)\_______
1138+
# (__)\ )\/\
1139+
# ||----w |
1140+
# || ||
1141+
#
1142+
# @param text [String] The string to cowsay
1143+
# @param width [Fixnum] Width of the cow's cloud. Default's to cowsay(1)'s default, 39.
1144+
def self.cowsay(text, width=39)
1145+
# cowsay(1) chunks a message up into 39-byte chunks and wraps it in '| ' and ' |'
1146+
# Rex::Text.wordwrap(text, 0, 39, ' |', '| ') almost does this, but won't
1147+
# split a word that has > 39 characters in it which results in oddly formed
1148+
# text in the cowsay banner, so just do it by hand. This big mess wraps
1149+
# the provided text in an ASCII-cloud and then makes it look like the cloud
1150+
# is a thought/word coming from the ASCII-cow. Each line in the
1151+
# ASCII-cloud is no more than the specified number-characters long, and the
1152+
# cloud corners are made to look rounded
1153+
text_lines = text.scan(Regexp.new(".{1,#{width-4}}"))
1154+
max_length = text_lines.map(&:size).sort.last
1155+
cloud_parts = []
1156+
cloud_parts << " #{'_' * (max_length + 2)}"
1157+
if text_lines.size == 1
1158+
cloud_parts << "< #{text} >"
1159+
else
1160+
cloud_parts << "/ #{text_lines.first.ljust(max_length, ' ')} \\"
1161+
if text_lines.size > 2
1162+
text_lines[1, text_lines.length - 2].each do |line|
1163+
cloud_parts << "| #{line.ljust(max_length, ' ')} |"
1164+
end
1165+
end
1166+
cloud_parts << "\\ #{text_lines.last.ljust(max_length, ' ')} /"
1167+
end
1168+
cloud_parts << " #{'-' * (max_length + 2)}"
1169+
cloud_parts << <<EOS
1170+
\\ ,__,
1171+
\\ (oo)____
1172+
(__) )\\
1173+
||--|| *
1174+
EOS
1175+
cloud_parts.join("\n")
1176+
end
1177+
1178+
11291179
##
11301180
#
11311181
# Transforms

modules/post/multi/general/wall.rb

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class Metasploit3 < Msf::Post
9+
def initialize(info = {})
10+
super(
11+
update_info(
12+
info,
13+
'Name' => 'Write Messages to Users',
14+
'Description' => %q{
15+
This module utilizes the wall(1) or write(1) utilities, as appropriate,
16+
to send messages to users on the target system.
17+
},
18+
'License' => MSF_LICENSE,
19+
'Author' => [
20+
'Jon Hart <jon_hart[at]rapid7.com>' # original metasploit module
21+
],
22+
# TODO: is there a way to do this on Windows?
23+
'Platform' => %w(linux osx unix),
24+
'SessionTypes' => %w(shell meterpreter)
25+
)
26+
)
27+
register_options(
28+
[
29+
OptString.new('MESSAGE', [false, 'The message to send', '']),
30+
OptString.new('USERS', [false, 'List of users to write(1) to, separated by commas. ' \
31+
' wall(1)s to all users by default']),
32+
OptBool.new('COWSAY', [true, 'Display MESSAGE in a ~cowsay way', false])
33+
], self.class)
34+
end
35+
36+
def users
37+
datastore['USERS'] ? datastore['USERS'].split(/\s*,\s*/) : nil
38+
end
39+
40+
def message
41+
if datastore['MESSAGE'].blank?
42+
text = "Hello from a metasploit session at #{Time.now}"
43+
else
44+
text = datastore['MESSAGE']
45+
end
46+
47+
datastore['COWSAY'] ? Rex::Text.cowsay(text) : text
48+
end
49+
50+
def run
51+
if users
52+
# this requires that the target user has write turned on
53+
users.map { |user| cmd_exec("echo '#{message}' | write #{user}") }
54+
else
55+
# this will send the messages to all users, regardless of whether or
56+
# not they have write turned on. If the session is root, the -n will disable
57+
# the annoying banner
58+
cmd_exec("echo '#{message}' | wall -n")
59+
end
60+
end
61+
end

spec/lib/rex/text_spec.rb

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,61 @@
167167
end
168168
end
169169

170+
context ".cowsay" do
171+
172+
def moo(num)
173+
(%w(moo) * num).join(' ')
174+
end
175+
176+
it "should cowsay single lines correctly" do
177+
cowsaid = <<EOCOW
178+
_____________________
179+
< moo moo moo moo moo >
180+
---------------------
181+
\\ ,__,
182+
\\ (oo)____
183+
(__) )\\
184+
||--|| *
185+
EOCOW
186+
described_class.cowsay(moo(5)).should eq(cowsaid)
187+
end
188+
189+
it "should cowsay two lines correctly" do
190+
cowsaid = <<EOCOW
191+
_____________________________________
192+
/ moo moo moo moo moo moo moo moo moo \\
193+
\\ moo moo moo moo moo moo /
194+
-------------------------------------
195+
\\ ,__,
196+
\\ (oo)____
197+
(__) )\\
198+
||--|| *
199+
EOCOW
200+
described_class.cowsay(moo(15)).should eq(cowsaid)
201+
end
202+
203+
it "should cowsay three+ lines correctly" do
204+
cowsaid = <<EOCOW
205+
_____________________________________
206+
/ moo moo moo moo moo moo moo moo moo \\
207+
| moo moo moo moo moo moo moo moo mo |
208+
| o moo moo moo moo moo moo moo moo m |
209+
\\ oo moo moo moo /
210+
-------------------------------------
211+
\\ ,__,
212+
\\ (oo)____
213+
(__) )\\
214+
||--|| *
215+
EOCOW
216+
described_class.cowsay(moo(30)).should eq(cowsaid)
217+
end
218+
219+
it "should respect the wrap" do
220+
wrap = 40 + rand(100)
221+
cowsaid = described_class.cowsay(moo(1000), wrap)
222+
max_len = cowsaid.split(/\n/).map(&:length).sort.last
223+
max_len.should eq(wrap)
224+
end
225+
end
170226
end
171227
end
172-

0 commit comments

Comments
 (0)