#!/usr/local/bin/ruby18
#
# $Id: msfencode 10409 2010-09-21 02:38:09Z jduck $
# $Revision: 10409 $
#

msfbase = __FILE__
while File.symlink?(msfbase)
	msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
end

$:.unshift(File.join(File.dirname(msfbase), 'lib'))
$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB']

require 'rex'
require 'msf/ui'
require 'msf/base'

OutStatus = "[*] "
OutError  = "[-] "

# Load supported formats
supported_formats = Msf::Simple::Buffer.transform_formats + Msf::Util::EXE.to_executable_fmt_formats

$args = Rex::Parser::Arguments.new(
	"-h" => [ false, "Help banner"                                                       ],
	"-l" => [ false, "List available encoders"                                           ],
	"-v" => [ false, "Increase verbosity"                                                ],
	# input/output
	"-i" => [ true, "Encode the contents of the supplied file path"                      ],
	"-m" => [ true, "Specifies an additional module search path"                         ],
	"-o" => [ true, "The output file"                                                    ],
	# architecture/platform
	"-a" => [ true, "The architecture to encode as"                                      ],
	"-p" => [ true, "The platform to encode for"                                         ],
	# format options
	"-t" => [ true, "The output format: #{supported_formats.join(',')}"                  ],
	# encoder options
	"-e" => [ true, "The encoder to use"                                                 ],
	"-n" => [ false, "Dump encoder information"                                          ],
	"-b" => [ true, "The list of characters to avoid: '\\x00\\xff'"                      ],
	"-s" => [ true, "The maximum size of the encoded data"                               ],
	"-c" => [ true, "The number of times to encode the data"                             ],
	# EXE generation options
	"-d" => [ true, "Specify the directory in which to look for EXE templates"           ],
	"-x" => [ true, "Specify an alternate executable template"                           ],
	"-k" => [ false, "Keep template working; run payload in new thread (use with -x)"    ]
)

#
# Dump the list of encoders
#
def dump_encoders(arch = nil)
	tbl = Rex::Ui::Text::Table.new(
		'Indent'  => 4,
		'Header'  => "Framework Encoders" + ((arch) ? " (architectures: #{arch})" : ""),
		'Columns' =>
			[
				"Name",
				"Rank",
				"Description"
			])
	cnt = 0

	$framework.encoders.each_module(
		'Arch' => arch ? arch.split(',') : nil) { |name, mod|
		tbl << [ name, mod.rank_to_s, mod.new.name ]

		cnt += 1
	}

	(cnt > 0) ? "\n" + tbl.to_s + "\n" : "\nNo compatible encoders found.\n\n"
end

#
# Returns the list of encoders to try
#
def get_encoders(arch, encoder)
	encoders = []

	if (encoder)
		encoders << $framework.encoders.create(encoder)
	else
		$framework.encoders.each_module_ranked(
			'Arch' => arch ? arch.split(',') : nil) { |name, mod|
			encoders << mod.new
		}
	end

	encoders
end

#
# Nuff said.
#
def usage
	$stderr.puts("\n" + "    Usage: #{$0} <options>\n" + $args.usage)
	exit
end

def write_encoded(buf)
	if (not $output)
		$stdout.write(buf)
	else
		File.open($output, "wb") do |fd|
			fd.write(buf)
		end
	end
end

# Defaults
verbose  = 0
cmd      = "encode"
arch     = nil
badchars = ''
space    = nil
encoder  = nil
fmt      = nil
input    = $stdin
options  = ''
delim    = '_|_'
output   = nil
ecount   = 1
plat     = nil

altexe   = nil
inject   = false
exedir   = nil  # use default

# Parse the argument and rock that shit.
$args.parse(ARGV) { |opt, idx, val|
	case opt
		when "-i"
			begin
				input = File.new(val)
			rescue
				$stderr.puts(OutError + "Failed to open file #{val}: #{$!}")
				exit
			end
		when "-m"
			$framework.modules.add_module_path(val)
		when "-l"
			cmd = "list"
		when "-n"
			cmd = "dump"
		when "-a"
			arch = val
		when "-c"
			ecount = val.to_i
		when "-b"
			badchars = Rex::Text.hex_to_raw(val)
		when "-p"
			plat = Msf::Module::PlatformList.transform(val)
		when "-s"
			space = val.to_i
		when "-t"
			if supported_formats.include?(val)
				fmt = val
			else
				$stderr.puts(OutError + "Invalid format: #{val}")
				exit
			end
		when "-o"
			$output = val
		when "-e"
			encoder = val

		when "-d"
			exedir = val
		when "-x"
			altexe = val
		when "-k"
			inject = true

		when "-h"
			usage
			
		when "-v"
			verbose += 1

		else
			if (val =~ /=/)
				options += ((options.length > 0) ? delim : "") + "#{val}"
			end
	end
}


if(not fmt and output)
	pre,ext = output.split('.')
	if(ext and not ext.empty?)
		fmt = ext
	end
end

if inject and not altexe
	$stderr.puts "[*] Error: the injection option must use a custom EXE template via -x, otherwise the injected payload will immediately exit when the main process dies."
	exit(1)
end
exeopts = {
	:inject => inject,
	:template => altexe,
	:template_path => exedir
}

# Initialize the simplified framework instance.
$framework = Msf::Simple::Framework.create(
	:module_types => [ Msf::MODULE_ENCODER, Msf::MODULE_NOP ],
	'DisableDatabase' => true
)

# Get the list of encoders to try
encoders = get_encoders(arch, encoder)

# Process the actual command
case cmd
	when "list"
		$stderr.puts(dump_encoders(arch))
	when "dump"
		enc = encoder ? $framework.encoders.create(encoder) : nil

		if (enc)
			$stderr.puts(Msf::Serializer::ReadableText.dump_module(enc))
		else
			$stderr.puts(OutError + "Invalid encoder specified.")
		end
	when "encode"
		buf = input.read

		encoders.each { |enc|
			next if not enc
			begin
				# Imports options
				enc.datastore.import_options_from_s(options, delim)

				skip = false
				eout = buf.dup
				raw  = nil

				1.upto(ecount) do |iteration|

					# Encode it up
					raw = enc.encode(eout, badchars, nil, plat)

					# Is it too big?
					if (space and space > 0 and raw.length > space)
						$stderr.puts(OutError + "#{enc.refname} created buffer that is too big (#{raw.length})")
						skip = true
						break
					end

					# Print it out
					$stderr.puts(OutStatus + "#{enc.refname} succeeded with size #{raw.length} (iteration=#{iteration})\n\n")
					eout = raw
				end

				next if skip

				output = Msf::Util::EXE.to_executable_fmt($framework, arch, plat, raw, fmt, exeopts)

				if not output
					fmt ||= "ruby"
					output = Msf::Simple::Buffer.transform(raw, fmt)
				end

				if exeopts[:fellback]
					$stderr.puts(OutError + "Warning: Falling back to default template: #{exeopts[:fellback]}")
				end

				write_encoded(output)

				exit

			rescue => e
				$stderr.puts(OutError + "#{enc.refname} failed: #{e}")
				if verbose > 0
					e.backtrace.each { |el|
						$stderr.puts(OutError + el.to_s)
					}
				end
			end
		}

		$stderr.puts(OutError + "No encoders succeeded.")
end
