Fix/xor dynamic register preservation#21670
Merged
bwatters-r7 merged 2 commits intoJul 13, 2026
Merged
Conversation
bwatters-r7
approved these changes
Jul 13, 2026
Contributor
Release NotesMarks the dynamic XOR encoders as unable to preserve registers and adds regression coverage for stage encoding when a preserved register is required. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This PR marks the dynamic XOR encoders as unable to preserve registers and adds regression coverage for stage encoding when a preserved register is required.
The bug is that
x64/xor_dynamicandx86/xor_dynamicadvertised register preservation support even though their decoder stubs do not preserve the registers they use. On the upstreammasterbase commit,x64/xor_dynamicreturnstruefrom#can_preserve_registers?at modules/encoders/x64/xor_dynamic.rb#L22-L23, but the decoder stub immediately loads the encoded payload pointer intordiwithpop rdi, searches through memory usingscas ... [rdi], decodes bytes through[rdi], and incrementsrdiat modules/encoders/x64/xor_dynamic.rb#L30-L41. That means the encoder clobbersrdi.The same issue exists in
x86/xor_dynamic: it returnstruefrom#can_preserve_registers?at modules/encoders/x86/xor_dynamic.rb#L22-L23, then movesebxintoedi, searches through[edi], decodes through[edi], and incrementsediat modules/encoders/x86/xor_dynamic.rb#L29-L38.Stage encoding can be asked to preserve registers needed by the payload stage. In the failing Windows x64 bind Meterpreter case, the stage expects the socket handle in
rdi. Becausex64/xor_dynamicclaimed it could preserve registers, Framework could select it forEnableStageEncoding=true; the decoder then clobberedrdi, causing the staged bind session to fail after the stage was sent. Marking these encoders as non-preserving makes the encoder selection logic skip them when register preservation is required.Related Issue: N/A
Breaking Changes
None expected for normal encoder selection. If a user explicitly forces
x64/xor_dynamicorx86/xor_dynamicwhile also requiring preserved registers and disabling fallback, Framework can now reject that incompatible selection instead of emitting a stage that violates the register preservation contract.Reviewer Notes
The implementation change is intentionally small: the dynamic XOR encoders now return
falsefrom#can_preserve_registers?because their current stubs do not implementSaveRegisters.The regression spec avoids asserting that a specific fallback encoder must be selected. It constrains the available x64 encoders to
x64/xor_dynamicandx64/xor, requiresrdipreservation, and verifies thatx64/xor_dynamicis not selected while the payload still encodes successfully.Verification Steps
bundle exec rspec spec/lib/msf/core/encoded_payload/stage_encoding_spec.rb.bundle exec rspec spec/lib/msf/core/encoded_payload_spec.rb.bundle exec rubocop modules/encoders/x64/xor_dynamic.rb modules/encoders/x86/xor_dynamic.rb spec/lib/msf/core/encoded_payload/stage_encoding_spec.rb.ruby tools/dev/msftidy.rb modules/encoders/x64/xor_dynamic.rb modules/encoders/x86/xor_dynamic.rb.EnableStageEncoding=true; the stage encoder should not usex64/xor_dynamicwhenrdimust be preserved, and the bind session should remain valid after the stage is sent.Test Evidence
Old and broken on the left (1181d3f), new and fixed on the right (1e762bb).
Environment
AI Usage Disclosure
Codex was used to help investigate the stage encoding failure, draft the fix and regression spec, and draft this PR description.
Pre-Submission Checklist
documentation/modules(new modules only)lib/changes)