Module: Msf::Sessions::WindowsEscaping

Included in:
CommandShellWindows, CommandShellWindows
Defined in:
lib/msf/base/sessions/windows_escaping.rb

Instance Method Summary collapse

Instance Method Details

#argv_to_commandline(args) ⇒ Object

Convert the executable and argument array to a commandline that can be passed to CreateProcessAsUserW.

Parameters:

  • args (Array<String>)

    The arguments to the process

[View source]

30
31
32
33
34
# File 'lib/msf/base/sessions/windows_escaping.rb', line 30

def argv_to_commandline(args)
   escaped_args = args.map { |arg| escape_arg(arg) }

  escaped_args.join(' ')
end

#escape_arg(arg) ⇒ Object

Escape an individual argument per Windows shell rules

Parameters:

  • arg (String)

    Shell argument

[View source]

38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/msf/base/sessions/windows_escaping.rb', line 38

def escape_arg(arg)
  needs_quoting = space_chars.any? { |char| arg.include?(char) }

  # Fix the weird behaviour when backslashes are treated differently when immediately prior to a double-quote
  # We need to send double the number of backslashes to make it work as expected
  # See: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw#remarks
  arg = arg.gsub(/(\\*)"/, '\\1\\1"')

  # Quotes need to be escaped
  arg = arg.gsub('"', '\\"')

  if needs_quoting
    # At the end of the argument, we're about to add another quote - so any backslashes need to be doubled here too
    arg = arg.gsub(/(\\*)$/, '\\1\\1')
    arg = "\"#{arg}\""
  end

  # Empty string needs to be coerced to have a value
  arg = '""' if arg == ''

  arg
end

#escape_cmd(executable) ⇒ Object

Escape a process for the command line

Parameters:

  • executable (String)

    The process to launch

[View source]

13
14
15
16
17
18
19
20
21
22
23
# File 'lib/msf/base/sessions/windows_escaping.rb', line 13

def escape_cmd(executable)
  needs_quoting = space_chars.any? do |char|
    executable.include?(char)
  end

  if needs_quoting
    executable = "\"#{executable}\""
  end

  executable
end

#shell_command_token(cmd, timeout = 10) ⇒ Object

[View source]

7
8
9
# File 'lib/msf/base/sessions/windows_escaping.rb', line 7

def shell_command_token(cmd,timeout = 10)
  shell_command_token_win32(cmd,timeout)
end

#space_charsObject

[View source]

3
4
5
# File 'lib/msf/base/sessions/windows_escaping.rb', line 3

def space_chars
  [' ', '\t', '\v']
end

#to_cmd(cmd_and_args) ⇒ Object

Convert the executable and argument array to a command that can be run in this command shell

Parameters:

  • cmd_and_args (Array<String>)

    The process path and the arguments to the process

[View source]

63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/msf/base/sessions/windows_escaping.rb', line 63

def to_cmd(cmd_and_args)
  # The space, caret and quote chars need to be inside double-quoted strings.
  # The percent character needs to be escaped using a caret char, while being outside a double-quoted string.
  #
  # Situations where these two situations combine are going to be the trickiest cases: something that has quote-requiring
  # characters (e.g. spaces), but which also needs to avoid expanding an environment variable. In this case,
  # the string needs to end up being partially quoted; with parts of the string in quotes, but others (i.e. bits with percents) not.
  # For example:
  # 'env var is %temp%, yes, %TEMP%' needs to end up as '"env var is "^%temp^%", yes, "^%TEMP^%'
  #
  # There is flexibility in how you might implement this, but I think this one looks the most "human" to me,
  # which would make it less signaturable.
  #
  # To do this, we'll consider each argument character-by-character. Each time we encounter a percent sign, we break out of any quotes
  # (if we've been inside them in the current "token"), and then start a new "token".

  quote_requiring = ['"', '^', ' ', "\t", "\v", '&', '<', '>', '|']

  escaped_cmd_and_args = cmd_and_args.map do |arg|
    # Escape quote chars by doubling them up, except those preceeded by a backslash (which are already effectively escaped, and handled below)
    arg = arg.gsub(/([^\\])"/, '\\1""')
    arg = arg.gsub(/^"/, '""')

    result = CommandShell._glue_cmdline_escape(arg, quote_requiring, '%', '^%', '"')

    # Fix the weird behaviour when backslashes are treated differently when immediately prior to a double-quote
    # We need to send double the number of backslashes to make it work as expected
    # See: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw#remarks
    result.gsub!(/(\\*)"/, '\\1\\1"')

    # Empty string needs to be coerced to have a value
    result = '""' if result == ''

    result
  end

  escaped_cmd_and_args.join(' ')
end