Class: MiqWin32::System

Inherits:
Object
  • Object
show all
Defined in:
lib/metadata/util/win32/Win32System.rb

Constant Summary collapse

OS_MAPPING =
[
  'ProductName',        :product_name,
  'CurrentVersion',     :version,
  'CurrentBuildNumber', :build,
  'SystemRoot',         :system_root,
  'CSDVersion',         :service_pack,
  'ProductId',          :productid,
  'DigitalProductId',   :product_key,
  'Vendor',             :distribution,
  'EditionID',          :edition_id,
]
COMPUTER_NAME_MAPPING =
[
  'ComputerName',       :machine_name,
]
PRODUCT_OPTIONS_MAPPING =
[
  'ProductType',        :product_type,
  'ProductSuite',       :product_suite,
]
ENVIRONMENT_MAPPING =
[
  'PROCESSOR_ARCHITECTURE', :architecture
]
TCPIP_MAPPING =
[
  "Hostname", :hostname,
]
NETWORK_CARDS_MAPPING =
[
  "ServiceName", :guid,
  "Description", :description,
]
DHCP_MAPPING =
[
  "EnableDHCP", :dhcp_enabled,
  "DhcpIPAddress", :ipaddress,
  "DhcpSubnetMask", :subnet_mask,
  "LeaseObtainedTime", :lease_obtained,
  "LeaseTerminatesTime", :lease_expires,
  "DhcpDefaultGateway", :default_gateway,
  "DhcpServer", :dhcp_server,
  "DhcpNameServer", :dns_server,
  "DhcpDomain", :domain,
]
STATIC_MAPPING =
[
  "EnableDHCP", :dhcp_enabled,
  "IPAddress", :ipaddress,
  "SubnetMask", :subnet_mask,
  "DefaultGateway", :default_gateway,
  "NameServer", :dns_server,
  "Domain", :domain,
]
SAM_STRUCT_ACCT_POLICY =

Definition derived from www.beginningtoseethelight.org/ntsecurity/#BB4F910C0FFA1E43 HKEY_LOCAL_MACHINESAMSAMDomainsAccountF

BinaryStruct.new([
  'a16',  nil,                      # UNKNOWN
  'Q',    :auto_increment,          # Auto-increment
  'Q',    :max_pw_age,              # Maximum password age (>=0 & <=999) days - minus from qword:ff + 1 = seconds x 10 million
  'Q',    :min_pw_age,              # Minimum password age (>=0 & <=999) days - minus from qword:ff + 1 = seconds x 10 million
  'a8',   nil,                      # UNKNOWN
  'Q',    :lockout_duration,        # Account lockout duration (>=0 & <=99,999) minutes - minus from qword:ff + 1 = seconds x 10 million
  'Q',    :reset_lockout_counter,   # Reset account lockout counter after (>=1 & <=99,999) minutes - minus from qword:ff + 1 = seconds x 10 million
  'a8',   nil,                      # UNKNOWN
  'I',    :next_rid,                # Next created users RID
  'C',    :pw_encrypt_pw_complex,   # High nibble
  #   Store password using reversible encryption for all users in the domain (enabled=1/disabled=0)
  # Low nibble
  #   Password must meet complexity requirements (enabled=1/disabled=0)
  'a3',   nil,                      # UNKNOWN
  'C',    :min_pw_len,              # Minimum password length (>=0 & <=14) characters
  'a1',   nil,                      # UNKNOWN
  'C',    :pw_hist,                 # Enforce password history (>=0 & <=24) passwords remembered
  'a1',   nil,                      # UNKNOWN
  'S',    :lockout_threshold,       # Account lockout threshold (>=0 & <=999) attempts
  'a26',  nil,                      # UNKNOWN
  'a48',  :syskey,                  # Part of syskey
  'a8',   nil,                      # UNKNOWN
])

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(_c, fs) ⇒ System

Returns a new instance of System.



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/metadata/util/win32/Win32System.rb', line 78

def initialize(_c, fs)
  @networks = []

  regHnd = RemoteRegistry.new(fs, true)
  software_doc = regHnd.loadHive("software", [
    {:key => "Microsoft/Windows NT/CurrentVersion", :depth => 1, :value => OS_MAPPING_VALUES},
    {:key => "Microsoft/Windows NT/CurrentVersion/NetworkCards", :depth => 0, :value => NETWORK_CARDS_VALUES}
  ])

  regHnd.close

  regHnd = RemoteRegistry.new(fs, true)
  sys_doc = regHnd.loadHive("system", [
    {:key => 'CurrentControlSet/Control/ComputerName/ComputerName', :value => COMPUTER_NAME_VALUES},
    {:key => 'CurrentControlSet/Control/Session Manager/Environment', :value => ENVIRONMENT_VALUES},
    {:key => 'CurrentControlSet/Control/ProductOptions', :value => PRODUCT_OPTIONS_VALUES},
    {:key => 'CurrentControlSet/Services/Tcpip/Parameters', :value => TCPIP_VALUES},
  ])
  regHnd.close

  # Get the OS information
  attrs = {:type => "windows"}

  reg_node = MIQRexml.findRegElement("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", software_doc.root)
  attrs.merge!(XmlFind.decode(reg_node, OS_MAPPING)) if reg_node

  reg_node = MIQRexml.findRegElement("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\ComputerName", sys_doc.root)
  attrs.merge!(XmlFind.decode(reg_node, COMPUTER_NAME_MAPPING)) if reg_node

  reg_node = MIQRexml.findRegElement("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\ProductOptions", sys_doc.root)
  attrs.merge!(XmlFind.decode(reg_node, PRODUCT_OPTIONS_MAPPING)) if reg_node

  reg_node = MIQRexml.findRegElement("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", sys_doc.root)
  attrs.merge!(XmlFind.decode(reg_node, ENVIRONMENT_MAPPING)) if reg_node

  attrs[:product_key] = MiqWin32::Software.DecodeProductKey(attrs[:product_key]) if attrs[:product_key]

  attrs[:architecture] = architecture_to_string(attrs[:architecture])

  # Parse product edition and append to product_name if needed.
  os_product_suite(attrs)

  @os = attrs

  # Get the network card information

  # Hold onto the parameters common to all network cards
  reg_tcpip = MIQRexml.findRegElement("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters", sys_doc.root)
  if reg_tcpip
    tcpip_params = XmlFind.decode(reg_tcpip, TCPIP_MAPPING)
    tcpip_params[:domain] = XmlFind.findNamedElement_hash("Domain", reg_tcpip)
    tcpip_params[:domain] = XmlFind.findNamedElement_hash("DhcpDomain", reg_tcpip) if tcpip_params[:domain].blank?
    tcpip_params[:domain] = nil if tcpip_params[:domain].blank?

    # Find each netword card, and get it's individual parameters
    reg_networkCards = MIQRexml.findRegElement("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards", software_doc.root)
    if reg_networkCards.kind_of?(Hash)
      reg_networkCards.each_element do |networkCard|
        attrs = XmlFind.decode(networkCard, NETWORK_CARDS_MAPPING)

        params = XmlFind.findElement("Interfaces/#{attrs[:guid]}", reg_tcpip)
        next if params.nil?

        # Add the common parameters
        attrs.merge!(tcpip_params)

        # Blank out fields that are not shared between network types
        attrs[:lease_obtained] = attrs[:lease_expires] = attrs[:dhcp_server] = nil

        # Get the rest of the parameters based on whether this network is DHCP enabled
        dhcp = XmlFind.findNamedElement_hash("EnableDHCP", params)
        attrs.merge!(XmlFind.decode(params, dhcp.to_i == 1 ? DHCP_MAPPING : STATIC_MAPPING))

        # Remove the extra curly braces from the guid
        attrs[:guid] = attrs[:guid][1..-2] unless attrs[:guid].nil?

        # Clean the lease times and check they are in a reasonable range
        [:lease_obtained, :lease_expires].each do |t|
          attrs[t] = Time.at(attrs[t].to_i).getutc.iso8601 if attrs[t] && attrs[t].to_i >= 0 && attrs[t].to_i < 0x80000000
        end
        @networks << attrs
      end
    end
  end

  # Extracted data also built into a human-readable format if uncommented
  # @debug_str = ''

  # Force memory cleanup
  software_doc = nil; sys_doc = nil; GC.start

  regHnd = RemoteRegistry.new(fs, true)
  sam_doc = regHnd.loadHive("sam", [{:key => "SAM/Domains/Account", :depth => 1, :value => ['F']}])
  regHnd.close

  # Extract the local account policy from the registry
  @debug_str += "Account Policy:\n" if @debug_str
  reg_node = MIQRexml.findRegElement("HKEY_LOCAL_MACHINE\\SAM\\SAM\\Domains\\Account", sam_doc.root)
  if reg_node
    reg_node.each_element(:value) do |e|
      acct_policy_f = process_acct_policy_f(e.text) if e.attributes[:name] == "F"

      unless acct_policy_f.nil?
        # Remove unused elements
        acct_policy_f.delete(:auto_increment)
        acct_policy_f.delete(:next_rid)
        acct_policy_f.delete(:pw_encrypt_pw_complex)
        acct_policy_f.delete(:syskey)

        @account_policy = acct_policy_f
      end
    end
  end

  # Dump the debug string to a file if we are collecting that data
  # File.open('C:/Temp/reg_extract_full_system.txt', 'w') { |f| f.write(@debug_str) } if @debug_str
  if $log
    os_dup = @os.dup
    [:productid, :product_key].each { |k| os_dup.delete(k) }
    $log.info "VM OS information: [#{os_dup.inspect}]"
  end
end

Instance Attribute Details

#account_policyObject (readonly)

Returns the value of attribute account_policy.



7
8
9
# File 'lib/metadata/util/win32/Win32System.rb', line 7

def 
  @account_policy
end

#networksObject (readonly)

Returns the value of attribute networks.



7
8
9
# File 'lib/metadata/util/win32/Win32System.rb', line 7

def networks
  @networks
end

#osObject (readonly)

Returns the value of attribute os.



7
8
9
# File 'lib/metadata/util/win32/Win32System.rb', line 7

def os
  @os
end

Instance Method Details

#accountPolicyToXml(doc = nil) ⇒ Object



215
216
217
218
219
# File 'lib/metadata/util/win32/Win32System.rb', line 215

def accountPolicyToXml(doc = nil)
  doc = MiqXml.createDoc(nil) unless doc
  doc.add_element(:account_policy, @account_policy) unless @account_policy.blank?
  doc
end

#architecture_to_string(architecture) ⇒ Object



230
231
232
233
234
235
# File 'lib/metadata/util/win32/Win32System.rb', line 230

def architecture_to_string(architecture)
  case architecture
  when "x86" then 32
  when "AMD64" then 64
  end
end

#networksToXml(doc = nil) ⇒ Object



221
222
223
224
225
226
227
228
# File 'lib/metadata/util/win32/Win32System.rb', line 221

def networksToXml(doc = nil)
  doc = MiqXml.createDoc(nil) unless doc
  unless @networks.empty?
    node = doc.add_element(:networks)
    @networks.each { |n| node.add_element(:network, n) }
  end
  doc
end

#os_product_suite(hash) ⇒ Object

Parse product edition and append to product_name if needed.



238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/metadata/util/win32/Win32System.rb', line 238

def os_product_suite(hash)
  eid = hash.delete(:edition_id)
  ps = hash.delete(:product_suite)

  # If edition_id is populated then the edition will already be part of the product_name string
  if eid.nil? && !hash[:product_name].nil?
    ps = ps.to_s.split("\n")
    if ps.length > 1 && !hash[:product_name].include?(ps.first)
      hash[:product_name] = "#{hash[:product_name].strip} #{ps.first} Edition"
    end
  end
end

#osToXml(doc = nil) ⇒ Object



209
210
211
212
213
# File 'lib/metadata/util/win32/Win32System.rb', line 209

def osToXml(doc = nil)
  doc = MiqXml.createDoc(nil) unless doc
  doc.add_element(:os, @os) unless @os.empty?
  doc
end

#process_acct_policy_f(data) ⇒ Object



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/metadata/util/win32/Win32System.rb', line 278

def process_acct_policy_f(data)
  bin = MSRegHive.regBinaryToRawBinary(data)
  f = SAM_STRUCT_ACCT_POLICY.decode(bin)

  @debug_str += "  auto_increment        - %s\n" % f[:auto_increment] if @debug_str

  @debug_str += "  max_pw_age            - %s - " % f[:max_pw_age] if @debug_str
  f[:max_pw_age] = process_acct_policy_f_date(f[:max_pw_age]) / 86400
  @debug_str += "%s days\n" % f[:max_pw_age] if @debug_str

  @debug_str += "  min_pw_age            - %s - " % f[:min_pw_age] if @debug_str
  f[:min_pw_age] = process_acct_policy_f_date(f[:min_pw_age]) / 86400
  @debug_str += "%s days\n" % f[:min_pw_age] if @debug_str

  @debug_str += "  lockout_duration      - %s - " % f[:lockout_duration] if @debug_str
  f[:lockout_duration] = process_acct_policy_f_date(f[:lockout_duration]) / 60
  @debug_str += "%s minutes\n" % f[:lockout_duration] if @debug_str

  @debug_str += "  reset_lockout_counter - %s - " % f[:reset_lockout_counter] if @debug_str
  f[:reset_lockout_counter] = process_acct_policy_f_date(f[:reset_lockout_counter]) / 60
  @debug_str += "%s minutes\n" % f[:reset_lockout_counter] if @debug_str

  @debug_str += "  next_rid              - %s\n" % f[:next_rid] if @debug_str

  @debug_str += "  pw_encrypt_pw_complex - 0x%02x\n" % f[:pw_encrypt_pw_complex] if @debug_str
  f[:pw_encrypt], f[:pw_complex] = process_acct_policy_f_pw_encrypt_pw_complex(f[:pw_encrypt_pw_complex])
  @debug_str += "    pw_encrypt          - %s\n" % f[:pw_encrypt] if @debug_str
  @debug_str += "    pw_complex          - %s\n" % f[:pw_complex] if @debug_str

  if @debug_str
    @debug_str += "  min_pw_len            - %s characters\n" % f[:min_pw_len]
    @debug_str += "  pw_hist               - %s passwords remembered\n" % f[:pw_hist]
    @debug_str += "  lockout_threshold     - %s attempts\n" % f[:lockout_threshold]
    @debug_str += "  syskey                - %s\n" % Accounts.rawBinaryToRegBinary(f[:syskey])
  end

  f
end

#process_acct_policy_f_date(data) ⇒ Object



317
318
319
320
321
# File 'lib/metadata/util/win32/Win32System.rb', line 317

def process_acct_policy_f_date(data)
  return 0 if data == 0 || data == 0x8000000000000000
  # minus from qword:ff + 1 = seconds x 10 million
  (0x10000000000000000 - data) / 10000000
end

#process_acct_policy_f_pw_encrypt_pw_complex(data) ⇒ Object



323
324
325
326
327
328
329
330
331
# File 'lib/metadata/util/win32/Win32System.rb', line 323

def process_acct_policy_f_pw_encrypt_pw_complex(data)
  pw_encrypt = data >> 4
  pw_encrypt = (pw_encrypt == 1)

  pw_complex = data & 0x0F
  pw_complex = (pw_complex == 1)

  return pw_encrypt, pw_complex
end

#to_xml(doc = nil) ⇒ Object



201
202
203
204
205
206
207
# File 'lib/metadata/util/win32/Win32System.rb', line 201

def to_xml(doc = nil)
  doc = MiqXml.createDoc(nil) unless doc
  osToXml(doc)
  accountPolicyToXml(doc)
  networksToXml(doc)
  doc
end