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.
- 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
|
+
|