librex 0.0.70 → 0.0.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +5 -13
  2. data/README.markdown +5 -10
  3. data/Rakefile +1 -1
  4. data/lib/rex/arch.rb +1 -1
  5. data/lib/rex/encoder/bloxor/bloxor.rb +1 -0
  6. data/lib/rex/encoder/ndr.rb +1 -1
  7. data/lib/rex/exploitation/heaplib.rb +4 -2
  8. data/lib/rex/exploitation/powershell.rb +62 -0
  9. data/lib/rex/exploitation/powershell/function.rb +63 -0
  10. data/lib/rex/exploitation/powershell/obfu.rb +98 -0
  11. data/lib/rex/exploitation/powershell/output.rb +151 -0
  12. data/lib/rex/exploitation/powershell/param.rb +23 -0
  13. data/lib/rex/exploitation/powershell/parser.rb +183 -0
  14. data/lib/rex/exploitation/powershell/psh_methods.rb +70 -0
  15. data/lib/rex/exploitation/powershell/script.rb +99 -0
  16. data/lib/rex/exploitation/ropdb.rb +1 -0
  17. data/lib/rex/mac_oui.rb +1 -0
  18. data/lib/rex/ole/util.rb +2 -2
  19. data/lib/rex/parser/group_policy_preferences.rb +185 -0
  20. data/lib/rex/parser/outpost24_nokogiri.rb +1 -0
  21. data/lib/rex/poly/machine.rb +1 -0
  22. data/lib/rex/poly/machine/machine.rb +1 -0
  23. data/lib/rex/poly/machine/x86.rb +1 -0
  24. data/lib/rex/post/meterpreter/extensions/android/android.rb +128 -0
  25. data/lib/rex/post/meterpreter/extensions/android/tlv.rb +40 -0
  26. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_psapi.rb +32 -0
  27. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb +6 -6
  28. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/multicall.rb +4 -4
  29. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb +2 -1
  30. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb +4 -4
  31. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb +4 -4
  32. data/lib/rex/post/meterpreter/packet.rb +3 -3
  33. data/lib/rex/post/meterpreter/ui/console.rb +2 -0
  34. data/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb +383 -0
  35. data/lib/rex/proto/dcerpc/ndr.rb +1 -1
  36. data/lib/rex/proto/ipmi/channel_auth_reply.rb +1 -0
  37. data/lib/rex/proto/ipmi/open_session_reply.rb +1 -0
  38. data/lib/rex/proto/ipmi/rakp2.rb +1 -0
  39. data/lib/rex/proto/natpmp/packet.rb +8 -8
  40. data/lib/rex/proto/ntp.rb +3 -0
  41. data/lib/rex/proto/ntp/constants.rb +12 -0
  42. data/lib/rex/proto/ntp/modes.rb +130 -0
  43. data/lib/rex/proto/pjl.rb +1 -0
  44. data/lib/rex/proto/pjl/client.rb +1 -0
  45. data/lib/rex/proto/sip.rb +4 -0
  46. data/lib/rex/proto/sip/response.rb +61 -0
  47. data/lib/rex/proto/smb/exceptions.rb +11 -3
  48. data/lib/rex/random_identifier_generator.rb +1 -0
  49. data/lib/rex/registry/lfkey.rb +1 -1
  50. data/lib/rex/registry/nodekey.rb +10 -10
  51. data/lib/rex/registry/valuekey.rb +5 -5
  52. data/lib/rex/registry/valuelist.rb +1 -1
  53. data/lib/rex/socket/ip.rb +1 -0
  54. data/lib/rex/sslscan/result.rb +1 -0
  55. data/lib/rex/sslscan/scanner.rb +1 -0
  56. data/lib/rex/text.rb +2 -13
  57. data/lib/rex/ui/text/output/buffer/stdout.rb +1 -0
  58. data/lib/rex/ui/text/table.rb +4 -4
  59. metadata +23 -4
@@ -0,0 +1,23 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Exploitation
5
+ module Powershell
6
+ class Param
7
+ attr_accessor :klass, :name
8
+ def initialize(klass, name)
9
+ @klass = klass.strip
10
+ @name = name.strip.gsub(/\s|,/, '')
11
+ end
12
+
13
+ #
14
+ # To String
15
+ #
16
+ # @return [String] Powershell param
17
+ def to_s
18
+ "[#{klass}]$#{name}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,183 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Exploitation
5
+ module Powershell
6
+ module Parser
7
+ # Reserved special variables
8
+ # Acquired with: Get-Variable | Format-Table name, value -auto
9
+ RESERVED_VARIABLE_NAMES = [
10
+ '$$',
11
+ '$?',
12
+ '$^',
13
+ '$_',
14
+ '$args',
15
+ '$ConfirmPreference',
16
+ '$ConsoleFileName',
17
+ '$DebugPreference',
18
+ '$Env',
19
+ '$Error',
20
+ '$ErrorActionPreference',
21
+ '$ErrorView',
22
+ '$ExecutionContext',
23
+ '$false',
24
+ '$FormatEnumerationLimit',
25
+ '$HOME',
26
+ '$Host',
27
+ '$input',
28
+ '$LASTEXITCODE',
29
+ '$MaximumAliasCount',
30
+ '$MaximumDriveCount',
31
+ '$MaximumErrorCount',
32
+ '$MaximumFunctionCount',
33
+ '$MaximumHistoryCount',
34
+ '$MaximumVariableCount',
35
+ '$MyInvocation',
36
+ '$NestedPromptLevel',
37
+ '$null',
38
+ '$OutputEncoding',
39
+ '$PID',
40
+ '$PROFILE',
41
+ '$ProgressPreference',
42
+ '$PSBoundParameters',
43
+ '$PSCulture',
44
+ '$PSEmailServer',
45
+ '$PSHOME',
46
+ '$PSSessionApplicationName',
47
+ '$PSSessionConfigurationName',
48
+ '$PSSessionOption',
49
+ '$PSUICulture',
50
+ '$PSVersionTable',
51
+ '$PWD',
52
+ '$ReportErrorShowExceptionClass',
53
+ '$ReportErrorShowInnerException',
54
+ '$ReportErrorShowSource',
55
+ '$ReportErrorShowStackTrace',
56
+ '$ShellId',
57
+ '$StackTrace',
58
+ '$true',
59
+ '$VerbosePreference',
60
+ '$WarningPreference',
61
+ '$WhatIfPreference'
62
+ ].map(&:downcase).freeze
63
+
64
+ #
65
+ # Get variable names from code, removes reserved names from return
66
+ #
67
+ # @return [Array] variable names
68
+ def get_var_names
69
+ our_vars = code.scan(/\$[a-zA-Z\-\_0-9]+/).uniq.flatten.map(&:strip)
70
+ our_vars.select { |v| !RESERVED_VARIABLE_NAMES.include?(v.downcase) }
71
+ end
72
+
73
+ #
74
+ # Get function names from code
75
+ #
76
+ # @return [Array] function names
77
+ def get_func_names
78
+ code.scan(/function\s([a-zA-Z\-\_0-9]+)/).uniq.flatten
79
+ end
80
+
81
+ #
82
+ # Attempt to find string literals in PSH expression
83
+ #
84
+ # @return [Array] string literals
85
+ def get_string_literals
86
+ code.scan(/@"(.+?)"@|@'(.+?)'@/m)
87
+ end
88
+
89
+ #
90
+ # Scan code and return matches with index
91
+ #
92
+ # @param str [String] string to match in code
93
+ # @param source [String] source code to match, defaults to @code
94
+ #
95
+ # @return [Array[String,Integer]] matched items with index
96
+ def scan_with_index(str, source = code)
97
+ ::Enumerator.new do |y|
98
+ source.scan(str) do
99
+ y << ::Regexp.last_match
100
+ end
101
+ end.map { |m| [m.to_s, m.offset(0)[0]] }
102
+ end
103
+
104
+ #
105
+ # Return matching bracket type
106
+ #
107
+ # @param char [String] opening bracket character
108
+ #
109
+ # @return [String] matching closing bracket
110
+ def match_start(char)
111
+ case char
112
+ when '{'
113
+ '}'
114
+ when '('
115
+ ')'
116
+ when '['
117
+ ']'
118
+ when '<'
119
+ '>'
120
+ else
121
+ fail ArgumentError, 'Unknown starting bracket'
122
+ end
123
+ end
124
+
125
+ #
126
+ # Extract block of code inside brackets/parenthesis
127
+ #
128
+ # Attempts to match the bracket at idx, handling nesting manually
129
+ # Once the balanced matching bracket is found, all script content
130
+ # between idx and the index of the matching bracket is returned
131
+ #
132
+ # @param idx [Integer] index of opening bracket
133
+ #
134
+ # @return [String] content between matching brackets
135
+ def block_extract(idx)
136
+ fail ArgumentError unless idx
137
+
138
+ if idx < 0 || idx >= code.length
139
+ fail ArgumentError, 'Invalid index'
140
+ end
141
+
142
+ start = code[idx]
143
+ stop = match_start(start)
144
+ delims = scan_with_index(/#{Regexp.escape(start)}|#{Regexp.escape(stop)}/, code[idx + 1..-1])
145
+ delims.map { |x| x[1] = x[1] + idx + 1 }
146
+ c = 1
147
+ sidx = nil
148
+ # Go through delims till we balance, get idx
149
+ while (c != 0) && (x = delims.shift)
150
+ sidx = x[1]
151
+ x[0] == stop ? c -= 1 : c += 1
152
+ end
153
+
154
+ code[idx..sidx]
155
+ end
156
+
157
+ #
158
+ # Extract a block of function code
159
+ #
160
+ # @param func_name [String] function name
161
+ # @param delete [Boolean] delete the function from the code
162
+ #
163
+ # @return [String] function block
164
+ def get_func(func_name, delete = false)
165
+ start = code.index(func_name)
166
+
167
+ return nil unless start
168
+
169
+ idx = code[start..-1].index('{') + start
170
+ func_txt = block_extract(idx)
171
+
172
+ if delete
173
+ delete_code = code[0..idx]
174
+ delete_code << code[(idx + func_txt.length)..-1]
175
+ @code = delete_code
176
+ end
177
+
178
+ Function.new(func_name, func_txt)
179
+ end
180
+ end # Parser
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,70 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Exploitation
5
+ module Powershell
6
+ ##
7
+ # Convenience methods for generating powershell code in Ruby
8
+ ##
9
+
10
+ module PshMethods
11
+ #
12
+ # Download file via .NET WebClient
13
+ #
14
+ # @param src [String] URL to the file
15
+ # @param target [String] Location to save the file
16
+ #
17
+ # @return [String] Powershell code to download a file
18
+ def self.download(src, target)
19
+ target ||= '$pwd\\' << src.split('/').last
20
+ %Q^(new-object System.Net.WebClient).DownloadFile("#{src}", "#{target}")^
21
+ end
22
+
23
+ #
24
+ # Uninstall app, or anything named like app
25
+ #
26
+ # @param app [String] Name of application
27
+ # @param fuzzy [Boolean] Whether to apply a fuzzy match (-like) to
28
+ # the application name
29
+ #
30
+ # @return [String] Powershell code to uninstall an application
31
+ def self.uninstall(app, fuzzy = true)
32
+ match = fuzzy ? '-like' : '-eq'
33
+ %Q^$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name #{match} "#{app}" }; $app.Uninstall()^
34
+ end
35
+
36
+ #
37
+ # Create secure string from plaintext
38
+ #
39
+ # @param str [String] String to create as a SecureString
40
+ #
41
+ # @return [String] Powershell code to create a SecureString
42
+ def self.secure_string(str)
43
+ %Q(ConvertTo-SecureString -string '#{str}' -AsPlainText -Force$)
44
+ end
45
+
46
+ #
47
+ # Find PID of file lock owner
48
+ #
49
+ # @param filename [String] Filename
50
+ #
51
+ # @return [String] Powershell code to identify the PID of a file
52
+ # lock owner
53
+ def self.who_locked_file(filename)
54
+ %Q^ Get-Process | foreach{$processVar = $_;$_.Modules | foreach{if($_.FileName -eq "#{filename}"){$processVar.Name + " PID:" + $processVar.id}}}^
55
+ end
56
+
57
+ #
58
+ # Return last time of login
59
+ #
60
+ # @param user [String] Username
61
+ #
62
+ # @return [String] Powershell code to return the last time of a user
63
+ # login
64
+ def self.get_last_login(user)
65
+ %Q^ Get-QADComputer -ComputerRole DomainController | foreach { (Get-QADUser -Service $_.Name -SamAccountName "#{user}").LastLogon} | Measure-Latest^
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,99 @@
1
+ # -*- coding: binary -*-
2
+
3
+ require 'rex'
4
+ require 'forwardable'
5
+
6
+ module Rex
7
+ module Exploitation
8
+ module Powershell
9
+ class Script
10
+ attr_accessor :code
11
+ attr_reader :functions, :rig
12
+
13
+ include Output
14
+ include Parser
15
+ include Obfu
16
+ # Pretend we are actually a string
17
+ extend ::Forwardable
18
+ # In case someone messes with String we delegate based on its instance methods
19
+ # eval %Q|def_delegators :@code, :#{::String.instance_methods[0..(String.instance_methods.index(:class)-1)].join(', :')}|
20
+ def_delegators :@code, :each_line, :strip, :chars, :intern, :chr, :casecmp, :ascii_only?, :<, :tr_s,
21
+ :!=, :capitalize!, :ljust, :to_r, :sum, :private_methods, :gsub, :dump, :match, :to_sym,
22
+ :enum_for, :display, :tr_s!, :freeze, :gsub, :split, :rindex, :<<, :<=>, :+, :lstrip!,
23
+ :encoding, :start_with?, :swapcase, :lstrip!, :encoding, :start_with?, :swapcase,
24
+ :each_byte, :lstrip, :codepoints, :insert, :getbyte, :swapcase!, :delete, :rjust, :>=,
25
+ :!, :count, :slice, :clone, :chop!, :prepend, :succ!, :upcase, :include?, :frozen?,
26
+ :delete!, :chop, :lines, :replace, :next, :=~, :==, :rstrip!, :%, :upcase!, :each_char,
27
+ :hash, :rstrip, :length, :reverse, :setbyte, :bytesize, :squeeze, :>, :center, :[],
28
+ :<=, :to_c, :slice!, :chomp!, :next!, :downcase, :unpack, :crypt, :partition,
29
+ :between?, :squeeze!, :to_s, :chomp, :bytes, :clear, :!~, :to_i, :valid_encoding?, :===,
30
+ :tr, :downcase!, :scan, :sub!, :each_codepoint, :reverse!, :class, :size, :empty?, :byteslice,
31
+ :initialize_clone, :to_str, :to_enum, :tap, :tr!, :trust, :encode!, :sub, :oct, :succ, :index,
32
+ :[]=, :encode, :*, :hex, :to_f, :strip!, :rpartition, :ord, :capitalize, :upto, :force_encoding,
33
+ :end_with?
34
+
35
+ def initialize(code)
36
+ @code = ''
37
+ @rig = Rex::RandomIdentifierGenerator.new
38
+
39
+ begin
40
+ # Open code file for reading
41
+ fd = ::File.new(code, 'rb')
42
+ while (line = fd.gets)
43
+ @code << line
44
+ end
45
+
46
+ # Close open file
47
+ fd.close
48
+ rescue Errno::ENAMETOOLONG, Errno::ENOENT
49
+ # Treat code as a... code
50
+ @code = code.to_s.dup # in case we're eating another script
51
+ end
52
+ @functions = get_func_names.map { |f| get_func(f) }
53
+ end
54
+
55
+ ##
56
+ # Class methods
57
+ ##
58
+
59
+ #
60
+ # Convert binary to byte array, read from file if able
61
+ #
62
+ # @param input_data [String] Path to powershell file or powershell
63
+ # code string
64
+ # @param var_name [String] Byte array variable name
65
+ #
66
+ # @return [String] input_data as a powershell byte array
67
+ def self.to_byte_array(input_data, var_name = Rex::Text.rand_text_alpha(rand(3) + 3))
68
+ # File will raise an exception if the path contains null byte
69
+ if input_data.include? "\x00"
70
+ code = input_data
71
+ else
72
+ code = ::File.file?(input_data) ? ::File.read(input_data) : input_data
73
+ end
74
+
75
+ code = code.unpack('C*')
76
+ psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}"
77
+ lines = []
78
+ 1.upto(code.length - 1) do |byte|
79
+ if (byte % 10 == 0)
80
+ lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}"
81
+ else
82
+ lines.push ",0x#{code[byte].to_s(16)}"
83
+ end
84
+ end
85
+
86
+ psh << lines.join('') + "\r\n"
87
+ end
88
+
89
+ #
90
+ # Return list of code modifier methods
91
+ #
92
+ # @return [Array] Code modifiers
93
+ def self.code_modifiers
94
+ instance_methods.select { |m| m =~ /^(strip|sub)/ }
95
+ end
96
+ end # class Script
97
+ end
98
+ end
99
+ end
@@ -1,3 +1,4 @@
1
+ # -*- coding: binary -*-
1
2
  require 'rex/text'
2
3
  require 'rexml/document'
3
4
 
@@ -1,3 +1,4 @@
1
+ # -*- coding: binary -*-
1
2
 
2
3
  module Rex
3
4
  module Oui
@@ -124,7 +124,7 @@ class Util
124
124
 
125
125
 
126
126
  def self.getUnicodeString(buf)
127
- buf = buf.unpack('S*').pack('C*')
127
+ buf = buf.unpack('v*').pack('C*')
128
128
  if (idx = buf.index(0x00.chr))
129
129
  buf.slice!(idx, buf.length)
130
130
  end
@@ -132,7 +132,7 @@ class Util
132
132
  end
133
133
 
134
134
  def self.putUnicodeString(buf)
135
- buf = buf.unpack('C*').pack('S*')
135
+ buf = buf.unpack('C*').pack('v*')
136
136
  if (buf.length < 0x40)
137
137
  buf << "\x00" * (0x40 - buf.length)
138
138
  end
@@ -0,0 +1,185 @@
1
+ # -*- coding: binary -*-
2
+ #
3
+
4
+ module Rex
5
+ module Parser
6
+
7
+ # This is a parser for the Windows Group Policy Preferences file
8
+ # format. It's used by modules/post/windows/gather/credentials/gpp.rb
9
+ # and uses REXML (as opposed to Nokogiri) for its XML parsing.
10
+ # See: http://msdn.microsoft.com/en-gb/library/cc232587.aspx
11
+ class GPP
12
+ require 'rex'
13
+ require 'rexml/document'
14
+
15
+ def self.parse(data)
16
+ if data.nil?
17
+ return []
18
+ end
19
+
20
+ xml = REXML::Document.new(data).root
21
+ results = []
22
+
23
+ unless xml and xml.elements and xml.elements.to_a("//Properties")
24
+ return []
25
+ end
26
+
27
+ xml.elements.to_a("//Properties").each do |node|
28
+ epassword = node.attributes['cpassword']
29
+ next if epassword.to_s.empty?
30
+ password = self.decrypt(epassword)
31
+
32
+ user = node.attributes['runAs'] if node.attributes['runAs']
33
+ user = node.attributes['accountName'] if node.attributes['accountName']
34
+ user = node.attributes['username'] if node.attributes['username']
35
+ user = node.attributes['userName'] if node.attributes['userName']
36
+ user = node.attributes['newName'] unless node.attributes['newName'].nil? || node.attributes['newName'].empty?
37
+ changed = node.parent.attributes['changed']
38
+
39
+ # Printers and Shares
40
+ path = node.attributes['path']
41
+
42
+ # Datasources
43
+ dsn = node.attributes['dsn']
44
+ driver = node.attributes['driver']
45
+
46
+ # Tasks
47
+ app_name = node.attributes['appName']
48
+
49
+ # Services
50
+ service = node.attributes['serviceName']
51
+
52
+ # Groups
53
+ expires = node.attributes['expires']
54
+ never_expires = node.attributes['neverExpires']
55
+ disabled = node.attributes['acctDisabled']
56
+
57
+ result = {
58
+ :USER => user,
59
+ :PASS => password,
60
+ :CHANGED => changed
61
+ }
62
+
63
+ result.merge!({ :EXPIRES => expires }) unless expires.nil? || expires.empty?
64
+ result.merge!({ :NEVER_EXPIRES => never_expires.to_i }) unless never_expires.nil? || never_expires.empty?
65
+ result.merge!({ :DISABLED => disabled.to_i }) unless disabled.nil? || disabled.empty?
66
+ result.merge!({ :PATH => path }) unless path.nil? || path.empty?
67
+ result.merge!({ :DATASOURCE => dsn }) unless dsn.nil? || dsn.empty?
68
+ result.merge!({ :DRIVER => driver }) unless driver.nil? || driver.empty?
69
+ result.merge!({ :TASK => app_name }) unless app_name.nil? || app_name.empty?
70
+ result.merge!({ :SERVICE => service }) unless service.nil? || service.empty?
71
+
72
+ attributes = []
73
+ node.elements.each('//Attributes//Attribute') do |dsn_attribute|
74
+ attributes << {
75
+ :A_NAME => dsn_attribute.attributes['name'],
76
+ :A_VALUE => dsn_attribute.attributes['value']
77
+ }
78
+ end
79
+
80
+ result.merge!({ :ATTRIBUTES => attributes }) unless attributes.empty?
81
+
82
+ results << result
83
+ end
84
+
85
+ results
86
+ end
87
+
88
+ def self.create_tables(results, filetype, domain=nil, dc=nil)
89
+ tables = []
90
+ results.each do |result|
91
+ table = Rex::Ui::Text::Table.new(
92
+ 'Header' => 'Group Policy Credential Info',
93
+ 'Indent' => 1,
94
+ 'SortIndex' => -1,
95
+ 'Columns' =>
96
+ [
97
+ 'Name',
98
+ 'Value',
99
+ ]
100
+ )
101
+
102
+ table << ["TYPE", filetype]
103
+ table << ["USERNAME", result[:USER]]
104
+ table << ["PASSWORD", result[:PASS]]
105
+ table << ["DOMAIN CONTROLLER", dc] unless dc.nil? || dc.empty?
106
+ table << ["DOMAIN", domain] unless domain.nil? || domain.empty?
107
+ table << ["CHANGED", result[:CHANGED]]
108
+ table << ["EXPIRES", result[:EXPIRES]] unless result[:EXPIRES].nil? || result[:EXPIRES].empty?
109
+ table << ["NEVER_EXPIRES?", result[:NEVER_EXPIRES]] unless result[:NEVER_EXPIRES].nil?
110
+ table << ["DISABLED", result[:DISABLED]] unless result[:DISABLED].nil?
111
+ table << ["PATH", result[:PATH]] unless result[:PATH].nil? || result[:PATH].empty?
112
+ table << ["DATASOURCE", result[:DSN]] unless result[:DSN].nil? || result[:DSN].empty?
113
+ table << ["DRIVER", result[:DRIVER]] unless result[:DRIVER].nil? || result[:DRIVER].empty?
114
+ table << ["TASK", result[:TASK]] unless result[:TASK].nil? || result[:TASK].empty?
115
+ table << ["SERVICE", result[:SERVICE]] unless result[:SERVICE].nil? || result[:SERVICE].empty?
116
+
117
+ unless result[:ATTRIBUTES].nil? || result[:ATTRIBUTES].empty?
118
+ result[:ATTRIBUTES].each do |dsn_attribute|
119
+ table << ["ATTRIBUTE", "#{dsn_attribute[:A_NAME]} - #{dsn_attribute[:A_VALUE]}"]
120
+ end
121
+ end
122
+
123
+ tables << table
124
+ end
125
+
126
+ tables
127
+ end
128
+
129
+ # Decrypts passwords using Microsoft's published key:
130
+ # http://msdn.microsoft.com/en-us/library/cc422924.aspx
131
+ def self.decrypt(encrypted_data)
132
+ password = ""
133
+ return password unless encrypted_data
134
+
135
+ password = ""
136
+ retries = 0
137
+ original_data = encrypted_data.dup
138
+
139
+ begin
140
+ mod = encrypted_data.length % 4
141
+
142
+ # PowerSploit code strips the last character, unsure why...
143
+ case mod
144
+ when 1
145
+ encrypted_data = encrypted_data[0..-2]
146
+ when 2, 3
147
+ padding = '=' * (4 - mod)
148
+ encrypted_data = "#{encrypted_data}#{padding}"
149
+ end
150
+
151
+ # Strict base64 decoding used here
152
+ decoded = encrypted_data.unpack('m0').first
153
+ rescue ::ArgumentError => e
154
+ # Appears to be some junk UTF-8 Padding appended at times in
155
+ # Win2k8 (not in Win2k8R2)
156
+ # Lets try stripping junk and see if we can decrypt
157
+ if retries < 8
158
+ retries += 1
159
+ original_data = original_data[0..-2]
160
+ encrypted_data = original_data
161
+ retry
162
+ else
163
+ return password
164
+ end
165
+ end
166
+
167
+ key = "\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"
168
+ aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
169
+ begin
170
+ aes.decrypt
171
+ aes.key = key
172
+ plaintext = aes.update(decoded)
173
+ plaintext << aes.final
174
+ password = plaintext.unpack('v*').pack('C*') # UNICODE conversion
175
+ rescue OpenSSL::Cipher::CipherError => e
176
+ puts "Unable to decode: \"#{encrypted_data}\" Exception: #{e}"
177
+ end
178
+
179
+ password
180
+ end
181
+
182
+ end
183
+ end
184
+ end
185
+