Path: blob/master/modules/encoders/x86/single_static_bit.rb
19758 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45#6# NOTE: this encoder currently has only be tested using bit 5 set to on.7#8# The decoder has been tested with all possible values, but the decoder stub9# is was not designed to bypass restrictions other than "bit 5 must be on"..10#11class MetasploitModule < Msf::Encoder1213# This encoder has a manual ranking because it should only be used in cases14# where information has been explicitly supplied, specifically15# BitNumber and BitValue.16Rank = ManualRanking1718def initialize19super(20'Name' => 'Single Static Bit',21'Description' => 'Static value for specific bit',22'Author' => 'jduck',23'Arch' => ARCH_X86,24'License' => MSF_LICENSE,25'EncoderType' => Msf::Encoder::Type::SingleStaticBit26)2728# this shouldn't be present in the decoder stub.29@key_marker = 0x101030end3132#33# Returns the decoder stub that is adjusted for the size of34# the buffer being encoded35#36def decoder_stub(state)37bit_num = (datastore['BitNumber'] || 5).to_i38datastore['BitValue'] || true3940# variables:41# bit to ignore (global - hardcoded)42# buf len (can be deduced with a jmp/call/pop) (global - ebx)43# current source byte ptr (global - esi)44# current dest byte ptr (global - edi) ?45# current dest byte (global - ah) ?46# number of bits accumulated (global - ebp) ?47# current source byte (outer - al)48# bit index (for this byte) (inner - cl) ?49pre_init = ''50pre_init << "\x31\xed" # xor ebp, ebp - no bits accumulated51pre_init << "\x83\xe1\x01" # and ecx, $0x1 - init inner loop counter (set to 0/1)52pre_init << "\x83\xe3\x01" # and ebx, $0x1 - init buffer length53pre_init << "\x66\xbb" + [@key_marker].pack('v') # - load encrypted buffer length54pre_init << "\x66\x81\xf3" + [@key_marker].pack('v') # - xor decrypt buffer length5556# we stored an entire byte, move to the next one57next_byte = ''58next_byte << "\x83\xef\xff" # sub edi, 0xffffffff - increment dst pointer59next_byte << "\x31\xed" # xor ebp, ebp - no bits accumulated6061# inside the loop, we need to extract a bit, as62# specified by:63#64# ecx-1 - bit number to extract65# al - byte to extract it from66get_a_bit = ''67get_a_bit << "\x60" # pusha - save all registers68get_a_bit << "\x83\xe9\x01" # sub ecx, 1 - account for 1-based counting69get_a_bit << "\x74\x06" # jz +6 - skip dividing if bit zero70get_a_bit << "\xb3\x02" # mov bl, 2 - set divisor to 271# divide_it:72get_a_bit << "\xf6\xf3" # div bl - do the division73get_a_bit << "\xe2" + [-1 * (2 + 2)].pack('C') # - divide again..74# store_bit:75get_a_bit << "\x83\xe0\x01" # and eax, 0x01 - we only want the lowest bit76get_a_bit << "\x6b\x2f\x02" # imul ebp, 2, [edi] - load [edi], shifted left by 1, to ebp77get_a_bit << "\x09\xe8" # or ebp, eax - set bit 078get_a_bit << "\xaa" # stosb al, [edi] - store byte back79get_a_bit << "\x61" # popa - restore previous ebx/eax80get_a_bit << "\x83\xed\xff" # sub ebp, 0xffffffff - increment bits stored8182inner_init = ''83inner_init << "\xb1\x08" # mov cl, $0x8 - init loop counter8485inner_loop = ''86# process_bits:87inner_loop << "\x80\xf9" # cmp cl, <ignore_bit + 1> - is this the one to ignore?88inner_loop << [(bit_num + 1)].pack('C')89len = get_a_bit.length + 3 + 2 + next_byte.length90inner_loop << "\x74" + [len].pack('C') # - je next_bit91inner_loop << get_a_bit92inner_loop << "\x83\xfd\x08" # cmp ebp, $0x8 - got 8 bits now?93inner_loop << "\x75" + [next_byte.length].pack('C') # - jne to next_bit94# next_dst_byte:95inner_loop << next_byte96# next_bit:97# I really wish this silly padding wasn't necessary, however removing the bad characters in the98# jump/call displacements has proven difficult otherwise.99inner_loop << "\x90" * 0x1a # nops - for padding (so relative jumps don't have badchars)100len = -1 * (inner_loop.length + 2)101inner_loop << "\xe2" + [len].pack('C') # - loop process_bits102103# prefixed by: # jmp data_beg_call104outer_init = ''105# get_data_beg:106outer_init << "\x5e" # pop esi - ptr to beginning of data107outer_init << pre_init108outer_init << "\x89\xf7" # mov edi, esi - decode in place, init dst ptr109110outer_loop = ''111# outer_loop << "\x90" * (0xd+6)112outer_loop << "\x83\xe0\x7f" # and eax, 0x7f - we only want the low byte113outer_loop << "\xac" # lods al, [esi] - load src byte114outer_loop << inner_init << inner_loop115outer_loop << "\x83\xeb\x01" # sub ebx, 1 - 1 byte down!116outer_loop << "\x74\x07" # jz +(2+5) - jump to data!117len = -1 * (outer_loop.length + 2)118# next_byte:119outer_loop << "\xeb" + [len].pack('C') # - jmp process_byte120# data_beg_call:121122decoder = outer_init + outer_loop123jmp = "\xeb" + [decoder.length].pack('C')124call = "\xe8" + [-1 * (decoder.length + 5)].pack('V')125decoder = jmp + decoder + call126127# encoded sled128state.context = ''129130return decoder131end132133def encode_block(_state, block)134bit_num = (datastore['BitNumber'] || 5).to_i135bit_num = (7 - bit_num)136bit_val = datastore['BitValue'] || true137138encoded = ''139new_byte = 0140nbits = 0141142block.unpack('C*').each do |ch|1437.step(0, -1) do |x|144# is this the special bit?145if (nbits == bit_num)146new_byte <<= 1 if nbits > 0147new_byte |= 1 if bit_val148nbits += 1149150# do we have a full byte?151if nbits == 8152encoded << new_byte.chr153new_byte = 0154nbits = 0155end156end157158# we have space, add it in159new_byte <<= 1 if nbits > 0160new_byte += 1 if (((ch >> x) & 1) > 0)161nbits += 1162163# do we have a full byte?164if nbits == 8165encoded << new_byte.chr166new_byte = 0167nbits = 0168end169end170end171172# if we have bits left, pad out to a whole byte173if nbits > 0174while nbits < 8175new_byte <<= 1176new_byte |= 1 if (nbits == bit_num) && bit_val177nbits += 1178end179encoded << new_byte.chr180end181182return encoded183end184185#186# Appends the encoded context portion.187#188def encode_end(state)189state.encoded += state.context190191xor_key = 0192xor_key_str = ''193enc_len_str = ''194loop do195xor_key = rand(0x10000)196xor_key_str = [xor_key].pack('v')197enc_len_str = [state.encoded.length ^ xor_key].pack('v')198next if has_badchars?(xor_key_str, state.badchars)199next if has_badchars?(enc_len_str, state.badchars)200201break202end203204marker_str = [@key_marker].pack('v')205206state.encoded.sub!(marker_str, enc_len_str)207state.encoded.sub!(marker_str, xor_key_str)208end209end210211212