Module: Msf::Exploit::Remote::TincdExploitClient

Includes:
Tcp
Defined in:
lib/msf/core/exploit/tincd.rb

Overview

This module does a handshake with a tincd server and sends one padded packet Author: Tobias Ospelt @floyd_ch

Constant Summary collapse

BF_BLOCKSIZE =
64 / 8
BF_KEY_LEN =
16
BF_IV_LEN =
8

Instance Attribute Summary

Attributes included from Tcp

#sock

Instance Method Summary collapse

Methods included from Tcp

#chost, #cleanup, #connect, #connect_timeout, #cport, #deregister_tcp_options, #disconnect, #handler, #lhost, #lport, #peer, #print_prefix, #proxies, #rhost, #rport, #set_tcp_evasions, #shutdown, #ssl, #ssl_cipher, #ssl_verify_mode, #ssl_version

Instance Method Details

#ackObject

Ack state to signalize challenge/response was successful


320
321
322
323
324
325
# File 'lib/msf/core/exploit/tincd.rb', line 320

def ack
  vprint_status('Sending ack (signalise server that we accept challenge' +
   'reply, ciphertext)')
  @encryption_queue.push("4 #{datastore['RPORT']} 123 0    \n")
  handle_write
end

#challengeObject

Send challenge random bytes


298
299
300
301
302
303
304
# File 'lib/msf/core/exploit/tincd.rb', line 298

def challenge
  vprint_status('Sending challenge (ciphertext)')
  challenge = SecureRandom.random_bytes(@server_key_len)
  msg = "2      #{challenge.unpack('H*')[0]}\n"
  @encryption_queue.push(msg)
  handle_write
end

#challenge_reply(challenge2) ⇒ Object

Reply to challenge that was sent by server


309
310
311
312
313
314
315
# File 'lib/msf/core/exploit/tincd.rb', line 309

def challenge_reply(challenge2)
  vprint_status('Sending challenge reply (ciphertext)')
  h = Digest::SHA1.hexdigest(challenge2)
  msg = "3      #{h.upcase}\n"
  @encryption_queue.push(msg)
  handle_write
end

#handle_writeObject

Encryption queue where waiting data gets encrypted and afterwards the remaining messages get sent


217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/msf/core/exploit/tincd.rb', line 217

def handle_write
  # handle encryption queue first
  unless @encryption_queue.empty?
    msg = @encryption_queue[0]
    @encryption_queue.delete_at(0)
    @buffer = @bf_enc_cipher.update(msg)
    @buffer << @bf_enc_cipher.final
    # DON'T DO A @bf_enc_cipher.reset, we're in OFB mode and
    # the resulting block is used to encrypt the next block.
  end

  unless @buffer.empty?
    sent = send_data(@buffer)
    vprint_status("Sent #{sent} bytes: " +
     "[#{@buffer.unpack('H*')[0][0..30]}...]")
    @buffer = @buffer[sent..@buffer.length]
  end
end

#idObject

Start message method after TCP handshake


277
278
279
280
281
282
# File 'lib/msf/core/exploit/tincd.rb', line 277

def id
  msg = "0 #{datastore['CLIENT_NAME']} 17.0\n"
  vprint_status("Sending ID (cleartext): [#{msg.gsub("\n", '')}]")
  @buffer += msg
  handle_write
end

#init_ciphers(server_file, client_file) ⇒ Object

Reading of certificate files and parsing them, generation of random keys and initialization of OFB mode blowfish cipher


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
# File 'lib/msf/core/exploit/tincd.rb', line 100

def init_ciphers(server_file, client_file)
  server_public_key_cipher = OpenSSL::PKey::RSA.new(File.read(server_file))
  @server_key_len = server_public_key_cipher.n.num_bytes
  @client_private_key_cipher = OpenSSL::PKey::RSA.new(File.read(client_file))
  @client_key_len = @client_private_key_cipher.n.num_bytes
  vprint_status("Our private key length is #{@client_key_len}, expecting same length for metakey and challenge")
  vprint_status("Server's public key length is #{@server_key_len}, sending same metakey and challenge length")

  # we don't want this to happen here:
  # `public_encrypt': data too large for modulus (OpenSSL::PKey::RSAError)
  # simple solution: choose the key_s1 with a leading zero byte
  key_s1 = "\x00"+SecureRandom.random_bytes(@server_key_len-1)
  enc_key_s1 = server_public_key_cipher.public_encrypt(key_s1, OpenSSL::PKey::RSA::NO_PADDING)

  @hex_enc_key_s1 = enc_key_s1.unpack('H*')[0]

  offset_key = @server_key_len - BF_KEY_LEN
  offset_iv = @server_key_len - BF_KEY_LEN - BF_IV_LEN
  bf_enc_key = key_s1[offset_key...@server_key_len]
  bf_enc_iv = key_s1[offset_iv...offset_key]

  @bf_enc_cipher = OpenSSL::Cipher.new('BF-OFB')
  @bf_enc_cipher.encrypt
  @bf_enc_cipher.key = bf_enc_key
  @bf_enc_cipher.iv = bf_enc_iv

  # #Looks like ruby openssl supports other lengths than multiple of 8!
  # test = @bf_enc_cipher.update('A'*10)
  # test << @bf_enc_cipher.final
  # puts "Testing cipher: "+test.unpack('H*')[0]
end

#initialize(info = {}) ⇒ Object

Module options


21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/msf/core/exploit/tincd.rb', line 21

def initialize(info = {})
  super
  register_options(
    [Opt::RPORT(655),
      # As this is only for post-auth exploits, you should know the value of the
      # following variables by simply checking
      # your configuration.
      OptPath.new('SERVER_PUBLIC_KEY_FILE', [true, 'Server\'s public key', '']),
      OptPath.new('CLIENT_PRIVATE_KEY_FILE', [true, 'Client private key', '']),
      # You should see CLIENT_NAME in cleartext in the first message to the
      # server by your usual tinc client (tcpdump or
      # wireshark it: e.g. "0 home 17.0", so it's "home"). On the server,
      # this is located in the config folder, e.g. in FreeBSD
      # there is the client public key file /usr/local/etc/tinc/hosts/home
      # for the client "home"
      # If you don't have a clue, maybe just try the filename of your private
      # key without file extension
      OptString.new('CLIENT_NAME', [true, 'Your client name (pre-shared with server)' , ''])
    ], self
  )
end

#line?Boolean

Check if we already received a newline, meaning we got an entire message for the next protocol step

Returns:

  • (Boolean)

270
271
272
# File 'lib/msf/core/exploit/tincd.rb', line 270

def line?
  !!(@inbuffer.match("\n"))
end

#metakeyObject

Sending metakey (transferring a symmetric key that will get encrypted with public key before beeing sent to the server)


288
289
290
291
292
293
# File 'lib/msf/core/exploit/tincd.rb', line 288

def metakey
  msg = "1 94 64 0 0 #{@hex_enc_key_s1}\n"
  vprint_status("Sending metakey (cleartext): [#{msg[0..30]}...]")
  @buffer += msg
  handle_write
end

#pop_inbuffer_and_decrypt(size) ⇒ Object

Decryption method to process data sent by server


246
247
248
249
250
251
252
253
254
# File 'lib/msf/core/exploit/tincd.rb', line 246

def pop_inbuffer_and_decrypt(size)
  # In ruby openssl OFM works not only on full blocks, but also on
  # parts. Therefore no worries like in pycrypto and no
  # modified decrypt routine, simply use the cipher as is.
  data = @bf_dec_cipher.update(@inbuffer.slice!(0, size))
  data << @bf_dec_cipher.final
  # DON'T DO A @bf_enc_cipher.reset, we're in OFB mode and
  # the resulting block is used to decrypt the next block.
end

#process_data(data) ⇒ Object

Depending on the state of the protocol handshake and the data we get back from the server, this method will decide which message has to be sent next


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
# File 'lib/msf/core/exploit/tincd.rb', line 136

def process_data(data)
  @inbuffer += data if data
  case @state
  when :id_state
    if line?
      data = read_line
      vprint_status("Received ID from server: [#{data[0..30]}]")
      @state = :metakey_state
      # next expected state
      metakey
    end
  when :metakey_state
    if line?
      data = read_line
      vprint_status("Received Metakey from server: [#{data[0..30]}...]")
      data = data.split(' ')
      fail 'Error in protocol. The first byte should be an ASCII 1.' unless data.first == '1'
      hexkey_s2 = data[5].rstrip # ("\n")
      fail "Error in protocol. metakey length should be #{@client_key_len}." unless hexkey_s2.length == @client_key_len * 2
      @enckey_s2 = [hexkey_s2].pack('H*')
      key_s2 = @client_private_key_cipher.private_decrypt(@enckey_s2, OpenSSL::PKey::RSA::NO_PADDING)

      # metakey setup according to protocol_auth.c
      # if(!EVP_DecryptInit(c->inctx, c->incipher,
      #    (unsigned char *)c->inkey + len - c->incipher->key_len,    # <--- KEY pointer
      #    (unsigned char *)c->inkey + len - c->incipher->key_len - c->incipher->iv_len # <--- IV pointer
      # ))
      offset_key = @client_key_len - BF_KEY_LEN
      offset_iv = @client_key_len - BF_KEY_LEN - BF_IV_LEN
      bf_dec_key = key_s2[offset_key...@client_key_len]
      bf_dec_iv = key_s2[offset_iv...offset_key]

      @bf_dec_cipher = OpenSSL::Cipher.new 'BF-OFB'
      @bf_dec_cipher.encrypt
      @bf_dec_cipher.key = bf_dec_key
      @bf_dec_cipher.iv = bf_dec_iv
      # don't forget, it *does* matter if you do a
      # @bf_dec_cipher.reset or not, we're in OFB mode. DON'T.
      vprint_status('Metakey handshake/exchange completed')
      @state = :challenge_state
      challenge
    end
  when :challenge_state
    need_len = 2 * @client_key_len + 3
    if @inbuffer.length >= need_len
      data = pop_inbuffer_and_decrypt(need_len)
      vprint_status("Received challenge from server: " +
       "[#{data.unpack('H*')[0][0..30]}...]")
      data = data.split(' ', 2)
      fail 'Error in protocol. The first byte should be an ASCII 2. Got #{data[0]}.' unless data.first == '2'
      challenge2 = data[1][0...2 * @client_key_len]
      challenge2 = [challenge2].pack('H*')
      fail "Error in protocol. challenge2 length should be #{@client_key_len}." unless challenge2.length == @client_key_len
      @state = :challenge_reply_state
      challenge_reply(challenge2)
    end
  when :challenge_reply_state
    need_len = 43
    if @inbuffer.length >= need_len
      data = pop_inbuffer_and_decrypt(need_len)
      vprint_status("Received challenge reply from server:" +
       " [#{data.unpack('H*')[0][0..30]}...]")
      @state = :ack_state
      ack
    end
  when :ack_state
    need_len = 12
    if @inbuffer.length >= need_len
      data = pop_inbuffer_and_decrypt(need_len)
      vprint_status("Received ack (server accepted challenge response):" +
       "[#{data.unpack('H*')[0][0..30]}...]")
      @state = :done_state
      send_packet
    end
  end
end

#read_lineObject

Read up to the next newline from the data the server sent


259
260
261
262
263
264
# File 'lib/msf/core/exploit/tincd.rb', line 259

def read_line
  idx = @inbuffer.index("\n")
  data = @inbuffer.slice!(0, idx)
  @inbuffer.lstrip!
  data
end

#send_data(buf) ⇒ Object

Simple socket put/write


239
240
241
# File 'lib/msf/core/exploit/tincd.rb', line 239

def send_data(buf)
  sock.put(buf)
end

#send_packetObject

Sending a packet inside the VPN connection after successful protocol setup


330
331
332
333
334
335
336
337
338
339
# File 'lib/msf/core/exploit/tincd.rb', line 330

def send_packet
  vprint_status('Protocol finished setup. Going to send packet.')
  msg = "17 #{@packet_payload.length}\n#{@packet_payload}"
  plen = BF_BLOCKSIZE - (msg.length % BF_BLOCKSIZE)
  # padding
  msg += 'B' * plen
  @encryption_queue.push(msg)
  @keep_reading_socket = false
  handle_write
end

#send_recv(packet_payload) ⇒ Object

The main method that will be called that will call other methods to send first message and continuously read from socket and ensures TCP disconnect at the end


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
# File 'lib/msf/core/exploit/tincd.rb', line 69

def send_recv(packet_payload)
  @packet_payload = packet_payload
  @keep_reading_socket = true
  connect
  begin
    # send the first message
    id
    # Condition to get out of the while loop: ack_state to false. Unsafe? Maybe a timeout?
    while @keep_reading_socket
      process_data(sock.get_once)
    end
  rescue Errno::ECONNRESET
    if @state == :metakey_state
      fail 'Server reset the connection. Probably rejecting ' +
       'the private key and/or client name (e.g. client name not associated ' +
       'with client public key on server side). ' +
       'Wrong server public key possible too. ' +
       'Please recheck client name, client private key and ' +
       'server public key.'
    else
      fail 'Server reset the connection, reason unknown.'
    end
  ensure
    disconnect
  end
end

#setup_ciphersObject

Setting up variables and calling cipher inits with file paths from configuration


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/msf/core/exploit/tincd.rb', line 46

def setup_ciphers
  @state = :id_state
  @buffer = ''
  @inbuffer = ''
  @encryption_queue = []

  @packet_payload = nil
  @keep_reading_socket = false

  @server_key_len = nil
  @client_key_len =  nil
  @client_private_key_cipher = nil
  @hex_enc_key_s1 = nil
  @bf_enc_cipher = nil
  init_ciphers(datastore['SERVER_PUBLIC_KEY_FILE'], datastore['CLIENT_PRIVATE_KEY_FILE'])
  vprint_status('Ciphers locally initalized, private key and public key files seem to be ok')
  @bf_dec_cipher = nil
end