librex 0.0.70 → 0.0.71
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/README.markdown +5 -10
- data/Rakefile +1 -1
- data/lib/rex/arch.rb +1 -1
- data/lib/rex/encoder/bloxor/bloxor.rb +1 -0
- data/lib/rex/encoder/ndr.rb +1 -1
- data/lib/rex/exploitation/heaplib.rb +4 -2
- data/lib/rex/exploitation/powershell.rb +62 -0
- data/lib/rex/exploitation/powershell/function.rb +63 -0
- data/lib/rex/exploitation/powershell/obfu.rb +98 -0
- data/lib/rex/exploitation/powershell/output.rb +151 -0
- data/lib/rex/exploitation/powershell/param.rb +23 -0
- data/lib/rex/exploitation/powershell/parser.rb +183 -0
- data/lib/rex/exploitation/powershell/psh_methods.rb +70 -0
- data/lib/rex/exploitation/powershell/script.rb +99 -0
- data/lib/rex/exploitation/ropdb.rb +1 -0
- data/lib/rex/mac_oui.rb +1 -0
- data/lib/rex/ole/util.rb +2 -2
- data/lib/rex/parser/group_policy_preferences.rb +185 -0
- data/lib/rex/parser/outpost24_nokogiri.rb +1 -0
- data/lib/rex/poly/machine.rb +1 -0
- data/lib/rex/poly/machine/machine.rb +1 -0
- data/lib/rex/poly/machine/x86.rb +1 -0
- data/lib/rex/post/meterpreter/extensions/android/android.rb +128 -0
- data/lib/rex/post/meterpreter/extensions/android/tlv.rb +40 -0
- data/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_psapi.rb +32 -0
- data/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb +6 -6
- data/lib/rex/post/meterpreter/extensions/stdapi/railgun/multicall.rb +4 -4
- data/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb +2 -1
- data/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb +4 -4
- data/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb +4 -4
- data/lib/rex/post/meterpreter/packet.rb +3 -3
- data/lib/rex/post/meterpreter/ui/console.rb +2 -0
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb +383 -0
- data/lib/rex/proto/dcerpc/ndr.rb +1 -1
- data/lib/rex/proto/ipmi/channel_auth_reply.rb +1 -0
- data/lib/rex/proto/ipmi/open_session_reply.rb +1 -0
- data/lib/rex/proto/ipmi/rakp2.rb +1 -0
- data/lib/rex/proto/natpmp/packet.rb +8 -8
- data/lib/rex/proto/ntp.rb +3 -0
- data/lib/rex/proto/ntp/constants.rb +12 -0
- data/lib/rex/proto/ntp/modes.rb +130 -0
- data/lib/rex/proto/pjl.rb +1 -0
- data/lib/rex/proto/pjl/client.rb +1 -0
- data/lib/rex/proto/sip.rb +4 -0
- data/lib/rex/proto/sip/response.rb +61 -0
- data/lib/rex/proto/smb/exceptions.rb +11 -3
- data/lib/rex/random_identifier_generator.rb +1 -0
- data/lib/rex/registry/lfkey.rb +1 -1
- data/lib/rex/registry/nodekey.rb +10 -10
- data/lib/rex/registry/valuekey.rb +5 -5
- data/lib/rex/registry/valuelist.rb +1 -1
- data/lib/rex/socket/ip.rb +1 -0
- data/lib/rex/sslscan/result.rb +1 -0
- data/lib/rex/sslscan/scanner.rb +1 -0
- data/lib/rex/text.rb +2 -13
- data/lib/rex/ui/text/output/buffer/stdout.rb +1 -0
- data/lib/rex/ui/text/table.rb +4 -4
- 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
|
data/lib/rex/mac_oui.rb
CHANGED
data/lib/rex/ole/util.rb
CHANGED
@@ -124,7 +124,7 @@ class Util
|
|
124
124
|
|
125
125
|
|
126
126
|
def self.getUnicodeString(buf)
|
127
|
-
buf = buf.unpack('
|
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('
|
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
|
+
|