Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/parser/group_policy_preferences.rb
Views: 11778
# -*- coding: binary -*-1#23module Rex4module Parser56# This is a parser for the Windows Group Policy Preferences file7# format. It's used by modules/post/windows/gather/credentials/gpp.rb8# and uses REXML (as opposed to Nokogiri) for its XML parsing.9# See: http://msdn.microsoft.com/en-gb/library/cc232587.aspx10class GPP11require 'rex'12require 'rexml/document'1314def self.parse(data)15if data.nil?16return []17end1819xml = REXML::Document.new(data).root20results = []2122unless xml and xml.elements and xml.elements.to_a("//Properties")23return []24end2526xml.elements.to_a("//Properties").each do |node|27epassword = node.attributes['cpassword']28next if epassword.to_s.empty?29password = self.decrypt(epassword)3031user = node.attributes['runAs'] if node.attributes['runAs']32user = node.attributes['accountName'] if node.attributes['accountName']33user = node.attributes['username'] if node.attributes['username']34user = node.attributes['userName'] if node.attributes['userName']35user = node.attributes['newName'] unless node.attributes['newName'].nil? || node.attributes['newName'].empty?36changed = node.parent.attributes['changed']3738# Printers and Shares39path = node.attributes['path']4041# Datasources42dsn = node.attributes['dsn']43driver = node.attributes['driver']4445# Tasks46app_name = node.attributes['appName']4748# Services49service = node.attributes['serviceName']5051# Groups52expires = node.attributes['expires']53never_expires = node.attributes['neverExpires']54disabled = node.attributes['acctDisabled']5556result = {57:USER => user,58:PASS => password,59:CHANGED => changed60}6162result.merge!({ :EXPIRES => expires }) unless expires.nil? || expires.empty?63result.merge!({ :NEVER_EXPIRES => never_expires.to_i }) unless never_expires.nil? || never_expires.empty?64result.merge!({ :DISABLED => disabled.to_i }) unless disabled.nil? || disabled.empty?65result.merge!({ :PATH => path }) unless path.nil? || path.empty?66result.merge!({ :DATASOURCE => dsn }) unless dsn.nil? || dsn.empty?67result.merge!({ :DRIVER => driver }) unless driver.nil? || driver.empty?68result.merge!({ :TASK => app_name }) unless app_name.nil? || app_name.empty?69result.merge!({ :SERVICE => service }) unless service.nil? || service.empty?7071attributes = []72node.elements.each('//Attributes//Attribute') do |dsn_attribute|73attributes << {74:A_NAME => dsn_attribute.attributes['name'],75:A_VALUE => dsn_attribute.attributes['value']76}77end7879result.merge!({ :ATTRIBUTES => attributes }) unless attributes.empty?8081results << result82end8384results85end8687def self.create_tables(results, filetype, domain=nil, dc=nil)88tables = []89results.each do |result|90table = Rex::Text::Table.new(91'Header' => 'Group Policy Credential Info',92'Indent' => 1,93'SortIndex' => -1,94'Columns' =>95[96'Name',97'Value',98]99)100101table << ["TYPE", filetype]102table << ["USERNAME", result[:USER]]103table << ["PASSWORD", result[:PASS]]104table << ["DOMAIN CONTROLLER", dc] unless dc.nil? || dc.empty?105table << ["DOMAIN", domain] unless domain.nil? || domain.empty?106table << ["CHANGED", result[:CHANGED]]107table << ["EXPIRES", result[:EXPIRES]] unless result[:EXPIRES].nil? || result[:EXPIRES].empty?108table << ["NEVER_EXPIRES?", result[:NEVER_EXPIRES]] unless result[:NEVER_EXPIRES].nil?109table << ["DISABLED", result[:DISABLED]] unless result[:DISABLED].nil?110table << ["PATH", result[:PATH]] unless result[:PATH].nil? || result[:PATH].empty?111table << ["DATASOURCE", result[:DSN]] unless result[:DSN].nil? || result[:DSN].empty?112table << ["DRIVER", result[:DRIVER]] unless result[:DRIVER].nil? || result[:DRIVER].empty?113table << ["TASK", result[:TASK]] unless result[:TASK].nil? || result[:TASK].empty?114table << ["SERVICE", result[:SERVICE]] unless result[:SERVICE].nil? || result[:SERVICE].empty?115116unless result[:ATTRIBUTES].nil? || result[:ATTRIBUTES].empty?117result[:ATTRIBUTES].each do |dsn_attribute|118table << ["ATTRIBUTE", "#{dsn_attribute[:A_NAME]} - #{dsn_attribute[:A_VALUE]}"]119end120end121122tables << table123end124125tables126end127128# Decrypts passwords using Microsoft's published key:129# http://msdn.microsoft.com/en-us/library/cc422924.aspx130def self.decrypt(encrypted_data)131password = ""132return password unless encrypted_data133134password = ""135retries = 0136original_data = encrypted_data.dup137138begin139mod = encrypted_data.length % 4140141# PowerSploit code strips the last character, unsure why...142case mod143when 1144encrypted_data = encrypted_data[0..-2]145when 2, 3146padding = '=' * (4 - mod)147encrypted_data = "#{encrypted_data}#{padding}"148end149150# Strict base64 decoding used here151decoded = encrypted_data.unpack('m0').first152rescue ::ArgumentError => e153# Appears to be some junk UTF-8 Padding appended at times in154# Win2k8 (not in Win2k8R2)155# Lets try stripping junk and see if we can decrypt156if retries < 8157retries += 1158original_data = original_data[0..-2]159encrypted_data = original_data160retry161else162return password163end164end165166key = "\x4e\x99\x06\xe8\xfc\xb6\x6c\xc9\xfa\xf4\x93\x10\x62\x0f\xfe\xe8\xf4\x96\xe8\x06\xcc\x05\x79\x90\x20\x9b\x09\xa4\x33\xb6\x6c\x1b"167aes = OpenSSL::Cipher.new("AES-256-CBC")168begin169aes.decrypt170aes.key = key171plaintext = aes.update(decoded)172plaintext << aes.final173password = plaintext.unpack('v*').pack('C*') # UNICODE conversion174rescue OpenSSL::Cipher::CipherError => e175puts "Unable to decode: \"#{encrypted_data}\" Exception: #{e}"176end177178password179end180181end182end183end184185186187