Skip to content

Commit a986b46

Browse files
kernelsmithkernelsmith
authored andcommitted
initial commit of alias console plugin
It's working, tab complete is messed up and clear is messed up, but the problems are related
1 parent 638d9d1 commit a986b46

File tree

1 file changed

+288
-0
lines changed

1 file changed

+288
-0
lines changed

plugins/alias.rb

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
##
2+
# $Id$
3+
# $Revision$
4+
##
5+
6+
require 'rex/ui/text/table'
7+
8+
module Msf
9+
10+
class Plugin::Alias < Msf::Plugin
11+
class AliasCommandDispatcher
12+
include Msf::Ui::Console::CommandDispatcher
13+
14+
attr_reader :aliases
15+
def initialize(driver)
16+
super(driver)
17+
@aliases = {}
18+
end
19+
20+
def name
21+
"Alias"
22+
end
23+
24+
@@alias_opts = Rex::Parser::Arguments.new(
25+
"-h" => [ false, "Help banner." ],
26+
"-c" => [ true, "Clear an alias (* to clear all)."],
27+
"-f" => [ true, "Force an alias assignment." ]
28+
)
29+
#
30+
# Returns the hash of commands supported by this dispatcher.
31+
#
32+
def commands # driver.dispatcher_stack[3].commands
33+
{
34+
"alias" => "create or view an alias."
35+
# "alias_clear" => "clear an alias (or all aliases).",
36+
# "alias_force" => "Force an alias (such as to override)"
37+
}.merge(aliases) # make aliased commands available as commands of their own
38+
end
39+
40+
#
41+
# the main alias command handler
42+
#
43+
# usage: alias [options] [name [value]]
44+
def cmd_alias(*args)
45+
# we parse args manually instead of using @@alias.opts.parse to handle special cases
46+
case args.length
47+
when 0 # print the list of current aliases
48+
if @aliases.length == 0
49+
return print_status("No aliases currently defined")
50+
else
51+
tbl = Rex::Ui::Text::Table.new(
52+
'Header' => "Current Aliases",
53+
'Prefix' => "\n",
54+
'Postfix' => "\n",
55+
'Columns' => [ 'Alias Name', 'Alias Value' ]
56+
)
57+
@aliases.each_pair do |key,val|
58+
tbl << [key,val]
59+
end
60+
return print(tbl.to_s)
61+
end
62+
when 1 # display the alias if one matches this name (or help)
63+
return cmd_alias_help if args[0] == "-h" or args[0] == "--help"
64+
if @aliases.keys.include?(args[0])
65+
print_status("\'#{args[0]}\' is aliased to \'#{@aliases[args[0]]}\'")
66+
else
67+
print_status("\'#{args[0]}\' is not currently aliased")
68+
end
69+
else # let's see if we can assign or clear the alias
70+
force = false
71+
clear = false
72+
# if using -f or -c, they must be the first arg, because -f/-c may also show up in the alias
73+
# value so we can't do something like if args.include("-f") or delete_if etc
74+
# we sould never have to force and clear simultaneously.
75+
if args[0] == "-f"
76+
force = true
77+
args.shift
78+
elsif args[0] == "-c"
79+
clear = true
80+
args.shift
81+
end
82+
name = args.shift
83+
print_good "The alias name is #{name}"
84+
if clear
85+
# clear all aliases if "*"
86+
if name == "*"
87+
@aliases.keys.each do |a|
88+
deregister_alias(a)
89+
end
90+
print_status "Cleared all aliases"
91+
else # clear the named alias if it exists
92+
print_status "Checking alias #{name} for clear"
93+
deregister_alias(name) if @aliases.keys.include?(name)
94+
print_status "Cleared alias #{name}"
95+
end
96+
return
97+
end
98+
# smash everything that's left together
99+
value = args.join(" ")
100+
101+
if is_valid_alias?(name,value)
102+
if force or (not Rex::FileUtils.find_full_path(name) and not @aliases.keys.include?(name))
103+
register_alias(name, value)
104+
else
105+
print_error("#{name} already exists as system command or current alias, use -f to force")
106+
end
107+
else
108+
print_error("\'#{name}\' is not a permitted name or \'#{value}\' is not a valid/permitted console or system command")
109+
end
110+
end
111+
end
112+
113+
def cmd_alias_help
114+
print_line "Usage: alias [options] [name [value]]"
115+
print_line
116+
print(@@alias_opts.usage())
117+
end
118+
119+
#
120+
# Tab completion for the alias command
121+
#
122+
def cmd_alias_tabs(str, words)
123+
if words.length <= 1
124+
return @@alias_opts.fmt.keys + tab_complete_aliases_and_commands(str, words)
125+
else
126+
return tab_complete_aliases_and_commands(str, words)
127+
end
128+
end
129+
130+
private
131+
#
132+
# do everything needed to add an alias of +name+ having the value +value+
133+
#
134+
def register_alias(name, value)
135+
#TODO: begin rescue?
136+
#TODO: security concerns since we are using eval
137+
138+
# define some class instance methods
139+
self.class_eval do
140+
# define a class instance method that will respond for the alias
141+
define_method "cmd_#{name}" do |*args|
142+
# just replace the alias w/the alias' value and run that
143+
driver.run_single("#{value} #{args.join(' ')}")
144+
end
145+
# define a class instance method that will tab complete the aliased command
146+
# we just proxy to the top-level tab complete function and let them handle it
147+
define_method "cmd_#{name}_tabs" do |str, words|
148+
#print_good "Creating cmd_#{name}_tabs as driver.tab_complete(#{value} #{words.join(' ')})"
149+
#driver.tab_complete("MONKEY")
150+
words.delete(name)
151+
driver.tab_complete("#{value} #{words.join(' ')}")
152+
end
153+
# we don't need a cmd_#{name}_help method, we just let the original handle that
154+
end
155+
# add the alias to the list
156+
@aliases[name] = value
157+
end
158+
159+
#
160+
# do everything required to remove an alias of name +name+
161+
#
162+
def deregister_alias(name)
163+
self.class_eval do
164+
# remove the methods we defined for this alias
165+
remove_method("cmd_#{name}")
166+
remove_method("cmd_#{name}_tab")
167+
end
168+
end
169+
170+
#
171+
# Validate a proposed alias
172+
#
173+
def is_valid_alias?(name,value)
174+
# some "bad words" to avoid for the value. value would have to not match these regexes
175+
# this is just basic idiot protection, it's not meant to be "undefeatable"
176+
value.strip!
177+
bad_words = [/^rm +(-rf|-r +-f|-f +-r) +\/+.*$/, /^msfconsole$/]
178+
bad_words.each do |regex|
179+
# don't mess around, just return false if we match
180+
return false if value =~ regex
181+
end
182+
# we're only gonna validate the first part of the cmd, e.g. just ls from "ls -lh"
183+
value = value.split(" ").first
184+
valid_value = false
185+
186+
# value is considered valid if it's a ref to a valid console command or
187+
# a system executable or existing alias
188+
189+
# gather all the current commands the driver's dispatcher's have & check 'em
190+
driver.dispatcher_stack.each do |dispatcher|
191+
next unless dispatcher.respond_to?(:commands)
192+
next if (dispatcher.commands.nil?)
193+
next if (dispatcher.commands.length == 0)
194+
195+
if dispatcher.respond_to?("cmd_#{value.split(" ").first}")
196+
valid_value = true
197+
break
198+
end
199+
end
200+
if not valid_value # then check elsewhere
201+
if @aliases.keys.include?(value)
202+
valid_value = true
203+
else
204+
[value, value+".exe"].each do |cmd|
205+
if Rex::FileUtils.find_full_path(cmd)
206+
valid_value = true
207+
end
208+
end
209+
end
210+
end
211+
# go ahead and return false at this point if the value isn't valid
212+
return false if not valid_value
213+
214+
# we don't check if this alias name exists or if it's a console command already etc as
215+
# -f can override that so those need to be checked externally.
216+
# We pretty much just check to see if the name is sane
217+
valid_name = true
218+
name.strip!
219+
bad_words = [/^alias$/,/\*/]
220+
# there are probably a bunch of others that need to be added here. We prevent the user
221+
# from naming the alias "alias" cuz they can end up unable to clear the aliases
222+
# for example you 'alias -f set unse't and then 'alias -f alias sessions', now you're
223+
# screwed. This prevents you from aliasing alias to alias -f etc, but no biggie.
224+
bad_words.each do |regex|
225+
# don't mess around, just return false in this case, prevents wasted processing
226+
return false if name =~ regex
227+
end
228+
229+
return valid_name
230+
end
231+
232+
#
233+
# Provide tab completion list for aliases and commands
234+
#
235+
def tab_complete_aliases_and_commands(str, words)
236+
items = []
237+
items.concat(driver.commands.keys) if driver.respond_to?('commands')
238+
items.concat(@aliases.keys)
239+
items
240+
end
241+
242+
end # end AliasCommandDispatcher class
243+
244+
#
245+
# The constructor is called when an instance of the plugin is created. The
246+
# framework instance that the plugin is being associated with is passed in
247+
# the framework parameter. Plugins should call the parent constructor when
248+
# inheriting from Msf::Plugin to ensure that the framework attribute on
249+
# their instance gets set.
250+
#
251+
attr_accessor :controller
252+
253+
def initialize(framework, opts)
254+
super
255+
256+
## Register the commands above
257+
add_console_dispatcher(AliasCommandDispatcher)
258+
end
259+
260+
261+
#
262+
# The cleanup routine for plugins gives them a chance to undo any actions
263+
# they may have done to the framework. For instance, if a console
264+
# dispatcher was added, then it should be removed in the cleanup routine.
265+
#
266+
def cleanup
267+
# If we had previously registered a console dispatcher with the console,
268+
# deregister it now.
269+
remove_console_dispatcher('Alias')
270+
end
271+
272+
#
273+
# This method returns a short, friendly name for the plugin.
274+
#
275+
def name
276+
"Alias"
277+
end
278+
279+
#
280+
# This method returns a brief description of the plugin. It should be no
281+
# more than 60 characters, but there are no hard limits.
282+
#
283+
def desc
284+
"Adds the ability to alias console commands"
285+
end
286+
287+
end ## End Plugin Class
288+
end ## End Module

0 commit comments

Comments
 (0)