CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/base/sessions/command_shell_windows.rb
Views: 11784
1
module Msf::Sessions
2
3
class CommandShellWindows < CommandShell
4
def initialize(*args)
5
self.platform = "windows"
6
super
7
end
8
9
def self.space_chars
10
[' ', '\t', '\v']
11
end
12
13
def shell_command_token(cmd,timeout = 10)
14
shell_command_token_win32(cmd,timeout)
15
end
16
17
# Convert the executable and argument array to a command that can be run in this command shell
18
# @param cmd_and_args [Array<String>] The process path and the arguments to the process
19
def to_cmd(cmd_and_args)
20
self.class.to_cmd(cmd_and_args)
21
end
22
23
# Escape a process for the command line
24
# @param executable [String] The process to launch
25
def self.escape_cmd(executable)
26
needs_quoting = space_chars.any? do |char|
27
executable.include?(char)
28
end
29
30
if needs_quoting
31
executable = "\"#{executable}\""
32
end
33
34
executable
35
end
36
37
# Convert the executable and argument array to a commandline that can be passed to CreateProcessAsUserW.
38
# @param args [Array<String>] The arguments to the process
39
# @remark The difference between this and `to_cmd` is that the output of `to_cmd` is expected to be passed
40
# to cmd.exe, whereas this is expected to be passed directly to the Win32 API, anticipating that it
41
# will in turn be interpreted by CommandLineToArgvW.
42
def self.argv_to_commandline(args)
43
escaped_args = args.map do |arg|
44
escape_arg(arg)
45
end
46
47
escaped_args.join(' ')
48
end
49
50
# Escape an individual argument per Windows shell rules
51
# @param arg [String] Shell argument
52
def self.escape_arg(arg)
53
needs_quoting = space_chars.any? do |char|
54
arg.include?(char)
55
end
56
57
# Fix the weird behaviour when backslashes are treated differently when immediately prior to a double-quote
58
# We need to send double the number of backslashes to make it work as expected
59
# See: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw#remarks
60
arg = arg.gsub(/(\\*)"/, '\\1\\1"')
61
62
# Quotes need to be escaped
63
arg = arg.gsub('"', '\\"')
64
65
if needs_quoting
66
# At the end of the argument, we're about to add another quote - so any backslashes need to be doubled here too
67
arg = arg.gsub(/(\\*)$/, '\\1\\1')
68
arg = "\"#{arg}\""
69
end
70
71
# Empty string needs to be coerced to have a value
72
arg = '""' if arg == ''
73
74
arg
75
end
76
77
# Convert the executable and argument array to a command that can be run in this command shell
78
# @param cmd_and_args [Array<String>] The process path and the arguments to the process
79
def self.to_cmd(cmd_and_args)
80
# The space, caret and quote chars need to be inside double-quoted strings.
81
# The percent character needs to be escaped using a caret char, while being outside a double-quoted string.
82
#
83
# Situations where these two situations combine are going to be the trickiest cases: something that has quote-requiring
84
# characters (e.g. spaces), but which also needs to avoid expanding an environment variable. In this case,
85
# the string needs to end up being partially quoted; with parts of the string in quotes, but others (i.e. bits with percents) not.
86
# For example:
87
# 'env var is %temp%, yes, %TEMP%' needs to end up as '"env var is "^%temp^%", yes, "^%TEMP^%'
88
#
89
# There is flexibility in how you might implement this, but I think this one looks the most "human" to me,
90
# which would make it less signaturable.
91
#
92
# To do this, we'll consider each argument character-by-character. Each time we encounter a percent sign, we break out of any quotes
93
# (if we've been inside them in the current "token"), and then start a new "token".
94
95
quote_requiring = ['"', '^', ' ', "\t", "\v", '&', '<', '>', '|']
96
97
escaped_cmd_and_args = cmd_and_args.map do |arg|
98
# Escape quote chars by doubling them up, except those preceeded by a backslash (which are already effectively escaped, and handled below)
99
arg = arg.gsub(/([^\\])"/, '\\1""')
100
arg = arg.gsub(/^"/, '""')
101
102
result = CommandShell._glue_cmdline_escape(arg, quote_requiring, '%', '^%', '"')
103
104
# Fix the weird behaviour when backslashes are treated differently when immediately prior to a double-quote
105
# We need to send double the number of backslashes to make it work as expected
106
# See: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw#remarks
107
result.gsub!(/(\\*)"/, '\\1\\1"')
108
109
# Empty string needs to be coerced to have a value
110
result = '""' if result == ''
111
112
result
113
end
114
115
escaped_cmd_and_args.join(' ')
116
end
117
end
118
119
end
120
121