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



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



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



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



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



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



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