Skip to content

Feat/extender support - #12

Merged
TGJLS merged 26 commits into
mainfrom
feat/extender-support
Aug 3, 2026
Merged

Feat/extender support#12
TGJLS merged 26 commits into
mainfrom
feat/extender-support

Conversation

@TGJLS

@TGJLS TGJLS commented Aug 3, 2026

Copy link
Copy Markdown
Owner

No description provided.

TGJLS and others added 26 commits July 10, 2026 13:57
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ofiles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dockerfile: install git so clone_repo() doesn't FileNotFoundError
- docker-compose.yml, docker-compose.kvm.yml: add container_name:adaptixc2
  so bare docker exec/restart commands resolve correctly
- test.yaml: wait for adaptixc2:4321 before running integration tests;
  always print adaptixc2 logs for debugging
- runner.py: retry adaptixc2 connection for up to 60s instead of failing
  immediately on ConnectionError
- cli/main.go: post-restart wait now checks adaptixc2's port (ss -tln)
  rather than the always-up testing-kit health endpoint
- extender_parser.py: add _widget() factory with all known no-op methods
  so axs scripts that call addWidget/setLayout/setPanel etc. don't throw;
  add missing form.create_{check,label,selector_file,gridlayout,hlayout,panel}
  and ax.interfaces(); add _es5_compat() to rewrite let/const→var and
  for...of→.forEach before passing to Duktape (ES5 only)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- profile.yaml: add full Teamserver config (port, certs, built-in extenders, HttpServer) so adaptixc2 no longer crashes on restart
- docker-compose: mount user extenders to /app/userextenders to avoid shadowing built-in extenders at /app/extenders
- profile_manager: switch os.replace to shutil.move to handle cross-device rename (EXDEV on bind mount), update default path to /app/userextenders
- api.py: fix _extender_name_from_url to use removesuffix(".git") instead of rstrip(".git") which was stripping individual chars and truncating "Extension-Kit" to "extension-k"
- extender_parser: add bool-default-False to classify_field so ssl/checkbox fields without defaults become auto=False instead of required; add missing mock JS stubs for Kharon agent axs (add_session_agent, add_session_browser, on_filebrowser_list, etc.)
- runner.py: guard _resolve_agent_from_extender with agent_schema null check (Kharon has no GenerateUI); add string→bool/int coercion in _resolve_schema_value for CLI --override values
- test.yaml: move all --flag args before the URL (Go flag.FlagSet stops at first non-flag); add all required Kharon listener overrides (port_bind, block_user_agents, domain_rotation_strategy, proxy_*, ssl_cert, ssl_key, uploaded_file)
- install scripts: update extender paths from /app/extenders/* to /app/userextenders/*

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- install-kharon.sh: use listener_kharon_http/Makefile and
  agent_kharon/Makefile instead of trying go build . in the wrong
  directory (no Go files at listener_kharon_http root, they live in
  src_server/); install make if not present
- test.yaml: add --no-restart for Extension-Kit add-extender because
  loading 11+ BOF axscripts causes adaptixc2 startup to exceed the
  60s health-check timeout; the CI tasks (shell whoami etc.) don't
  require axscripts to be loaded

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
adaptixc2 restart health check was 60s — loading Kharon Go plugins takes
longer; raised to 300s (150×2s). Both extender CI jobs now wait up to 50
minutes for Windows SSH before running tests, matching the Windows 11 QEMU
boot time; timeout-minutes raised to 90 to accommodate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Go plugin ABI requires exact package version match. The Kharon go.mod
pinned axc2 v1.1.3 but the running adaptixc2 binary uses v1.2.0, causing
plugin.Open to fail at startup. The install script now reads the axc2
version from the adaptixc2 binary via `go version -m` and runs `go get`
to align the dependency before building. Also adds adaptixc2 log capture
on failure for the Kharon test job.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The go version -m detection hit a pipefail edge case (binary is named
adaptixserver not adaptixc2) that exited the install script immediately.
Hardcode v1.2.0 which we confirmed from AdaptixC2's go.mod.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- install-kharon.sh: full build pipeline (GOEXPERIMENT detection, pl_agent.go
  patch for mask_sleep=none, go.work, src_beacon prebuild, src_core BOFs,
  win32.h stubs, symlinks, cstdint shim)
- kharon-tasks.yaml: use real Kharon commands (token getuid, process create,
  fs ls) instead of generic shell commands
- kharon-malleable-profile.json: malleable HTTP profile for listener
- test.yaml: add --override agent.mask_sleep=none to ensure no sleep obfuscation
- extender_parser.py: fix combo widget mock to track addItem/setCurrentIndex so
  defaults (Format=Exe, mask_sleep=none) are auto-detected from ax_config.axs;
  add mask_sleep to _SPECIAL registry as CI-safe override
- cli/main.go: use bash TCP probe instead of ss for adaptixc2 readiness check
- config/config.yaml: fix SFTP agent_path to POSIX form for OpenSSH on Windows
- run-kharon-test.sh: local helper script mirroring the CI test-kharon job

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add guardrails_user, guardrails_domain, killdate_date, workingtime_start,
workingtime_end to the _SPECIAL registry with empty-string values so the
extender parser marks them as source:auto instead of source:required.
These are optional evasive features: empty string disables them in Kharon.
Also add matching --override flags in the test-kharon CI step so they are
explicitly cleared even if the schema detection changes.

Without this, add-extender exits 1 ("missing required fields") and the CI
job never reaches the Windows test run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…aron.sh

python3 is not present in the adaptixc2 base image; add it to the initial
apt-get install block alongside make so the pl_agent.go and win32.h patch
steps don't fail with 'command not found'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d 1.25

The Kharon module's go.mod requires go >= 1.25.4, but the go.work header
was hardcoded to 'go 1.25', causing 'go build -buildmode=plugin' to fail
with 'module requires go >= 1.25.4, but go.work lists go 1.25'.

Extract the version from the adaptixserver binary (e.g. go1.25.4) and strip
the 'go' prefix for the go.work directive so they always match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mismatch

The listener .so was being built without the combined go.work, causing
adaptixserver to reject it with:
  plugin was built with a different version of package axc2

Move AdaptixC2 clone and go.work creation before both plugin builds so that
both listener_kharon_http and agent_kharon use identical package resolution.
Add GOWORK to the listener make call just as it was already used for the agent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…work

- Print full go version -m /app/adaptixserver so CI logs reveal the
  exact axc2 version and any replace directives in the server binary
- Extract axc2 version from binary (BINARY_AXC2) and use it as
  AXC2_VERSION instead of hardcoding v1.2.0
- Strip AdaptixC2 modules from go.work — including them caused MVS to
  bump axc2 to the AdaptixC2 HEAD version, which mismatches the docker
  image (built from an older commit); now go.work contains only the
  Kharon listener + agent modules so axc2 resolves to BINARY_AXC2
- Add diagnostic: print all go.mod files in the AdaptixC2 clone
  (including their axc2 require lines) for future debugging
- Print the listener Makefile so we can verify GOEXPERIMENT is preserved
- Print axc2 version from both built plugins and the server binary for
  direct comparison after each build

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ibility

Go plugin ABI compatibility requires every shared package to have the
same build ID. The build ID is: hash(source + dep_ids + go_compiler_hash).
Even when go version strings match (go1.25.11), a freshly-downloaded
tarball can produce a different compiler binary hash than the binary
embedded in the ghcr.io/tgjls/adaptixc2 Docker image.

Fix: rebuild /app/adaptixserver from the TGJLS/AdaptixC2 source clone
using the same go1.25.11 we install, then build the Kharon plugins with
that same binary. Since both server and plugins are compiled by the
identical go binary, all package build IDs are guaranteed to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous rebuild-adaptixserver approach broke the pre-built beacon/gopher
plugins (the server was rebuilt from HEAD, which uses a different axc2 than the
image was built with).

Root cause of original Kharon failure: axc2 ABI mismatch was caused by
TRANSITIVE dependency version skew (x/sys, x/text, etc.), not the compiler
binary hash. Even with identical axc2 h1: source hashes, different x/sys/x/text
versions produce different axc2 build IDs because build IDs are recursive.

Fix: extract every dep version embedded in the server binary via
'go version -m /app/adaptixserver' and pin them all with 'go mod edit -require'
before building Kharon plugins. This guarantees identical build IDs for all
shared packages without touching the pre-built server or beacon plugins.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Kharon extender config paths were pre-committed into profile.yaml,
causing adaptixc2 to crash-loop at startup because those files don't
exist until after install-kharon.sh runs. The testing-kit activate step
adds them dynamically — the static file must not reference them.

This also fixes the Extension-Kit BOFs job which shares the same
profile.yaml and was crash-looping for the same reason.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The container's go env file has GOFLAGS=-ldflags="-s -w".  Go combines
env-var GOFLAGS and go-env-file GOFLAGS, so adding GOFLAGS='-mod=mod'
produced the invalid combined value '-mod=mod -ldflags="-s -w"'.  When
tokenized, '-w"' appears as an unknown flag.

go mod download is explicitly exempt from -mod=readonly and updates
go.sum without needing -mod=mod.  Use that instead to pre-populate go.sum
before the plugin builds, then drop GOFLAGS entirely from the build steps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Go plugin ABI requires every shared package (axc2, x/sys, x/text…) to have
the same package build ID, computed as:
  hash(source_content + dep_build_ids + compiler_binary_hash)

Even when go version strings match (go1.25.11), a freshly-downloaded tarball
has a different compiler binary hash than the one baked into the Docker image.
This causes all shared package build IDs to diverge, so plugin.Open fails with
"plugin was built with a different version of package axc2".

Fix (building on e6b1217):
1. Read original server dep versions from the binary BEFORE any rebuild.
2. Clone TGJLS/AdaptixC2 and pin those same dep versions in go.mod.
3. Rebuild /app/adaptixserver with our downloaded go1.25.11 (same binary
   that will compile Kharon plugins) → server and plugins share the same
   compiler hash, guaranteeing identical package build IDs.

The pre-built beacon/gopher plugins will fail ABI checks against the rebuilt
server (different compiler hash), but the Kharon test job only needs Kharon
to load — beacon/gopher failures are non-fatal and don't block the test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Windows QEMU VM (dockurr/windows) uses QEMU user-mode networking
where DNS for Docker container hostnames ('adaptixc2') is not available.
The Beacon agent works because its callback address is hardcoded to the
static IP 172.28.0.10:8080.

The Kharon malleable profile had 'adaptixc2:8080' as the callback host,
which gets baked into the agent binary via HTTP_MALLEABLE_BYTES. The
Windows VM cannot resolve 'adaptixc2' so the agent never beacons.

Fix: use 172.28.0.10:8080 (adaptixc2's fixed Docker network IP) directly,
matching how the Beacon agent's callback_addresses is configured in
config.yaml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
install-kharon.sh:
- Fix BINARY_GOEXP awk: only set when binary actually carries X: flags;
  the old awk always printed NR==1, so a server built without GOEXPERIMENT
  would set BINARY_GOEXP to the full version string and abort every go build
- Fix pl_agent.go patch to exit 1 on failure instead of printing a warning
  and continuing, which would silently build the plugin with KH_SLEEP_MASK=3
- Remove 2>/dev/null from server-rebuild go mod download so errors are visible
- Consolidate two separate apt-get update+install blocks into one
- Drop diagnostic dump blocks (=== Original adaptixserver build info ===,
  === Generated go.work ===, === listener_kharon_http/Makefile ===,
  === axc2 version in built plugins ===, === Key dep versions ===)

runner.py:
- _resolve_agent_from_extender was passing port_bind=0 for all agent schema
  fields; network-source fields (callback_addresses) resolved to host:0
  instead of the actual listener port

api.py:
- activate_extender for listener type now removes the previously active
  listener's profile.yaml entries before writing the new ones, preventing
  both configs from accumulating in the file across extender swaps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@TGJLS
TGJLS merged commit 6ac0aa3 into main Aug 3, 2026
10 checks passed
@TGJLS
TGJLS deleted the feat/extender-support branch August 3, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant