<commit_before>##
# session.rb
# Created March, 2013
# By <NAME>
#
# See: LICENSE.md
#
##

require 'controller/encryptor'
require 'controller/packet'
require 'drivers/driver_command'
require 'drivers/driver_console'
require 'drivers/driver_process'
require 'libs/commander'
require 'libs/dnscat_exception'
require 'libs/swindow'

class Session
  class SessionKiller < StandardError
  end

  @@isn = nil # nil = random

  attr_reader :id, :name, :options, :state
  attr_reader :window

  # The session hasn't seen a SYN packet yet (but may have done some encryption negotiation)
  STATE_NEW           = 0x00

  # After receiving a SYN
  STATE_ESTABLISHED   = 0x01

  # After being manually killed
  STATE_KILLED        = 0xFF

  HANDLERS = {
    Packet::MESSAGE_TYPE_SYN => :_handle_syn,
    Packet::MESSAGE_TYPE_MSG => :_handle_msg,
    Packet::MESSAGE_TYPE_FIN => :_handle_fin,
    Packet::MESSAGE_TYPE_ENC => :_handle_enc,
  }

  def initialize(id, main_window)
    @state = STATE_NEW
    @kill_reason = nil
    @their_seq = 0
    @my_seq    = @@isn.nil? ? rand(0xFFFF) : @@isn
    @options = 0

    @id = id
    @incoming_data = ''
    @outgoing_data = ''
    @driver = nil

    # Stuff that's displayed after the window's name
    @crypto_state = '[cleartext]'

    # Create this whether or not we're actually encrypting - it cleans up
    # the handler code
    @encryptor = Encryptor.new(Settings::GLOBAL.get('secret'))

    @settings = Settings.new()
    @window = SWindow.new(main_window, false, {:times_out => true})

    @settings.create("prompt", Settings::TYPE_NO_STRIP, "not set> ", "Change the prompt (if you want a space, use quotes; 'set prompt=\"a> \"'.") do |old_val, new_val|
      @window.prompt = new_val
    end

    @settings.create("name", Settings::TYPE_NO_STRIP, "(not set)", "Change the name of the window, and how it's displayed on the 'windows' list; this implicitly changes the prompt as well.") do |old_val, new_val|
      @window.name = new_val + ' ' + @crypto_state
      @settings.set("prompt", "%s %d> " % [new_val, @window.id])
    end

    @settings.create("history_size", Settings::TYPE_INTEGER, @window.history_size, "Change the number of lines to store in the window's history") do |old_val, new_val|
      @window.history_size = new_val
      @window.puts("history_size (session) => #{new_val}")
    end
  end

  def kill(reason = "No reason given")
    @window.with({:to_ancestors=>true, :to_descendants=>true}) do
      if(@state != STATE_KILLED)
        @state = STATE_KILLED
        @kill_reason = reason

        @window.with({:to_ancestors => true}) do
          @window.puts("Session #{@window.id} killed: #{reason}")
        end
      else
        @window.puts("Session #{@window.id} killed (again): #{reason}")
      end
    end

    @window.close()
    if(@driver)
      @driver.shutdown()
    end
  end

  def _next_outgoing(n)
    ret = @outgoing_data[0,n-1]
    return ret
  end

  def _ack_outgoing(n)
    # "n" is the current ACK value
    bytes_acked = (n - @my_seq)

    # Handle wraparounds properly
    if(bytes_acked < 0)
      bytes_acked += 0x10000
    end

    @outgoing_data = @outgoing_data[bytes_acked..-1]
    @my_seq = n
  end

  def _valid_ack?(ack)
    bytes_acked = (ack - @my_seq) & 0xFFFF
    return bytes_acked <= @outgoing_data.length
  end

  def queue_outgoing(data)
    @outgoing_data = @outgoing_data + data.force_encoding("ASCII-8BIT")
  end

  def to_s()
    return "id: 0x%04x [internal: %d], state: %d, their_seq: 0x%04x, my_seq: 0x%04x, incoming_data: %d bytes [%s], outgoing data: %d bytes [%s]" % [@id, @window.id, @state, @their_seq, @my_seq, @incoming_data.length, @incoming_data, @outgoing_data.length, @outgoing_data]
  end

  def _do_display_crypto_values()
    @window.with({:to_ancestors => true}) do
      if(@encryptor.authenticated?())
        @window.puts("Session #{@window.id} Security: ENCRYPTED AND VERIFIED!")
        @window.puts("(the security depends on the strength of your pre-shared secret!)")
      elsif(@encryptor.ready?())
        @window.puts("Session #{@window.id} security: ENCRYPTED BUT *NOT* VALIDATED")
        @window.puts("For added security, please ensure the client displays the same string:")
        @window.puts()
        @window.puts(">> #{@encryptor.get_sas()}")
      else
        @window.puts("Session #{@window.id} security: UNENCRYPTED")
      end
    end
  end

  def _handle_syn(packet, max_length)
    options = 0

    # Ignore errant SYNs - they are, at worst, retransmissions that we don't care about
    if(@state != STATE_NEW)
      raise(DnscatException, "Duplicate SYN received!")
    end

    _do_display_crypto_values()

    # Save some of their options
    @their_seq = packet.body.seq
    @options   = packet.body.options

    # TODO: We're going to need different driver types
    if((@options & Packet::OPT_COMMAND) == Packet::OPT_COMMAND)
      @driver = DriverCommand.new(@window, @settings)
    else
      process = @settings.get("process")
      if(process.nil?)
        @driver = DriverConsole.new(@window, @settings)
      else
        @driver = DriverProcess.new(@window, @settings, process)<commit_msg>Handle duplicate SYN situation in a more robust way<commit_after>##
# session.rb
# Created March, 2013
# By <NAME>
#
# See: LICENSE.md
#
##

require 'controller/encryptor'
require 'controller/packet'
require 'drivers/driver_command'
require 'drivers/driver_console'
require 'drivers/driver_process'
require 'libs/commander'
require 'libs/dnscat_exception'
require 'libs/swindow'

class Session
  class SessionKiller < StandardError
  end

  @@isn = nil # nil = random

  attr_reader :id, :name, :options, :state
  attr_reader :window

  # The session hasn't seen a SYN packet yet (but may have done some encryption negotiation)
  STATE_NEW           = 0x00

  # After receiving a SYN
  STATE_ESTABLISHED   = 0x01

  # After being manually killed
  STATE_KILLED        = 0xFF

  HANDLERS = {
    Packet::MESSAGE_TYPE_SYN => :_handle_syn,
    Packet::MESSAGE_TYPE_MSG => :_handle_msg,
    Packet::MESSAGE_TYPE_FIN => :_handle_fin,
    Packet::MESSAGE_TYPE_ENC => :_handle_enc,
  }

  def initialize(id, main_window)
    @state = STATE_NEW
    @kill_reason = nil
    @their_seq = 0
    @my_seq    = @@isn.nil? ? rand(0xFFFF) : @@isn
    @options = 0

    @id = id
    @incoming_data = ''
    @outgoing_data = ''
    @driver = nil

    # Stuff that's displayed after the window's name
    @crypto_state = '[cleartext]'

    # Create this whether or not we're actually encrypting - it cleans up
    # the handler code
    @encryptor = Encryptor.new(Settings::GLOBAL.get('secret'))

    @settings = Settings.new()
    @window = SWindow.new(main_window, false, {:times_out => true})

    @settings.create("prompt", Settings::TYPE_NO_STRIP, "not set> ", "Change the prompt (if you want a space, use quotes; 'set prompt=\"a> \"'.") do |old_val, new_val|
      @window.prompt = new_val
    end

    @settings.create("name", Settings::TYPE_NO_STRIP, "(not set)", "Change the name of the window, and how it's displayed on the 'windows' list; this implicitly changes the prompt as well.") do |old_val, new_val|
      @window.name = new_val + ' ' + @crypto_state
      @settings.set("prompt", "%s %d> " % [new_val, @window.id])
    end

    @settings.create("history_size", Settings::TYPE_INTEGER, @window.history_size, "Change the number of lines to store in the window's history") do |old_val, new_val|
      @window.history_size = new_val
      @window.puts("history_size (session) => #{new_val}")
    end
  end

  def kill(reason = "No reason given")
    @window.with({:to_ancestors=>true, :to_descendants=>true}) do
      if(@state != STATE_KILLED)
        @state = STATE_KILLED
        @kill_reason = reason

        @window.with({:to_ancestors => true}) do
          @window.puts("Session #{@window.id} killed: #{reason}")
        end
      else
        @window.puts("Session #{@window.id} killed (again): #{reason}")
      end
    end

    @window.close()
    if(@driver)
      @driver.shutdown()
    end
  end

  def _next_outgoing(n)
    ret = @outgoing_data[0,n-1]
    return ret
  end

  def _ack_outgoing(n)
    # "n" is the current ACK value
    bytes_acked = (n - @my_seq)

    # Handle wraparounds properly
    if(bytes_acked < 0)
      bytes_acked += 0x10000
    end

    @outgoing_data = @outgoing_data[bytes_acked..-1]
    @my_seq = n
  end

  def _valid_ack?(ack)
    bytes_acked = (ack - @my_seq) & 0xFFFF
    return bytes_acked <= @outgoing_data.length
  end

  def queue_outgoing(data)
    @outgoing_data = @outgoing_data + data.force_encoding("ASCII-8BIT")
  end

  def to_s()
    return "id: 0x%04x [internal: %d], state: %d, their_seq: 0x%04x, my_seq: 0x%04x, incoming_data: %d bytes [%s], outgoing data: %d bytes [%s]" % [@id, @window.id, @state, @their_seq, @my_seq, @incoming_data.length, @incoming_data, @outgoing_data.length, @outgoing_data]
  end

  def _do_display_crypto_values()
    @window.with({:to_ancestors => true}) do
      if(@encryptor.authenticated?())
        @window.puts("Session #{@window.id} Security: ENCRYPTED AND VERIFIED!")
        @window.puts("(the security depends on the strength of your pre-shared secret!)")
      elsif(@encryptor.ready?())
        @window.puts("Session #{@window.id} security: ENCRYPTED BUT *NOT* VALIDATED")
        @window.puts("For added security, please ensure the client displays the same string:")
        @window.puts()
        @window.puts(">> #{@encryptor.get_sas()}")
      else
        @window.puts("Session #{@window.id} security: UNENCRYPTED")
      end
    end
  end

  def _handle_syn(packet, max_length)
    options = 0

    # Ignore errant SYNs - they are, at worst, retransmissions that we don't care about
    #if(@state != STATE_NEW)
    #  raise(DnscatException, "Duplicate SYN received!")
    #end

    _do_display_crypto_values()

    # Save some of their options
    @their_seq = packet.body.seq
    @options   = packet.body.options

    # TODO: We're going to need different driver types
    if((@options & Packet::OPT_COMMAND) == Packet::OPT_COMMAND)
      @driver = DriverCommand.new(@window, @settings)
    else
      process = @settings.get("process")
      if(process.nil?)
        @driver = DriverConsole.new(@window, @settings)
      else
        @driver = DriverProcess.new(@window, @settings, process)
{'fraction_non_alphanumeric': 0.12892322549493, 'fraction_numerical': 0.006953162723322067, 'mean_word_length': 3.331242158092848, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2985725', 'n_tokens_mistral': 3658, 'n_tokens_neox': 3450, 'n_words': 893}