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 |
#!/usr/bin/env ruby # encoding: ASCII-8BIT # By Ramon de C Valle. This work is dedicated to the public domain. require 'openssl' require 'optparse' require 'socket' Version = [0, 0, 1] Release = nil def prf(secret, label, seed) if secret.empty? s1 = s2 = '' else length = ((secret.length * 1.0) / 2).ceil s1 = secret[0..(length - 1)] s2 = secret[(length - 1)..(secret.length - 1)] end hmac_md5 = OpenSSL::HMAC.digest(OpenSSL::Digest.new('md5'), s1, label + seed) hmac_md5 = OpenSSL::HMAC.digest(OpenSSL::Digest.new('md5'), s1, hmac_md5 + label + seed) hmac_sha1 = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), s2, label + seed) hmac_sha1 = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), s2, hmac_sha1 + label + seed) result = '' [hmac_md5.length, hmac_sha1.length].max.times { |i| result << [(hmac_md5.getbyte(i) || 0) ^ (hmac_sha1.getbyte(i) || 0)].pack('C') } result end def prf_sha256(secret, label, seed) hmac_sha256 = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), secret, label + seed) OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), secret, hmac_sha256 + label + seed) end class String def hexdump(stream=$stdout) 0.step(bytesize - 1, 16) do |i| stream.printf('%08x', i) 0.upto(15) do |j| stream.printf(' ') if j == 8 if i + j >= bytesize stream.printf(' ') else stream.printf('%02x ', getbyte(i + j)) end end stream.printf(' ') 0.upto(15) do |j| if i + j >= bytesize stream.printf(' ') else if /[[:print:]]/ === getbyte(i + j).chr && /[^[:space:]]/ === getbyte(i + j).chr stream.printf('%c', getbyte(i + j)) else stream.printf('.') end end end stream.printf("\n") end end end options = {} OptionParser.new do |parser| parser.banner = "Usage: #{parser.program_name} [options] host" parser.separator('') parser.separator('Options:') parser.on('-H', '--local-host HOST', 'Local host') do |host| options[:local_host] = host end parser.on('-P', '--local-port PORT', 'Local port') do |port| options[:local_port] = port end parser.on('-d', '--debug', 'Debug mode') do options[:debug] = true end parser.on('-h', '--help', 'Show this message') do puts parser exit end parser.on('-o', '--output FILE', 'Output file') do |file| options[:file] = File.new(file, 'w+b') end parser.on('-p', '--port PORT', 'Port') do |port| options[:port] = port end parser.on('-v', '--verbose', 'Verbose mode') do options[:verbose] = true end parser.on('--version', 'Show version') do puts parser.ver exit end end.parse! local_host = options[:local_host] || '0.0.0.0' local_port = options[:local_port] || 443 debug = options[:debug] || false file = options[:file] || nil host = ARGV[0] or fail ArgumentError, 'no host given' port = options[:port] || 443 verbose = options[:verbose] || false proxy = TCPServer.new(local_host, local_port) puts 'Listening on %s:%d' % [proxy.addr[2], proxy.addr[1]] if debug || verbose loop do Thread.start(proxy.accept) do |client| puts 'Accepted connection from %s:%d' % [client.peeraddr[2], client.peeraddr[1]] if debug || verbose finished_sent = false handshake_messages = '' version = '' context = OpenSSL::SSL::SSLContext.new(:TLSv1) context.verify_mode = OpenSSL::SSL::VERIFY_NONE tcp_socket = TCPSocket.new(host, port) ssl_server = OpenSSL::SSL::SSLSocket.new(tcp_socket, context) ssl_server.connect puts 'Connected to %s:%d' % [ssl_server.peeraddr[2], ssl_server.peeraddr[1]] if debug || verbose server = TCPSocket.new(host, port) puts 'Connected to %s:%d' % [server.peeraddr[2], server.peeraddr[1]] if debug || verbose loop do readable, = IO.select([client, server]) readable.each do |r| if r == ssl_server # ssl_server is an SSL socket; read application data directly header = '' fragment = r.readpartial(4096) fragment.hexdump($stderr) if debug puts '%d bytes received' % [fragment.bytesize] if debug || verbose else header = r.read(5) raise EOFError if header.nil? header.hexdump($stderr) if debug puts '%d bytes received' % [header.bytesize] if debug || verbose fragment = r.read(header[3, 2].unpack('n')[0]) fragment.hexdump($stderr) if debug puts '%d bytes received' % [fragment.bytesize] if debug || verbose end if finished_sent if file # Save application data file.write(fragment) file.flush file.fsync end elsif fragment =~ /^\x0e\x00\x00\x00/ # server_hello_done # Drop the server hello done message and send the finished # message in plaintext. if header[2, 1] == "\x03" verify_data = prf_sha256('', 'server finished', OpenSSL::Digest::SHA256.digest(handshake_messages)) verify_data = verify_data[0, 12] else verify_data = prf('', 'server finished', OpenSSL::Digest::MD5.digest(handshake_messages) + OpenSSL::Digest::SHA1.digest(handshake_messages)) verify_data = verify_data[0, 12] end finished = "\x14#{[verify_data.length].pack('N')[1, 3]}#{verify_data}" record = header[0, 3] + [finished.length].pack('n') + finished count = client.write(record) client.flush record.hexdump($stderr) if debug puts '%d bytes sent' % [count] if debug || verbose finished_sent = true # Change to the SSL socket server.close server = ssl_server # Save version used in the handshake version = header[2, 1] next else # Save handshake messages handshake_messages << fragment end case r when client if finished_sent # server is an SSL socket count = server.write(fragment) server.flush fragment.hexdump($stderr) if debug puts '%d bytes sent' % [count] if debug || verbose else # server isn't an SSL socket record = header + fragment count = server.write(record) server.flush record.hexdump($stderr) if debug puts '%d bytes sent' % [count] if debug || verbose end when ssl_server # client isn't an SSL socket; add the record layer header with # the same version used in the handshake. header = "\x17\x03#{version}" + [fragment.length].pack('n') record = header + fragment count = client.write(record) client.flush record.hexdump($stderr) if debug puts '%d bytes sent' % [count] if debug || verbose when server record = header + fragment count = client.write(record) client.flush record.hexdump($stderr) if debug puts '%d bytes sent' % [count] if debug || verbose end end end client.close server.close end end proxy.close |