Update Windows service PE template for stageless payloads#21602
Conversation
Now the baseic appender is the child class and functionality that uses the segment is named after what it does with the appended segment.
2e13cf5 to
eac38bf
Compare
There was a problem hiding this comment.
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_serviceflow that appends a dedicated payload section (readable data) and patches the template’s.payloadtag (and optional service name). - Refactor/clarify PE section helpers:
SegmentAppenderbecomes a base appender; introduceSegmentHijackerfor entrypoint redirection;SegmentInjectornow 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
psexecnative upload payload space.
Impact Analysis:
- Blast radius: medium–high; affects Windows EXE/service payload generation (
Msf::Util::EXE), thewindows/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.payloadsection-name marker; also introduces/uses:section_name(but legacy:secnamemust 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/:secnamecompatibility, (3)psexecnative 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. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| [ '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 |
There was a problem hiding this comment.
is this a new constraint or was it missing form before?
There was a problem hiding this comment.
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.
Release NotesUpdates the Windows service PE template to use an injected segment instead of the old substitution method. |
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.payloadsection 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
.\build_all.ps1 -Templates exe_serviceTest Evidence
Build Output
PSexec Stageless Output
Environment
.ruby-versionAI Usage Disclosure
Used GPT-5.5 for some assistance
Pre-Submission Checklist
rubocopon new files with no new offenses (net new files only)msftidyon changed module files with no new offenses (modules only)msftidy_docson changed documentation files with no new offenses (documentation files only)documentation/modules(new modules only)lib/changes)