Add -CSVersion so profiles load on Cobalt Strike 4.13 (second half of #32) - #33
Open
warpedatom wants to merge 5 commits into
Open
Add -CSVersion so profiles load on Cobalt Strike 4.13 (second half of #32)#33warpedatom wants to merge 5 commits into
warpedatom wants to merge 5 commits into
Conversation
-Injector defaulted to an empty string with no empty case in GenerateProcessInject, so it was silently mandatory and no profile could be generated without it. Random table pickers used GenerateNumer(0, len-1), which is exclusive of its upper bound, making the last entry of every lookup table unreachable (4 of 8 SSH banners). GenerateURIValues dropped rejected URIs instead of retrying, so -Uri N returned fewer than N. Re-seeding math/rand from time.Now() on every call is deprecated and returns identical values within a clock tick. CDN_Value was never read from the YAML config, the config overlay clobbered flag defaults, and out-of-range numeric flags panicked instead of erroring. Adds unit tests and a build workflow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Slack profile hardcoded its http-stager URIs as /messages/DALBNSf25 and /messages/DALBNSF25, so every profile generated from that template shared the same two paths. GoToMeeting derived both from UValue, making the x86 and x64 stager URIs identical and tying the staging request to the beacon check-ins, which reuse UValue in their prepends and cookie. Both now use independent per-architecture stager tokens of varied length. Outlook.Live already randomized correctly and is unchanged. Separately, -Sleep, -Jitter, -Datajitter, -Allocation and the three -Tasks*MaxSize flags were written into the profile unvalidated, so bad values only surfaced when the teamserver refused to load it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
c2lint on Cobalt Strike 4.13 rejects every profile SourcePoint generates. Two of the causes are unambiguous. smartinject is a post-ex option, but the stage block set it too, which c2lint reports as 'invalid option for <.stage>'; the post-ex copy was hardcoded to true, so -SmartInject drove the invalid one and never affected the profile it was meant to. sleep_mask was emitted without quotes around its value, unlike every other boolean in the block, which c2lint reports as 'Unknown statement in <.stage>'. Both are fixed and covered by tests against the template text. Two further causes, stage.rdll_loader and stage.name, are version compatibility questions rather than bugs and are reported separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cobalt Strike 4.13 removed the stage.rdll_loader and stage.name malleable C2 options, so every profile SourcePoint generated was rejected by c2lint with 'invalid option for <.stage>'. Rather than dropping the options outright, which would degrade older team servers, -CSVersion selects the target release: 4.13 and newer omit them, 4.12 and older emit them as before. The flag defaults to 4.13. -RdllLoader is still validated regardless of target so a typo remains an error, and the omission is reported on stdout rather than being silent. The spoofed module name is now recovered with a regex before stripping instead of by splitting the clone block on ';' and indexing len-3, which broke once the block lost a directive. Verified with c2lint on a licensed 4.13 team server: a 4.13 profile compiles, a 4.12 profile still fails with the two stage errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cobalt Strike requires stage.image_size_x86 and image_size_x64 to be at least the size of the Beacon DLL it stomps into the image, and rejects the profile otherwise with 'must be larger than N bytes'. The values in Peclone_list are the real sizes of the modules being mimicked and the Beacon has outgrown most of them, so on 4.13 only four of the thirty clone entries produce a loadable profile: the four that happen to carry no image_size directives at all. Raising them to a fixed floor would age out again as the Beacon grows each release, so they are dropped for 4.13 and newer and Cobalt Strike sizes the image itself. Verified by generating a profile for every clone entry and confirming none still carries image_size or name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
This is the second half of #32, and it builds on #30, so it wants merging
after that one. #30 fixes two of the four reasons
c2lintrejects everygenerated profile on Cobalt Strike 4.13; this fixes the other two. Because the
branch is cut from #30, the diff here includes those commits until #30 lands.
The problem
Cobalt Strike 4.13 no longer accepts two of the
stageoptions SourcePointemits, so no generated profile can be loaded on the current release:
I isolated every stage option SourcePoint writes, one per minimal profile, to
confirm exactly which ones 4.13 rejects:
set rdll_loader "PrependLoader"set rdll_loader "StompLoader"set name "test.dll"set checksum "123"set rich_header "\x00"set copy_pe_header "false"set eaf_bypass "false"set rdll_use_syscalls "false"set syscall_method "None"rdll_loaderis rejected on both values, so this is not the stomp loaderdeprecation from an earlier release. And
checksumandrich_headerstillwork while
namedoes not, so PE cloning survives in 4.13, it just no longertakes the cloned module's name.
The approach
I did not want to simply delete the two options, since that would quietly
degrade anyone still on an older team server:
nameappears in all 30Peclone_listentries, andrdll_loaderbacks the documented-RdllLoaderflag.
So
-CSVersionselects the target release.4.13and newer omit both options;4.12and older emit them exactly as before. It defaults to4.13, on thegrounds that a tool should work against the current release out of the box, but
that default is a one-word change in
Loader/CSVersion.goif you would rathernot alter behaviour for existing users.
The omission is reported rather than silent, so nobody wonders where their
-RdllLoadersetting went.-RdllLoaderis still validated regardless oftarget, so a typo remains an error instead of being discarded along with the
directive.
A third incompatibility, same shape
With
rdll_loaderandnamehandled,c2lintgets further and rejects the PEclone image sizes:
Cobalt Strike requires each to be at least the size of the Beacon DLL it stomps
into the image. The values in
Peclone_listare the real sizes of the modulesbeing mimicked, and the Beacon has outgrown most of them, so on 4.13 only four
of the thirty clone entries produce a loadable profile: the four that happen to
carry no
image_sizedirectives at all.Raising them to a fixed floor would age out again with the next Beacon growth,
so
4.13and newer omit them and let Cobalt Strike size the image. The fourentries that already ship without them are the evidence that works.
One fix that falls out of this
The
set namedirective lives inside thePeclone_listentries, and the"Beacon DLL Spoofed To:" summary line recovered the module name by splitting
the clone block on
;and indexinglen-3. That arithmetic breaks as soon asthe block loses a directive, so the name is now read with a regex before the
directive is stripped. Four of the thirty entries also turn out to carry no
image_sizedirectives, which the old index maths happened to survive and thenew code does not care about either way.
Verification
Against a licensed Cobalt Strike 4.13 team server:
just past
c2lint:[+] I see you're into threat replication. loaded.and[+] Team server is up. The only remainingc2lintwarning is the absentcode-signing keystore.
-PE_Clone 4, one of the twenty-six entries that fails withoutthe
image_sizechange, and with-DNSand-Stage trueboth onimage_sizeornamedirective anywhere-CSVersion 4.12still fails on 4.13 with thestageerrors, confirming the gate actually gates-CSVersion nonsenseis rejected up frontTests cover version parsing and comparison, reading the module name out of
every
Peclone_listentry, stripping removing the name directive and nothingelse, and
set pipename/set ssh_pipenamenot being mistaken forset name.