lib/msf/core/post/windows/cli_parse.rb
# -*- coding: binary -*-
module Msf
class Post
module Windows
module CliParse
require 'rex/exceptions'
#Msf::Post::Windows::CliParse::ParseError
class ParseError < ArgumentError
#
# Create a new ParseError object. Expects a method name, an error
# message, an error code, and the command that caused the error.
#
def initialize(method, einfo='', ecode=nil, clicmd=nil)
@method = method
@info = einfo
# try to look up info if not given, but code is?
@code = ecode
@clicmd = clicmd || "Unknown shell command"
end
#
# Convert a ParseError to a string.
#
def to_s
"#{@method}: Operation failed: #{@info}:#{@code} while running #{@clicmd}"
end
# The method that failed.
attr_reader :method
# The error info that occurred, typically a windows error message.
attr_reader :info
# The error result that occurred, typically a windows error code.
attr_reader :code
# The shell command that caused the error, if known
attr_reader :clicmd
end
#
# Parses output of some windows CLI commands and returns a hash with the
# keys/vals detected. If the item has multiple values, they will all be
# returned in the val separated by commas. Keys are downcased and
# symbolized (key.downcase.to_sym)
#
# sc.exe example (somewhat contrived):
# SERVICE_NAME: dumbservice
# DISPLAY_NAME: KernelSmith Dumb Service - User-mode
# TYPE : 20 WIN32_SHARE_PROCESS
# STATE : 4 RUNNING
# (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
# START_TYPE : 2 AUTO_START
# BINARY_PATH_NAME : C:\Windows\system32\svchost.exe -k LocalSystemNetworkRestricted
# DEPENDENCIES : PlugPlay
# : DumberService
# SERVICE_START_NAME : LocalSystem
#
# returns:
# {
# :service_name => "dumbservice",
# :display_name => "KernelSmith Dumb Service - User-mod",
# :state => "4 RUNNING",
# :start_type => "2 AUTO_START",
# :binary_path_name => "C:\Windows\system32\svchost.exe -k LocalSystemNetworkRestricted",
# :dependencies => "PlugPlay,DumberService"
# <...etc...>
# }
#
def win_parse_results(str)
tip = false
hashish = {}
lastkey = nil
str.each_line do |line|
line.chomp!
line.gsub!("\t",' ') # lose any tabs
if (tip == true && line =~ /^ + :/)
# then this is probably a continuation of the previous, let's append to previous
# NOTE: this will NOT pickup the (NOT_STOPPABLE, NOT_PAUSABLE), see next, but it
# will pickup when there's multiple dependencies
arr = line.scan(/\w+/)
val = arr.join(',') # join with commas, though there is probably only one item in arr
hashish[lastkey] << ",#{val}" # append to old val with preceding ','
# if that's confusing, maybe: hashish[lastkey] = "#{hashish[lastkey]},#{val}"
tip = false
elsif (tip == true && line =~ /^ + \(/)
# then this is probably a continuation of the previous, let's append to previous
# NOTE: this WILL pickup (NOT_STOPPABLE, NOT_PAUSABLE) etc
arr = line.scan(/\w+/) # put each "word" into an array
val = arr.join(',') # join back together with commas in case comma wasn't the sep
hashish[lastkey] << ",#{val}" # append to old val with preceding ','
# if that's confusing, maybe: hashish[lastkey] = "#{hashish[lastkey]},#{val}"
tip = false
elsif line =~ /^ *[A-Z]+[_]*[A-Z]+.*:/
tip = true
arr = line.split(':')
k = arr[0].strip.downcase.to_sym
# grab all remaining fields for hash val in case ':' present in val
v = arr[1..-1].join(':').strip
# now add this entry to the hash
hashish[k] = v
lastkey = k
end
end
return hashish
end
#
# Parses error output of some windows CLI commands and returns hash with
# the keys/vals detected always returns hash as follows but :errval only
# comes back from sc.exe using 'FAILED' keyword
#
# Note, most of the time the :errval will be nil, it's not usually provided
#
#
# sc.exe error example:
# [SC] EnumQueryServicesStatus:OpenService FAILED 1060:
#
# The specified service does not exist as an installed service.
# returns:
# {
# :error => "The specified service does not exist as an installed service",
# :errval => 1060
# }
#
# reg.exe error example:
# ERROR: Invalid key name.
# Type "REG QUERY /?" for usage.
# returns:
# {
# :error => "INVALID KEY NAME."
# :errval => nil
# }
#
def win_parse_error(results)
hashish = {
:error => "Unknown Error",
:errval => nil
}
# parse the results
if ma = /^error:.*/i.match(results) # if line starts with Error: just pass to regular parser
hashish.merge!(win_parse_results(ma[0].upcase)) #upcase required to satisfy regular parser
# merge results. Results from win_parse_results will override any duplicates in hashish
elsif ma = /FAILED +[0-9]+/.match(results) # look for 'FAILED ' followed by some numbers
sa = ma[0].split(' ')
hashish[:errval] = sa[1].chomp.to_i
# ^ intended to capture the numbers after the word 'FAILED' as [:errval]
ma = /^[^\[\n].+/.match(results)
hashish[:error] = ma[0].chomp.strip
# above intended to capture first non-empty line not starting with '[' or \n into [:error]
else
# do nothing, defaults are good
end
return hashish
end
end
end
end
end