Compare commits

...
Author SHA1 Message Date
moloch-- 5dc6adae47 Pin Reflektor v0.0.6 2026-08-19 13:33:44 -05:00
moloch-- 264b00f3c7 Pin Reflektor v0.0.5 2026-08-19 12:22:06 -05:00
moloch-- ada79c8ecd Use Reflektor native loader for Unix extensions 2026-08-19 11:06:49 -05:00
moloch-- edd4c3d097 Support native extensions on macOS and Linux
Replace the Darwin universal loader with Reflektor, add Linux extension handlers and a shared Unix callback ABI, fix client registration ordering and target visibility, and extend the Reflektor integration lifecycle across supported platforms.
2026-08-19 09:36:44 -05:00
Joe aee0958e99 Merge pull request #2329 from BishopFox/dependabot/go_modules/charm.land/lipgloss/v2-2.0.6
chore(deps): bump charm.land/lipgloss/v2 from 2.0.4 to 2.0.6
2026-08-18 13:36:26 -07:00
Joe 23c067982a Merge pull request #2315 from BishopFox/dependabot/npm_and_yarn/docs/sliver-docs/mermaid-11.16.1
chore(deps): bump mermaid from 11.15.0 to 11.16.1 in /docs/sliver-docs
2026-08-18 13:36:12 -07:00
dependabot[bot] 7e917da3ac chore(deps): bump charm.land/lipgloss/v2 from 2.0.4 to 2.0.6
Bumps [charm.land/lipgloss/v2](https://github.com/charmbracelet/lipgloss) from 2.0.4 to 2.0.6.
- [Release notes](https://github.com/charmbracelet/lipgloss/releases)
- [Commits](https://github.com/charmbracelet/lipgloss/compare/v2.0.4...v2.0.6)

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

Signed-off-by: dependabot[bot] <[email protected]>
2026-08-18 03:08:02 +00:00
Joe 674b49f41f Merge pull request #2303 from BishopFox/dependabot/go_modules/github.com/jedib0t/go-pretty/v6-6.8.3
chore(deps): bump github.com/jedib0t/go-pretty/v6 from 6.7.8 to 6.8.3
2026-08-17 20:05:17 -07:00
moloch-- 4ba04df621 Only run reflektor tests on changes to implant/... 2026-08-17 20:02:29 -07:00
Joe 3274382bc7 Merge pull request #2333 from BishopFox/docs-refresh
Docs refresh
2026-08-17 19:58:50 -07:00
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
dependabot[bot] 9c9943c94c chore(deps): bump github.com/jedib0t/go-pretty/v6 from 6.7.8 to 6.8.3
Bumps [github.com/jedib0t/go-pretty/v6](https://github.com/jedib0t/go-pretty) from 6.7.8 to 6.8.3.
- [Release notes](https://github.com/jedib0t/go-pretty/releases)
- [Commits](https://github.com/jedib0t/go-pretty/compare/v6.7.8...v6.8.3)

---
updated-dependencies:
- dependency-name: github.com/jedib0t/go-pretty/v6
  dependency-version: 6.8.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-08-18 00:10:18 +00:00
Joe 2e9b52371e Merge pull request #2332 from BishopFox/reflektor-tests
Test Sliver shared libraries with Reflektor
2026-08-17 17:07:02 -07:00
moloch-- 6d6df68972 Update Reflektor to v0.0.3
Synchronize the root and implant module graphs and vendor trees with the all-platform Go c-shared loader fixes.
2026-08-17 00:29:26 -07:00
moloch-- 7f863d3172 Fix cross-platform Reflektor integration failures
Close rendered implant files before import renames, use native GCC for Linux/386 Go shared libraries, and preserve actionable integration logs.
2026-08-17 00:28:49 -07:00
moloch-- 6ecb379bce Test Sliver shared libraries with Reflektor 2026-08-16 22:09:22 -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
Ronan Kervella b5d3d0bad6 Merge branch 'master' into dependabot/npm_and_yarn/docs/sliver-docs/mermaid-11.16.1 2026-08-13 09:41:06 -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
dependabot[bot] 528b04427a chore(deps): bump mermaid from 11.15.0 to 11.16.1 in /docs/sliver-docs
Bumps [mermaid](https://github.com/mermaid-js/mermaid) from 11.15.0 to 11.16.1.
- [Release notes](https://github.com/mermaid-js/mermaid/releases)
- [Commits](https://github.com/mermaid-js/mermaid/compare/[email protected]@11.16.1)

---
updated-dependencies:
- dependency-name: mermaid
  dependency-version: 11.16.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-08-13 04:00:22 +00: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
1772 changed files with 162755 additions and 194615 deletions

No files matched your search

+2 -2
View File
@@ -52,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
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
with:
fetch-depth: 0
- 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
# Gate only findings introduced since the event base revision.
args: --new-from-rev=${{ github.event.pull_request.base.sha || github.event.before }} ./client/... ./server/... ./util/... ./protobuf/... ./docs ./implant/...
+231
View File
@@ -0,0 +1,231 @@
name: Reflektor Integration Test Matrix
on:
push:
branches:
- master
paths:
- "implant/**"
- "test/reflektor/**"
- ".github/workflows/reflektor-tests.yml"
pull_request:
paths:
- "implant/**"
- "test/reflektor/**"
- ".github/workflows/reflektor-tests.yml"
types:
- opened
- reopened
- synchronize
- ready_for_review
workflow_dispatch:
permissions:
contents: read
jobs:
darwin:
name: reflektor-darwin-${{ matrix.goarch }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 180
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15-intel
goarch: amd64
server_arch: amd64
- runner: macos-15
goarch: arm64
server_arch: arm64
defaults:
run:
shell: bash
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true
- name: Download Sliver build assets
run: go run -mod=vendor ./util/cmd/assets
- name: Build Sliver server
env:
CGO_ENABLED: "0"
GOARCH: ${{ matrix.server_arch }}
GOOS: darwin
run: go build -mod=vendor -trimpath -tags go_sqlite,server -o sliver-server ./server
- name: Build Reflektor integration driver
env:
CGO_ENABLED: "0"
GOARCH: ${{ matrix.goarch }}
GOOS: darwin
run: go build -mod=vendor -trimpath -tags client,go_sqlite -o sliver-reflektor-e2e ./test/reflektor
- name: Load Sliver with Reflektor
run: >-
./sliver-reflektor-e2e
-repo .
-server ./sliver-server
-target-os darwin
-target-arch ${{ matrix.goarch }}
linux-native:
name: reflektor-linux-${{ matrix.goarch }}-native
runs-on: ${{ matrix.runner }}
timeout-minutes: 180
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
goarch: amd64
server_arch: amd64
- runner: ubuntu-24.04-arm
goarch: arm64
server_arch: arm64
defaults:
run:
shell: bash
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true
- name: Download Sliver build assets
run: go run -mod=vendor ./util/cmd/assets
- name: Build Sliver server
env:
CGO_ENABLED: "0"
GOARCH: ${{ matrix.server_arch }}
GOOS: linux
run: go build -mod=vendor -trimpath -tags go_sqlite,server -o sliver-server ./server
- name: Build Reflektor integration driver
env:
CGO_ENABLED: "0"
GOARCH: ${{ matrix.goarch }}
GOOS: linux
run: go build -mod=vendor -trimpath -tags client,go_sqlite -o sliver-reflektor-e2e ./test/reflektor
- name: Load Sliver with Reflektor
run: >-
./sliver-reflektor-e2e
-repo .
-server ./sliver-server
-target-os linux
-target-arch ${{ matrix.goarch }}
linux-386:
name: reflektor-linux-386-docker
runs-on: ubuntu-24.04
timeout-minutes: 180
env:
CONTAINER_NAME: sliver-reflektor-linux-386-${{ github.run_id }}-${{ github.run_attempt }}
IMAGE_NAME: sliver-reflektor-linux-386:${{ github.run_id }}-${{ github.run_attempt }}
defaults:
run:
shell: bash
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build linux/386 test image
run: |
set -euo pipefail
go_version="$(awk '$1 == "go" { print $2; exit }' go.mod)"
docker buildx build \
--load \
--platform linux/386 \
--build-arg "GO_VERSION=${go_version}" \
--file test/reflektor/Dockerfile.linux-386 \
--tag "${IMAGE_NAME}" \
.
docker buildx prune --all --force
- name: Load Sliver with Reflektor
run: >-
docker run
--name "${CONTAINER_NAME}"
--platform linux/386
"${IMAGE_NAME}"
- name: Clean up linux/386 Docker resources
if: ${{ always() }}
run: |
docker rm --force "${CONTAINER_NAME}" 2>/dev/null || true
docker image rm --force "${IMAGE_NAME}" 2>/dev/null || true
docker buildx prune --all --force >/dev/null 2>&1 || true
windows:
name: reflektor-windows-${{ matrix.goarch }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 180
strategy:
fail-fast: false
matrix:
include:
- runner: windows-2022
goarch: amd64
server_arch: amd64
- runner: windows-2022
goarch: 386
server_arch: amd64
- runner: windows-11-arm
goarch: arm64
server_arch: amd64
defaults:
run:
shell: bash
steps:
- name: Check out code
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true
- name: Download Sliver build assets
run: go run -mod=vendor ./util/cmd/assets
- name: Build Sliver server
env:
CGO_ENABLED: "0"
GOARCH: ${{ matrix.server_arch }}
GOOS: windows
run: go build -mod=vendor -trimpath -tags go_sqlite,server -o sliver-server.exe ./server
- name: Build Reflektor integration driver
env:
CGO_ENABLED: "0"
GOARCH: ${{ matrix.goarch }}
GOOS: windows
run: go build -mod=vendor -trimpath -tags client,go_sqlite -o sliver-reflektor-e2e.exe ./test/reflektor
- name: Load Sliver with Reflektor
run: >-
./sliver-reflektor-e2e.exe
-repo .
-server ./sliver-server.exe
-target-os windows
-target-arch ${{ matrix.goarch }}
+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.
+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)
+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)
}
}
}
@@ -1,10 +1,21 @@
package extensions
import (
"context"
"encoding/json"
"errors"
"testing"
"time"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/util"
"github.com/spf13/cobra"
"google.golang.org/grpc"
)
/*
@@ -115,6 +126,25 @@ const (
}`
)
type registerExtensionTestRPC struct {
rpcpb.SliverRPCClient
register func(context.Context, *sliverpb.RegisterExtensionReq, ...grpc.CallOption) (*sliverpb.RegisterExtension, error)
}
func (f *registerExtensionTestRPC) RegisterExtension(ctx context.Context, req *sliverpb.RegisterExtensionReq, opts ...grpc.CallOption) (*sliverpb.RegisterExtension, error) {
return f.register(ctx, req, opts...)
}
func newExtensionTestConsole(t *testing.T, rpc rpcpb.SliverRPCClient, session *clientpb.Session, beacon *clientpb.Beacon) *console.SliverClient {
t.Helper()
con := console.NewConsole(false)
con.IsCLI = true
con.Rpc = rpc
con.ActiveTarget.Set(session, beacon)
return con
}
func TestParseExtensionManifest(t *testing.T) {
expectedPath := util.ResolvePath("foo/test1.dll")
@@ -189,6 +219,218 @@ func TestParseExtensionManifest(t *testing.T) {
}
func TestConvertOldManifestPreservesInit(t *testing.T) {
const initExport = "InitializeExtension"
manifest, err := ParseExtensionManifest([]byte(`{
"name": "legacy-test",
"command_name": "legacy-test",
"help": "some help",
"init": "InitializeExtension",
"files": [
{
"os": "windows",
"arch": "amd64",
"path": "foo/test.dll"
}
]
}`))
if err != nil {
t.Fatalf("Error parsing legacy extension manifest: %s", err)
}
if len(manifest.ExtCommand) != 1 {
t.Fatalf("Expected 1 extension command, got %d", len(manifest.ExtCommand))
}
if manifest.ExtCommand[0].Init != initExport {
t.Errorf("Expected init export %q, got %q", initExport, manifest.ExtCommand[0].Init)
}
}
func TestRegisterExtensionPropagatesInit(t *testing.T) {
const initExport = "InitializeExtension"
tests := []struct {
name string
session *clientpb.Session
beacon *clientpb.Beacon
wantAsync bool
wantID string
}{
{
name: "session",
session: &clientpb.Session{ID: "session-id"},
wantID: "session-id",
},
{
name: "beacon",
beacon: &clientpb.Beacon{ID: "beacon-id"},
wantAsync: true,
wantID: "beacon-id",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var gotReq *sliverpb.RegisterExtensionReq
rpc := &registerExtensionTestRPC{
register: func(_ context.Context, req *sliverpb.RegisterExtensionReq, _ ...grpc.CallOption) (*sliverpb.RegisterExtension, error) {
gotReq = req
return &sliverpb.RegisterExtension{Response: &commonpb.Response{}}, nil
},
}
con := newExtensionTestConsole(t, rpc, test.session, test.beacon)
err := registerExtension("linux", &ExtCommand{CommandName: "test", Init: initExport}, []byte("extension"), nil, con)
if err != nil {
t.Fatalf("RegisterExtension returned an error: %s", err)
}
if gotReq == nil {
t.Fatal("RegisterExtension RPC was not called")
}
if gotReq.Init != initExport {
t.Errorf("Expected init export %q, got %q", initExport, gotReq.Init)
}
if gotReq.Request.GetAsync() != test.wantAsync {
t.Errorf("Expected async=%t, got %t", test.wantAsync, gotReq.Request.GetAsync())
}
if test.wantAsync && gotReq.Request.GetBeaconID() != test.wantID {
t.Errorf("Expected beacon ID %q, got %q", test.wantID, gotReq.Request.GetBeaconID())
}
if !test.wantAsync && gotReq.Request.GetSessionID() != test.wantID {
t.Errorf("Expected session ID %q, got %q", test.wantID, gotReq.Request.GetSessionID())
}
})
}
}
func TestRegisterExtensionWaitsForBeaconRegistration(t *testing.T) {
started := make(chan struct{})
release := make(chan struct{})
rpc := &registerExtensionTestRPC{
register: func(_ context.Context, _ *sliverpb.RegisterExtensionReq, _ ...grpc.CallOption) (*sliverpb.RegisterExtension, error) {
close(started)
<-release
return &sliverpb.RegisterExtension{Response: &commonpb.Response{Async: true}}, nil
},
}
con := newExtensionTestConsole(t, rpc, nil, &clientpb.Beacon{ID: "beacon-id"})
done := make(chan error, 1)
go func() {
done <- registerExtension("linux", &ExtCommand{CommandName: "test"}, []byte("extension"), nil, con)
}()
select {
case <-started:
case <-time.After(time.Second):
t.Fatal("Timed out waiting for RegisterExtension RPC")
}
select {
case err := <-done:
t.Fatalf("RegisterExtension returned before the beacon registration was enqueued: %v", err)
case <-time.After(100 * time.Millisecond):
}
close(release)
select {
case err := <-done:
if err != nil {
t.Fatalf("RegisterExtension returned an error: %s", err)
}
case <-time.After(time.Second):
t.Fatal("Timed out waiting for RegisterExtension to return")
}
}
func TestRegisterExtensionReturnsBeaconRPCError(t *testing.T) {
wantErr := errors.New("register failed")
rpc := &registerExtensionTestRPC{
register: func(_ context.Context, _ *sliverpb.RegisterExtensionReq, _ ...grpc.CallOption) (*sliverpb.RegisterExtension, error) {
return nil, wantErr
},
}
con := newExtensionTestConsole(t, rpc, nil, &clientpb.Beacon{ID: "beacon-id"})
err := registerExtension("linux", &ExtCommand{CommandName: "test"}, []byte("extension"), nil, con)
if !errors.Is(err, wantErr) {
t.Fatalf("Expected error %q, got %v", wantErr, err)
}
}
func TestRegisterExtensionRejectsNilResponse(t *testing.T) {
rpc := &registerExtensionTestRPC{
register: func(_ context.Context, _ *sliverpb.RegisterExtensionReq, _ ...grpc.CallOption) (*sliverpb.RegisterExtension, error) {
return nil, nil
},
}
con := newExtensionTestConsole(t, rpc, &clientpb.Session{ID: "session-id"}, nil)
if err := registerExtension("linux", &ExtCommand{CommandName: "test"}, []byte("extension"), nil, con); err == nil {
t.Fatal("RegisterExtension accepted a nil response")
}
}
func TestExtensionCommandVisibilityUsesExactTargetPairs(t *testing.T) {
extension := &ExtCommand{Files: []*extensionFile{
{OS: "windows", Arch: "amd64"},
{OS: "linux", Arch: "amd64"},
{OS: "darwin", Arch: "arm64"},
}}
tests := []struct {
name string
session *clientpb.Session
beacon *clientpb.Beacon
wantVisible bool
}{
{
name: "cross-platform Linux session",
session: &clientpb.Session{OS: "linux", Arch: "amd64"},
wantVisible: true,
},
{
name: "cross-platform macOS beacon",
beacon: &clientpb.Beacon{OS: "darwin", Arch: "arm64"},
wantVisible: true,
},
{
name: "cross-platform Windows session",
session: &clientpb.Session{OS: "windows", Arch: "amd64"},
wantVisible: true,
},
{
name: "Linux architecture mismatch",
session: &clientpb.Session{OS: "linux", Arch: "arm64"},
wantVisible: false,
},
{
name: "macOS architecture mismatch",
beacon: &clientpb.Beacon{OS: "darwin", Arch: "amd64"},
wantVisible: false,
},
{
name: "Windows architecture mismatch",
session: &clientpb.Session{OS: "windows", Arch: "386"},
wantVisible: false,
},
{
name: "unsupported native extension target",
beacon: &clientpb.Beacon{OS: "linux", Arch: "riscv64"},
wantVisible: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
con := newExtensionTestConsole(t, nil, test.session, test.beacon)
command := &cobra.Command{Annotations: makeCommandPlatformFilters(extension)}
activeFilters := con.App.Menu(consts.ImplantMenu).ActiveFiltersFor(command)
visible := len(activeFilters) == 0
if visible != test.wantVisible {
t.Fatalf("command visibility = %t, want %t (active filters: %v)", visible, test.wantVisible, activeFilters)
}
})
}
}
func TestParseMultipleCmdManifest(t *testing.T) {
expectedPath := util.ResolvePath("ex.dll")
+38 -51
View File
@@ -221,6 +221,7 @@ func convertOldManifest(old *ExtensionManifest_) *ExtensionManifest {
Help: old.Help,
LongHelp: old.LongHelp,
Entrypoint: old.Entrypoint,
Init: old.Init,
Files: old.Files,
Arguments: old.Arguments,
Schema: nil,
@@ -370,8 +371,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 +384,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)
@@ -468,44 +472,30 @@ func registerExtension(goos string, ext *ExtCommand, binData []byte, cmd *cobra.
//set extension name to a hash of the data to avoid loading more than one instance
bd := sha256.Sum256(binData)
name := hex.EncodeToString(bd[:])
sess, beac := con.ActiveTarget.GetInteractive()
sess, _ := con.ActiveTarget.GetInteractive()
ctrl := make(chan bool)
//first time run of an extension will require some waiting depending on the size
if sess != nil {
msg := fmt.Sprintf("Sending %s to implant ...", ext.CommandName)
con.SpinUntil(msg, ctrl)
}
//don't block if we are in beacon mode
if beac != nil && sess == nil {
go func() {
registerResp, err := con.Rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
Name: name,
Data: binData,
OS: goos,
Init: ext.Init,
Request: con.ActiveTarget.Request(cmd),
})
if err != nil {
con.PrintErrorf("Error registering extension: %s\n", err)
}
if registerResp.Response != nil && registerResp.Response.Err != "" {
con.PrintErrorf("Error registering extension: %s\n", errors.New(registerResp.Response.Err))
}
}()
return nil
}
//session mode (hopefully)
registerResp, err := con.Rpc.RegisterExtension(context.Background(), &sliverpb.RegisterExtensionReq{
Name: name,
Data: binData,
OS: goos,
Init: ext.Init,
Request: con.ActiveTarget.Request(cmd),
})
ctrl <- true
<-ctrl
if sess != nil {
ctrl <- true
<-ctrl
}
if err != nil {
return err
}
if registerResp == nil {
return errors.New("received empty register extension response")
}
if registerResp.Response != nil && registerResp.Response.Err != "" {
return errors.New(registerResp.Response.Err)
}
@@ -529,6 +519,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
@@ -850,37 +852,22 @@ func makeExtensionFlagNameCompletion(extCmd *ExtCommand) carapace.Action {
}
func makeCommandPlatformFilters(extCmd *ExtCommand) map[string]string {
filtersOS := make(map[string]bool)
filtersArch := make(map[string]bool)
var all []string
// Only add filters for architectures when there OS matters.
supportedTargets := make(map[string]struct{}, len(extCmd.Files))
for _, file := range extCmd.Files {
filtersOS[file.OS] = true
if filtersOS[file.OS] {
filtersArch[file.Arch] = true
}
supportedTargets[file.OS+"/"+file.Arch] = struct{}{}
}
for os, enabled := range filtersOS {
if enabled {
all = append(all, os)
// Console command filters are hide-on-match, so annotate the command with
// every exact target pair it does not support. A separate fallback hides all
// native extensions on target pairs outside the supported loader matrix.
filters := []string{consts.NativeExtensionUnsupportedTargetFilter}
for _, target := range consts.NativeExtensionTargets() {
if _, supported := supportedTargets[target.GOOS+"/"+target.GOARCH]; !supported {
filters = append(filters, target.Filter)
}
}
for arch, enabled := range filtersArch {
if enabled {
all = append(all, arch)
}
}
if len(all) == 0 {
return map[string]string{}
}
return map[string]string{
appConsole.CommandFilterKey: strings.Join(all, ","),
appConsole.CommandFilterKey: strings.Join(filters, ","),
}
}
+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()
+6
View File
@@ -87,6 +87,7 @@ var (
"linux/arm64": true,
"windows/386": true,
"windows/amd64": true,
"windows/arm64": true,
}
ErrNoExternalBuilder = errors.New("no external builders are available")
@@ -254,6 +255,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 +392,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:
+2
View File
@@ -1147,6 +1147,7 @@ func (con *SliverClient) ExposeCommands() {
case con.ActiveTarget.session != nil:
session := con.ActiveTarget.session
filters = append(filters, consts.BeaconCmdsFilter)
filters = append(filters, consts.NativeExtensionTargetFilter(session.OS, session.Arch))
// Operating system
if session.OS != "windows" {
@@ -1161,6 +1162,7 @@ func (con *SliverClient) ExposeCommands() {
case con.ActiveTarget.beacon != nil:
beacon := con.ActiveTarget.beacon
filters = append(filters, consts.SessionCmdsFilter)
filters = append(filters, consts.NativeExtensionTargetFilter(beacon.OS, beacon.Arch))
// Operating system
if beacon.OS != "windows" {
+45
View File
@@ -363,8 +363,53 @@ const (
BeaconCmdsFilter = "beacon"
WindowsCmdsFilter = "windows"
WireguardCmdsFilter = "wireguard"
NativeExtensionUnsupportedTargetFilter = "native-extension:unsupported"
NativeExtensionDarwinAMD64Filter = "native-extension:darwin/amd64"
NativeExtensionDarwinARM64Filter = "native-extension:darwin/arm64"
NativeExtensionLinux386Filter = "native-extension:linux/386"
NativeExtensionLinuxAMD64Filter = "native-extension:linux/amd64"
NativeExtensionLinuxARM64Filter = "native-extension:linux/arm64"
NativeExtensionWindows386Filter = "native-extension:windows/386"
NativeExtensionWindowsAMD64Filter = "native-extension:windows/amd64"
NativeExtensionWindowsARM64Filter = "native-extension:windows/arm64"
)
// NativeExtensionTarget identifies a GOOS/GOARCH pair supported by native
// implant extensions and the command filter associated with that exact pair.
type NativeExtensionTarget struct {
GOOS string
GOARCH string
Filter string
}
var nativeExtensionTargets = [8]NativeExtensionTarget{
{GOOS: "darwin", GOARCH: "amd64", Filter: NativeExtensionDarwinAMD64Filter},
{GOOS: "darwin", GOARCH: "arm64", Filter: NativeExtensionDarwinARM64Filter},
{GOOS: "linux", GOARCH: "386", Filter: NativeExtensionLinux386Filter},
{GOOS: "linux", GOARCH: "amd64", Filter: NativeExtensionLinuxAMD64Filter},
{GOOS: "linux", GOARCH: "arm64", Filter: NativeExtensionLinuxARM64Filter},
{GOOS: "windows", GOARCH: "386", Filter: NativeExtensionWindows386Filter},
{GOOS: "windows", GOARCH: "amd64", Filter: NativeExtensionWindowsAMD64Filter},
{GOOS: "windows", GOARCH: "arm64", Filter: NativeExtensionWindowsARM64Filter},
}
// NativeExtensionTargets returns the supported native extension target matrix.
func NativeExtensionTargets() [8]NativeExtensionTarget {
return nativeExtensionTargets
}
// NativeExtensionTargetFilter returns the filter for an exact supported target.
// Unsupported pairs share a filter that hides all native extension commands.
func NativeExtensionTargetFilter(goos string, goarch string) string {
for _, target := range nativeExtensionTargets {
if target.GOOS == goos && target.GOARCH == goarch {
return target.Filter
}
}
return NativeExtensionUnsupportedTargetFilter
}
// Creds (needed here to avoid recursive imports).
const (
UserColonHashNewlineFormat = "user:hash" // username:hash\n
+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,
+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,9 +3,9 @@ import React from "react";
const LoadingState: React.FC = () => {
return (
<div className="flex min-h-screen w-full items-center justify-center gap-3 text-foreground">
<div className="flex min-h-[55vh] w-full items-center justify-center gap-3 text-muted">
<Spinner aria-label="Loading" />
<span>Loading...</span>
<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>
);
+162 -128
View File
@@ -5,24 +5,28 @@ 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, SearchField, 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";
const routes = [
{ href: "/docs", label: "Docs" },
{ href: "/tutorials", label: "Tutorials" },
{ href: "/talks", label: "Talks" },
{ href: "/docs", label: "Docs" },
];
const discordUrl =
"https://discord.com/channels/791066041198968873/1339996286514106409";
const githubUrl = "https://github.com/BishopFox/sliver";
export default function TopNavbar() {
const router = useRouter();
const { theme, setTheme } = useTheme();
@@ -30,9 +34,28 @@ export default function TopNavbar() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
const activeTheme = theme || Themes.DARK;
const lightDarkModeIcon = React.useMemo(() => {
return activeTheme === Themes.DARK ? faSun : faMoon;
}, [activeTheme]);
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(() => {
const desktopBreakpoint = window.matchMedia("(min-width: 768px)");
const closeMobileMenu = (event: MediaQueryListEvent) => {
if (event.matches) {
setIsMobileMenuOpen(false);
}
};
desktopBreakpoint.addEventListener("change", closeMobileMenu);
return () =>
desktopBreakpoint.removeEventListener("change", closeMobileMenu);
}, []);
const handleSearchSubmit = React.useCallback(
(value = query) => {
@@ -41,7 +64,11 @@ export default function TopNavbar() {
if (searchQuery.length === 0) {
return;
}
router.push({ pathname: "/search/", query: { search: searchQuery } });
void router.push({
pathname: "/search/",
query: { search: searchQuery },
});
setQuery("");
setIsMobileMenuOpen(false);
},
@@ -58,12 +85,13 @@ export default function TopNavbar() {
[],
);
const renderSearchInput = (wrapperClassName?: string) => (
const renderSearchInput = (className?: string) => (
<SearchField
aria-label="Search documentation"
className={wrapperClassName}
className={className}
fullWidth
value={query}
variant="secondary"
onChange={setQuery}
onSubmit={handleSearchSubmit}
>
@@ -71,8 +99,10 @@ export default function TopNavbar() {
<SearchField.SearchIcon>
<FontAwesomeIcon icon={faSearch} />
</SearchField.SearchIcon>
<SearchField.Input placeholder="Search..." />
{query.length > 0 ? <SearchField.ClearButton aria-label="Clear search" /> : null}
<SearchField.Input placeholder="Search documentation…" />
{query.length > 0 ? (
<SearchField.ClearButton aria-label="Clear search" />
) : null}
</SearchField.Group>
</SearchField>
);
@@ -82,82 +112,80 @@ export default function TopNavbar() {
return (
<li key={href}>
<Button
variant={isActive ? "secondary" : "ghost"}
onPress={() => router.push(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}
</Button>
</NextLink>
</li>
);
};
const renderMobileRoute = (href: string, label: string) => {
const isActive = href === "/" ? router.pathname === "/" : router.pathname.startsWith(href);
const isActive =
href === "/"
? router.pathname === "/"
: router.pathname.startsWith(href);
return (
<Button
<NextLink
key={href}
fullWidth
variant={isActive ? "primary" : "ghost"}
onPress={() => {
router.push(href);
setIsMobileMenuOpen(false);
}}
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)}
>
{href === "/" ? <FontAwesomeIcon fixedWidth icon={faHome} /> : null}
{label}
</Button>
</NextLink>
);
};
return (
<>
<nav className="sliver-navbar-glass sticky top-0 z-40 px-4 shadow-sm md:px-6">
<div className="flex h-16 items-center gap-4">
<button
type="button"
className="flex items-center gap-2 text-foreground"
onClick={() => {
router.push("/");
setIsMobileMenuOpen(false);
}}
aria-label="Sliver C2 home"
<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"
>
<SliversIcon />
<span className="hidden font-bold text-inherit sm:block">
Sliver C2
<span className="flex size-8 items-center justify-center rounded-xl bg-accent text-accent-foreground">
<SliversIcon height={18} />
</span>
</button>
<span className="hidden items-baseline gap-1.5 sm:flex">
<span>Sliver C2</span>
</span>
</NextLink>
<ul className="hidden items-center gap-2 md:flex">
<li>
{renderTooltip(
"Home",
<Button
isIconOnly
aria-label="Home"
variant={router.pathname === "/" ? "secondary" : "ghost"}
onPress={() => router.push("/")}
>
<FontAwesomeIcon fixedWidth icon={faHome} />
</Button>,
)}
</li>
{routes.map((route) => renderDesktopRoute(route.href, route.label))}
<ul className="hidden items-center gap-1 md:flex">
{routes.map((route) =>
renderDesktopRoute(route.href, route.label),
)}
</ul>
<div className="ml-auto hidden items-center gap-2 md:flex">
{renderSearchInput("w-64")}
<div className="ml-auto hidden items-center gap-1 md:flex">
{renderSearchInput("mr-2 w-56 xl:w-72")}
{renderTooltip(
activeTheme === Themes.DARK ? "Switch to light mode" : "Switch to dark mode",
themeToggleLabel,
<Button
isIconOnly
aria-label={activeTheme === Themes.DARK ? "Switch to light mode" : "Switch to dark mode"}
aria-label={themeToggleLabel}
variant="ghost"
onPress={() => {
setTheme(activeTheme === Themes.DARK ? Themes.LIGHT : Themes.DARK);
}}
onPress={toggleTheme}
>
<FontAwesomeIcon icon={lightDarkModeIcon} />
</Button>,
@@ -165,62 +193,55 @@ export default function TopNavbar() {
{renderTooltip(
"Join Discord",
<Button
isIconOnly
<a
href={discordUrl}
target="_blank"
rel="noreferrer"
aria-label="Join Discord"
variant="ghost"
onPress={() => {
window.open(
"https://discord.com/channels/791066041198968873/1339996286514106409",
"_blank",
"noopener,noreferrer",
);
}}
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} />
</Button>,
</a>,
)}
{renderTooltip(
"View on GitHub",
<Button
isIconOnly
<a
href={githubUrl}
target="_blank"
rel="noreferrer"
aria-label="View on GitHub"
variant="ghost"
onPress={() => {
window.open(
"https://github.com/BishopFox/sliver",
"_blank",
"noopener,noreferrer",
);
}}
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} />
</Button>,
</a>,
)}
</div>
<div className="ml-auto flex items-center gap-2 md:hidden">
<div className="ml-auto flex items-center gap-1 md:hidden">
{renderTooltip(
activeTheme === Themes.DARK ? "Switch to light mode" : "Switch to dark mode",
themeToggleLabel,
<Button
isIconOnly
aria-label={activeTheme === Themes.DARK ? "Switch to light mode" : "Switch to dark mode"}
aria-label={themeToggleLabel}
variant="ghost"
onPress={() => {
setTheme(activeTheme === Themes.DARK ? Themes.LIGHT : Themes.DARK);
}}
onPress={toggleTheme}
>
<FontAwesomeIcon icon={lightDarkModeIcon} />
</Button>,
)}
{renderTooltip(
isMobileMenuOpen ? "Close menu" : "Open menu",
<Button
isIconOnly
variant="ghost"
aria-controls="mobile-navigation"
aria-expanded={isMobileMenuOpen}
aria-label={isMobileMenuOpen ? "Close menu" : "Open menu"}
onPress={() => setIsMobileMenuOpen((current) => !current)}
variant="ghost"
onPress={() =>
setIsMobileMenuOpen((current) => !current)
}
>
<FontAwesomeIcon icon={isMobileMenuOpen ? faXmark : faBars} />
</Button>,
@@ -229,43 +250,56 @@ export default function TopNavbar() {
</div>
</nav>
{isMobileMenuOpen ? (
<div className="sliver-navbar-glass px-4 pb-4 shadow-sm md:hidden">
<div className="mt-3">{renderSearchInput("w-full")}</div>
<div className="mt-4 flex flex-col gap-2">
{renderMobileRoute("/", "Home")}
{routes.map((route) => renderMobileRoute(route.href, route.label))}
<Button
fullWidth
variant="ghost"
onPress={() => {
window.open(
"https://discord.com/channels/791066041198968873/1339996286514106409",
"_blank",
"noopener,noreferrer",
);
}}
>
<FontAwesomeIcon icon={faDiscord} />
Discord
</Button>
<Button
fullWidth
variant="ghost"
onPress={() => {
window.open(
"https://github.com/BishopFox/sliver",
"_blank",
"noopener,noreferrer",
);
}}
>
<FontAwesomeIcon icon={faGithub} />
GitHub
</Button>
</div>
</div>
) : null}
<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>
</>
);
}
+33 -40
View File
@@ -3,7 +3,6 @@ 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;
@@ -21,55 +21,48 @@ export type TutorialCardCardProps = {
};
export default function TutorialCard(props: TutorialCardCardProps) {
const actionButtonClassName =
"ml-auto h-auto min-h-0 shrink-0 px-0 py-0 text-sm font-semibold [--button-bg-hover:transparent] [--button-bg-pressed:transparent] [--button-bg:transparent] [--button-fg:var(--warning)]";
return (
<Card className="relative z-0 gap-0 overflow-hidden p-0">
<Card.Header className="absolute left-3 right-3 top-3 z-10 flex-col items-end p-0 text-right">
<p className="text-sm font-bold uppercase leading-none text-white/70">
{props.name}
</p>
</Card.Header>
<div className={`${styles["hide-control-bar"]} overflow-hidden`}>
<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>
<Card.Footer className="absolute bottom-0 left-0 right-0 z-10 border-t border-white/20 bg-black/40 p-3 backdrop-blur-md">
<div className="flex min-h-10 w-full flex-row items-center gap-4">
<p
className={
props.italicDescription
? "min-w-0 flex-1 text-sm leading-snug text-white/80 italic"
: "min-w-0 flex-1 text-sm leading-snug 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"
size="sm"
onPress={props.onPress}
className={actionButtonClassName}
>
{props.buttonText ? props.buttonText : "Read Tutorial"}
<FontAwesomeIcon icon={faChevronRight} />
</Button>
) : (
<></>
)}
</div>
{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>
);
+1075 -1147
View File
File diff suppressed because it is too large. Load diff
+5 -3
View File
@@ -26,8 +26,8 @@
"asciinema-player": "^3.15.1",
"fuse.js": "^7.3.0",
"lunr": "^2.3.9",
"mermaid": "^11.14.0",
"next": "^16.2.4",
"mermaid": "^11.16.1",
"next": "^16.2.6",
"next-themes": "^0.4.6",
"react": "^19.2.5",
"react-dom": "^19.2.5",
@@ -51,8 +51,10 @@
"typescript": "^6.0.3"
},
"overrides": {
"dompurify": "^3.4.2",
"dompurify": "^3.4.13",
"postcss": "^8.5.13",
"sharp": "^0.35.3",
"shell-quote": "^1.8.4",
"uuid": "^14.0.0"
}
}
+53 -46
View File
@@ -8,6 +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 { RouterProvider } from "@heroui/react";
import {
HydrationBoundary,
QueryClient,
@@ -15,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());
@@ -101,54 +112,50 @@ export default function App({ Component, pageProps }: AppProps) {
>
<QueryClientProvider client={queryClient}>
<HydrationBoundary state={pageProps.dehydratedState}>
<SearchContext.Provider value={search}>
<Navbar />
<Component {...pageProps} />
<div className="mb-16 md:mb-20"></div>
<footer className="z-20 w-full border-t border-separator bg-surface/80 px-3 py-4 text-center shadow backdrop-blur-md dark:bg-black/75 md:fixed md:bottom-0 md:left-0 md:right-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-muted sm:text-sm md:flex-row md:items-center md:gap-3 md:text-left">
<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} />
<RouterProvider navigate={navigate}>
<SearchContext.Provider value={search}>
<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>
</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>
<ul className="mt-3 flex flex-wrap items-center justify-center gap-3 text-xs font-medium text-muted md:mt-0 md:gap-6 md:text-sm">
<li>
<a
href="https://github.com/BishopFox/sliver/blob/master/LICENSE"
target="_blank"
rel="noreferrer"
className="hover:underline"
>
GPLv3 License
</a>
</li>
</ul>
</footer>
</SearchContext.Provider>
</SearchContext.Provider>
</RouterProvider>
</HydrationBoundary>
</QueryClientProvider>
</NextThemesProvider>
+154 -77
View File
@@ -3,14 +3,18 @@ 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 { 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,
ListBox,
ScrollShadow,
SearchField,
Separator,
} from "@heroui/react";
import { useQuery } from "@tanstack/react-query";
import Fuse from "fuse.js";
@@ -20,6 +24,15 @@ 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 router = useRouter();
@@ -70,13 +83,20 @@ const DocsIndexPage: NextPage = () => {
}, [docs, name, visibleDocs]);
const listboxClasses =
"p-0 gap-0 divide-y divide-separator bg-surface overflow-visible rounded-lg shadow-sm";
"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}
>
@@ -99,87 +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">{renderFilterInput()}</div>
<select
id="docs-mobile-selector"
className="mt-3 w-full rounded-lg border border-separator bg-surface p-2 text-sm"
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">
{renderFilterInput("mt-2")}
<ScrollShadow className="max-h-[calc(100vh-15rem)] sliver-scrollbar overflow-y-auto pr-1 rounded-lg">
<ListBox
aria-label="Toolbox Menu"
className={listboxClasses}
onAction={(key) => {
router.push({
pathname: "/docs",
query: { name: String(key) },
});
}}
>
{visibleDocs.map((doc) => (
<ListBox.Item
key={doc.name}
id={doc.name}
textValue={doc.name}
className="h-12 rounded-none px-3 first:rounded-t-lg last:rounded-b-lg"
>
{doc.name}
</ListBox.Item>
))}
</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">
<Card.Header>
<span className="text-3xl">{name}</span>
</Card.Header>
<Separator />
<Card.Content>
<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
/>
</Card.Content>
</Card>
) : (
<div className="mt-8 text-center text-2xl text-foreground/90">
Welcome to the Sliver Wiki!
<div className="mt-2 text-xl text-muted">
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>
</>
@@ -168,6 +168,8 @@ As the alias support relies on Sliver side loading capabilities, please make sur
## Extensions
⚠️ Extensions are currently only supported when using Windows implants, though we're actively working on MacOS/Linux support.
Native extensions are supported by Windows implants (`386`, `amd64`, and `arm64`), Linux implants (`386`, `amd64`, and `arm64`), and macOS implants (`amd64` and `arm64`). Each manifest must include a platform-native shared library for every target it supports: `.dll` on Windows, `.so` on Linux, and `.dylib` on macOS.
Extensions are similar in structure to an alias, but internally work differently. An extension is an artifact of native code that is reflectively loaded by the implant and passed certain callbacks. These callbacks allow the extension to return data to the C2 server. Extensions may also have dependencies (other extensions), which Sliver will load prior to the extension; circular dependencies are not allowed. For example, all BOF extensions (`.o` files) rely on the COFF Loader extension (a `.dll`).
Native exports use the same Sliver extension ABI on every supported operating system. The export receives the argument buffer, its size, and a callback used to return output. The optional `init` export in the extension manifest is called once after the library is loaded and before any command export is invoked.
@@ -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
@@ -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.
+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 -122
View File
@@ -1,144 +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, Separator } 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 isDarkTheme = React.useMemo(() => {
return (resolvedTheme || Themes.DARK) !== Themes.LIGHT;
}, [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-separator bg-surface shadow-sm">
<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">
<Card.Header>
<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>
</Card.Header>
<Separator />
<Card.Content>
<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"
variant="primary"
onPress={() => {
window.open(
"https://github.com/BishopFox/sliver/releases/latest",
"_blank",
"noopener,noreferrer",
);
}}
>
<FontAwesomeIcon icon={faDownload} />
Download Latest Release
</Button>
<Button
className="sliver-armory-ghost flex-1"
variant="ghost"
onPress={() => {
window.open(
"https://github.com/sliverarmory",
"_blank",
"noopener,noreferrer",
);
}}
onPress={() => router.push("/tutorials")}
>
Visit the Armory
<FontAwesomeIcon icon={faExternalLink} />
Browse all tutorials
</Button>
</div>
</Card.Content>
</Card>
</div>
<div className="col-span-12 mt-8">
<Separator />
</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>
</>
);
}
+136 -37
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, Separator } 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}>
<Card.Content 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"
variant="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>
<Separator />
<div className="max-h-[200px] overflow-hidden">
<MarkdownViewer markdown={doc.content} />
</div>
) : null}
</Card.Content>
</Card>
))}
)}
</div>
</div>
</>
);
};
+113 -70
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, Button, Card } 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,81 +100,124 @@ const talkSections: TalkSection[] = [
];
const TalksIndexPage: NextPage = () => {
const [footerHidden, setFooterHidden] = React.useState<Record<string, boolean>>(
{}
);
const actionButtonClassName =
"ml-auto h-auto min-h-0 shrink-0 px-0 py-0 text-sm font-semibold [--button-bg-hover:transparent] [--button-bg-pressed:transparent] [--button-bg:transparent] [--button-fg:var(--warning)]";
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
allowsMultipleExpanded
defaultExpandedKeys={["workshops", "general-tradecraft"]}
hideSeparator
className="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="text-lg font-semibold">{section.title}</span>
<span className="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 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 className="relative z-0 gap-0 overflow-hidden p-0">
<Youtube
className="w-full"
url={talk.url}
title={talk.title}
onPlay={() => hideFooterFor(talk.url)}
/>
{footerHidden[talk.url] ? null : (
<Card.Footer className="absolute bottom-0 left-0 right-0 z-10 border-t border-white/20 bg-black/40 p-3 backdrop-blur-md">
<div className="flex min-h-10 w-full items-center gap-4">
<p className="min-w-0 flex-1 text-sm leading-snug text-white/80">{talk.description}</p>
<Button
variant="ghost"
size="sm"
className={actionButtonClassName}
onPress={() => {
window.open(talk.url, "_blank", "noopener,noreferrer");
}}
>
Watch <FontAwesomeIcon icon={faChevronRight} />
</Button>
</div>
</Card.Footer>
)}
</Card>
</div>
))}
</div>
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
))}
</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>
</>
);
+141 -77
View File
@@ -3,14 +3,18 @@ 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 { 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,
ListBox,
ScrollShadow,
SearchField,
Separator,
} from "@heroui/react";
import { useQuery } from "@tanstack/react-query";
import Fuse from "fuse.js";
@@ -70,13 +74,14 @@ const TutorialsIndexPage: NextPage = () => {
}, [name, tutorials, visibleTutorials]);
const listboxClasses =
"p-0 gap-0 divide-y divide-separator bg-surface overflow-visible rounded-lg shadow-sm";
"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}
>
@@ -99,87 +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">{renderFilterInput()}</div>
<select
id="tutorials-mobile-selector"
className="mt-3 w-full rounded-lg border border-separator bg-surface p-2 text-sm"
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">
{renderFilterInput("mt-2")}
<ScrollShadow className="max-h-[calc(100vh-6rem)] sliver-scrollbar overflow-y-auto pr-1 rounded-lg">
<ListBox
aria-label="Toolbox Menu"
className={listboxClasses}
onAction={(key) => {
router.push({
pathname: "/tutorials",
query: { name: String(key) },
});
}}
>
{visibleTutorials.map((tutorial) => (
<ListBox.Item
key={tutorial.name}
id={tutorial.name}
textValue={tutorial.name}
className="h-12 rounded-none px-3 first:rounded-t-lg last:rounded-b-lg"
>
{tutorial.name}
</ListBox.Item>
))}
</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">
<Card.Header>
<span className="text-3xl">{name}</span>
</Card.Header>
<Separator />
<Card.Content>
<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
/>
</Card.Content>
</Card>
) : (
<div className="mt-8 text-center text-2xl text-foreground/90">
Welcome to the Sliver Tutorials!
<div className="mt-2 text-xl text-muted">
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>
</>
+19 -8
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
+80 -21
View File
@@ -6,14 +6,39 @@
: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 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. */
@@ -31,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,
@@ -44,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;
}
@@ -57,33 +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;
}
.sliver-navbar-glass {
width: 100%;
border-bottom: 1px solid var(--separator);
background-color: color-mix(in oklab, var(--surface) 72%, transparent);
-webkit-backdrop-filter: blur(18px) saturate(1.18);
backdrop-filter: blur(18px) saturate(1.18);
.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%);
}
.sliver-armory-ghost {
--button-bg: transparent;
--button-bg-hover: rgb(109 40 217 / 0.16);
--button-bg-pressed: rgb(109 40 217 / 0.24);
--button-fg: #a78bfa;
.sliver-terminal-frame .ap-wrapper {
border-radius: 0 !important;
}
.dark .sliver-scrollbar {
scrollbar-color: rgba(79, 97, 125, 0.8) transparent;
.sliver-asciinema-bottom-aligned .ap-wrapper {
align-items: flex-end;
justify-content: flex-start;
}
.dark .sliver-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(79, 97, 125, 0.8);
.sliver-terminal-frame .asciinema-player {
background: #111315;
}
.sliver-asciinema-controls-hidden .ap-control-bar {
display: none !important;
}
.asciinema-player {
@@ -94,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;
}
}
+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.
+39 -49
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,14 +10,12 @@ 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.6
filippo.io/age v1.3.1
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0
github.com/Ne0nd0g/go-clr v1.0.3
github.com/SherClockHolmes/webpush-go v1.4.0
github.com/alecthomas/chroma v0.10.0
@@ -25,6 +23,7 @@ require (
github.com/cheggaaa/pb/v3 v3.1.7
github.com/chromedp/cdproto v0.0.0-20250803210736-d308e07a266d
github.com/chromedp/chromedp v0.14.1
github.com/ebitengine/purego v0.10.1
github.com/glebarez/sqlite v1.11.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/golang/protobuf v1.5.4
@@ -32,7 +31,7 @@ require (
github.com/gorilla/mux v1.8.1
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/hashicorp/yamux v0.1.2
github.com/jedib0t/go-pretty/v6 v6.7.8
github.com/jedib0t/go-pretty/v6 v6.8.3
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018
github.com/klauspost/compress v1.18.4
github.com/lesnuages/go-winio v0.4.19
@@ -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.6
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.47.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/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886 // 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,17 +231,15 @@ 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/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 // indirect
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 // indirect
github.com/atotto/clipboard v0.1.4
github.com/aws/aws-sdk-go-v2 v1.41.3 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.11 // indirect
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 // indirect
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.8
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 +271,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 +279,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.1
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.24 // 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 +314,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
+72 -116
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.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ=
charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q=
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=
@@ -51,19 +49,16 @@ firebase.google.com/go/v4 v4.18.0 h1:S+g0P72oDGqOaG4wlLErX3zQmU9plVdu7j+Bc3R1qFw
firebase.google.com/go/v4 v4.18.0/go.mod h1:P7UfBpzc8+Z3MckX79+zsWzKVfpGryr6HLbAe7gCWfs=
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb h1:c1Bb1n1wr2xgTHkNVxkUHBshff33m+IXgY6lE6oYttg=
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb/go.mod h1:fSQ9asCJK/lIGXUnoRwJNMXcnzel53AsZMCtlVBIn3k=
github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
github.com/Binject/debug v0.0.0-20210312092933-6277045c2fdf/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a h1:4c0nc0krv8eh7gD809n+swLaCuFyHpxdrxwx0ZmHvBw=
github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 h1:T8z8Wz/fqaPPANF8Unv4LjWLoa0TAvsjYAsRpvSkOqs=
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563/go.mod h1:HMbzsKPz1sF7H4Hmeovh+d2PH2iKPYpAB4XMOVz8wmM=
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0 h1:1sdnMvqxxlMXsu8ku/Xrh6gi3V40ykksst5fd0lfWnc=
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0/go.mod h1:J3XDRlam5pPYca3i6EqgQ35GCCEoyxafpCbLkta0ozc=
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=
@@ -116,14 +111,8 @@ github.com/atc0005/go-teams-notify/v2 v2.14.0 h1:7N+xw+COnYANLREaAveQ65rsNQ12nIZ
github.com/atc0005/go-teams-notify/v2 v2.14.0/go.mod h1:EECsWM2b0Hvoz7O+QdlsvyN2KCUOFQCGj8bUBXv3A3Q=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 h1:JjwxKkxzcBk4k8147g0eBQRCIy0UN1Be8AAv6RaIj4Q=
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0/go.mod h1:IbERvuyb387Hppp8hX0SQTFt/mkej8+OhuS8L0nC2CI=
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 h1:cIAK2NNf2yafdgpFRNJrgZMwvy61BEVpGoHc2n4/yWs=
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 +175,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/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/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-20260811164956-006e29f97886 h1:rdnVWKgJpTVXKuKuJyxDJ+NFJdUaUqGvyGy61OcvlbA=
github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886/go.mod h1:nAw0d9PhFp1qdzi2xhQU5YOu5sVpDIHWlaW2Uz/bCro=
github.com/charmbracelet/x/ansi v0.11.8 h1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ=
github.com/charmbracelet/x/ansi v0.11.8/go.mod h1:ZNN+3mXny/516oTQPLMPIBeSINvNJJQ8uQXDgbeJxY0=
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 +197,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,12 +263,12 @@ 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=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY=
github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -321,8 +308,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 +341,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 +349,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,15 +450,15 @@ 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=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jedib0t/go-pretty/v6 v6.7.8 h1:BVYrDy5DPBA3Qn9ICT+PokP9cvCv1KaHv2i+Hc8sr5o=
github.com/jedib0t/go-pretty/v6 v6.7.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/jedib0t/go-pretty/v6 v6.8.3 h1:yVSk5aemoYHCvcrtqyXklwqcgHQIQzmy/oUzFlmffSQ=
github.com/jedib0t/go-pretty/v6 v6.8.3/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
github.com/jellydator/ttlcache/v3 v3.1.0 h1:0gPFG0IHHP6xyUyXq+JaD8fwkDCqgqwohXNJBcYE71g=
github.com/jellydator/ttlcache/v3 v3.1.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
@@ -494,14 +477,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 +516,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.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=
github.com/lucasb-eyer/go-colorful v1.4.1/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 +526,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 +538,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.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
github.com/mattn/go-runewidth v0.0.24/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 +571,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 +616,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 +667,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.6 h1:iW492LhJ6yeG3u647mJgyhdTbjrZZNnw8bks4UxIL1s=
github.com/sliverarmory/reflektor v0.0.6/go.mod h1:+lCyQy0xHVqx6D5nYbcz5XIDVtAGbDMIMU0Yc6Brb1s=
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 +801,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 +839,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 +859,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 +882,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 +900,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=
@@ -957,7 +922,6 @@ golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -975,8 +939,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.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.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 +950,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 +965,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 +983,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 +997,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 +1053,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 +1069,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=
+10 -13
View File
@@ -1,35 +1,32 @@
module github.com/bishopfox/sliver
go 1.24.0
go 1.26.6
require (
filippo.io/age v1.1.1
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0
github.com/Ne0nd0g/go-clr v1.0.3
github.com/ebitengine/purego v0.10.1
github.com/gofrs/uuid v4.0.0+incompatible
github.com/hashicorp/yamux v0.1.2
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.6
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
)
require (
github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2 // indirect
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 // indirect
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
github.com/google/btree v1.1.2 // indirect
@@ -37,11 +34,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
+20 -28
View File
@@ -1,22 +1,16 @@
filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg=
filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE=
github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2 h1:8kQNJC9AxAaNs0JkXnWUfbNeDnIO1QLYYWjYqC6JEE4=
github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0 h1:1sdnMvqxxlMXsu8ku/Xrh6gi3V40ykksst5fd0lfWnc=
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0/go.mod h1:J3XDRlam5pPYca3i6EqgQ35GCCEoyxafpCbLkta0ozc=
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr2F7h1sriovOZ8BMhca2Rg85c2nk=
github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/Ne0nd0g/go-clr v1.0.3 h1:xt92wwuqY23ZSC7RuHD3mKu3K22Bk5NNbxI803vojK4=
github.com/Ne0nd0g/go-clr v1.0.3/go.mod h1:TKYSQ/5xT25EvBUttAlUrzpR8yHuI0qTRK495I5xG/I=
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 h1:JjwxKkxzcBk4k8147g0eBQRCIy0UN1Be8AAv6RaIj4Q=
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0/go.mod h1:IbERvuyb387Hppp8hX0SQTFt/mkej8+OhuS8L0nC2CI=
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 h1:cIAK2NNf2yafdgpFRNJrgZMwvy61BEVpGoHc2n4/yWs=
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4/go.mod h1:SalMPBCab3yuID8nIhLfzwoBV+lBRyaC7NhuN8qL8xE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY=
github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
@@ -39,16 +33,15 @@ 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.6 h1:iW492LhJ6yeG3u647mJgyhdTbjrZZNnw8bks4UxIL1s=
github.com/sliverarmory/reflektor v0.0.6/go.mod h1:+lCyQy0xHVqx6D5nYbcz5XIDVtAGbDMIMU0Yc6Brb1s=
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=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tetratelabs/wazero v1.7.1 h1:QtSfd6KLc41DIMpDYlJdoMc6k7QTN246DM2+n2Y/Dx8=
@@ -61,51 +54,50 @@ 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=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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
+6 -4
View File
@@ -2,13 +2,15 @@
## Overview
Extension host runtime allowing implants to load optional capabilities. Manages sandboxing, lifecycle, and communication with extension modules. Runtime components handle extension darwin, extension windows, memfs, and WASM for implant-side extension features.
Extension host runtime allowing implants to load optional capabilities. Manages native shared-library and WASM extension lifecycle, registration, and communication across supported implant platforms.
## Go Files
- `extension.go` – Core extension host that loads modules and brokers RPC communication.
- `extension_darwin.go` – macOS-specific stubs and build tags for extension support.
- `extension_windows.go` – Windows-specific integration for loading and managing extensions.
- `extension.go` – Thread-safe native extension registry and single-flight loading lifecycle.
- `extension_darwin.go` – macOS constructor for the shared Reflektor-backed Unix loader.
- `extension_linux.go` – Linux constructor for the shared Reflektor-backed Unix loader.
- `extension_unix.go` – CGO-free macOS/Linux loading, export invocation, and callback bridge.
- `extension_windows.go` – Windows-specific Reflektor integration for loading and invoking extensions.
- `memfs.go` – Implements the in-memory filesystem backing extension assets.
- `memfs_test.go` *(tests)* – Tests the in-memory filesystem behavior for extensions.
- `wasm.go` – Sets up the WASM runtime for implant extensions and handles module lifecycle.
+63 -2
View File
@@ -20,13 +20,31 @@ package extension
import (
"errors"
"sync"
// {{if .Config.Debug}}
"log"
// {{end}}
)
var extensions map[string]Extension
const (
// Success indicates that a native extension callback completed successfully.
Success = 0
// Failure indicates that a native extension callback failed.
Failure = 1
)
var (
extensions map[string]Extension
extensionLoads map[string]*extensionLoad
extensionsMu sync.RWMutex
)
type extensionLoad struct {
done chan struct{}
err error
waiters int
}
type Extension interface {
Load() error
@@ -36,10 +54,47 @@ type Extension interface {
}
func Add(e Extension) {
extensionsMu.Lock()
defer extensionsMu.Unlock()
extensions[e.GetID()] = e
}
// Register loads an extension once per ID and publishes it only after loading
// succeeds. Concurrent registrations for the same ID wait for the in-flight
// load instead of mapping duplicate libraries and allocating duplicate callback
// trampolines.
func Register(e Extension) error {
id := e.GetID()
extensionsMu.Lock()
if _, found := extensions[id]; found {
extensionsMu.Unlock()
return nil
}
if loading, found := extensionLoads[id]; found {
loading.waiters++
extensionsMu.Unlock()
<-loading.done
return loading.err
}
loading := &extensionLoad{done: make(chan struct{})}
extensionLoads[id] = loading
extensionsMu.Unlock()
err := e.Load()
extensionsMu.Lock()
if err == nil {
extensions[id] = e
}
loading.err = err
close(loading.done)
delete(extensionLoads, id)
extensionsMu.Unlock()
return err
}
func List() []string {
extensionsMu.RLock()
defer extensionsMu.RUnlock()
var extList []string
for id := range extensions {
extList = append(extList, id)
@@ -48,10 +103,15 @@ func List() []string {
}
func Run(extID string, funcName string, arguments []byte, callback func([]byte)) error {
if ext, found := extensions[extID]; found {
extensionsMu.RLock()
ext, found := extensions[extID]
extensionsMu.RUnlock()
if found {
return ext.Call(funcName, arguments, callback)
}
// {{if .Config.Debug}}
extensionsMu.RLock()
defer extensionsMu.RUnlock()
for id, ext := range extensions {
log.Printf("Extension '%s' (%s)", id, ext.GetArch())
}
@@ -61,4 +121,5 @@ func Run(extID string, funcName string, arguments []byte, callback func([]byte))
func init() {
extensions = make(map[string]Extension)
extensionLoads = make(map[string]*extensionLoad)
}
+4 -107
View File
@@ -18,113 +18,10 @@ package extension
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"bytes"
"runtime"
"sync"
"unsafe"
// {{if .Config.Debug}}
"log"
// {{end}}
"github.com/Binject/universal"
)
type DarwinExtension struct {
id string
data []byte
module *universal.Library
arch string
serverStore bool
init string
sync.Mutex
}
type extensionArguments struct {
inDataSize uintptr
inDataBuff uintptr
outDataSize uintptr
outDataBuff uintptr
}
// DarwinExtension is a Reflektor-backed native macOS extension.
type DarwinExtension = UnixExtension
// NewDarwinExtension creates a native macOS extension backed by Reflektor.
func NewDarwinExtension(data []byte, id string, arch string, init string) *DarwinExtension {
return &DarwinExtension{
id: id,
data: data,
arch: arch,
init: init,
}
}
func (d *DarwinExtension) GetID() string {
return d.id
}
func (d *DarwinExtension) GetArch() string {
return d.arch
}
func (d *DarwinExtension) Load() error {
var err error
d.Lock()
defer d.Unlock()
loader, err := universal.NewLoader()
if err != nil {
return err
}
d.module, err = loader.LoadLibrary(d.id, &d.data)
if err != nil {
return err
}
if d.init != "" {
if _, errInit := d.module.Call(d.init); errInit != nil {
return errInit
}
}
return nil
}
func (d *DarwinExtension) Call(export string, arguments []byte, onFinish func([]byte)) error {
// We currently have 2 issues with Darwin extensions:
// - cppgo (used by universal) fucks up when calling a function with more than 1 argument
// - we don't have Go callback support for the loaded extension,
// so we have to wait for the call to finish to get the results
// To circumvent these issues, we pass the extensionArguments structure
// as the only argument to the call, so we can pass args in and extract
// the result at the same time.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
extArgs := extensionArguments{}
if len(arguments) > 0 {
extArgs.inDataBuff = uintptr(unsafe.Pointer(&arguments[0]))
extArgs.inDataSize = uintptr(uint64(len(arguments)))
}
// {{if .Config.Debug}}
log.Printf("Calling %s, arg size: %d\n", export, extArgs.inDataSize)
// {{end}}
d.Lock()
defer d.Unlock()
_, err := d.module.Call(export, uintptr(unsafe.Pointer(&extArgs)))
if err != nil {
return err
}
// {{if .Config.Debug}}
log.Printf("%s done!\n", export)
// {{end}}
outData := new(bytes.Buffer)
outDataSize := int(extArgs.outDataSize)
// {{if .Config.Debug}}
log.Printf("Out data size: %d\n", outDataSize)
// {{end}}
for i := 0; i < outDataSize; i++ {
b := (*byte)(unsafe.Pointer(uintptr(i) + extArgs.outDataBuff))
outData.WriteByte(*b)
}
// We currently don't have a way to trigger a callback
// in the loaded code for Darwin.
if outData.Len() > 0 {
onFinish(outData.Bytes())
}
return nil
return newUnixExtension(data, id, arch, init)
}
@@ -0,0 +1,29 @@
//go:build 386 || amd64 || arm64
package extension
/*
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/>.
*/
// LinuxExtension is a Reflektor-backed native Linux extension.
type LinuxExtension = UnixExtension
// NewLinuxExtension creates a native Linux extension backed by Reflektor.
func NewLinuxExtension(data []byte, id string, arch string, init string) *LinuxExtension {
return newUnixExtension(data, id, arch, init)
}
@@ -0,0 +1,55 @@
//go:build !(386 || amd64 || arm64)
package extension
/*
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 "errors"
// LinuxExtension reports unsupported native-extension operations on Linux
// architectures without a Reflektor backend.
type LinuxExtension struct {
id string
arch string
}
// NewLinuxExtension creates an unsupported-architecture extension placeholder.
func NewLinuxExtension(_ []byte, id string, arch string, _ string) *LinuxExtension {
return &LinuxExtension{id: id, arch: arch}
}
// GetID returns the extension identifier.
func (l *LinuxExtension) GetID() string {
return l.id
}
// GetArch returns the extension architecture.
func (l *LinuxExtension) GetArch() string {
return l.arch
}
// Load reports that native extensions are unsupported on this architecture.
func (l *LinuxExtension) Load() error {
return errors.New("{{if .Config.Debug}} native extensions are not supported on this Linux architecture {{end}}")
}
// Call reports that native extensions are unsupported on this architecture.
func (l *LinuxExtension) Call(_ string, _ []byte, _ func([]byte)) error {
return errors.New("{{if .Config.Debug}} native extensions are not supported on this Linux architecture {{end}}")
}
+236
View File
@@ -0,0 +1,236 @@
package extension
import (
"errors"
"fmt"
"runtime"
"sync"
"sync/atomic"
"testing"
"time"
)
type testExtension struct {
id string
}
func (e *testExtension) Load() error {
return nil
}
func (e *testExtension) Call(_ string, arguments []byte, callback func([]byte)) error {
callback(arguments)
return nil
}
func (e *testExtension) GetID() string {
return e.id
}
func (e *testExtension) GetArch() string {
return "test"
}
type registeringTestExtension struct {
testExtension
loadCalls *atomic.Int32
loadErr error
started chan struct{}
release chan struct{}
}
func (e *registeringTestExtension) Load() error {
e.loadCalls.Add(1)
if e.started != nil {
close(e.started)
}
if e.release != nil {
<-e.release
}
return e.loadErr
}
func resetExtensionRegistry(t *testing.T) {
t.Helper()
extensionsMu.Lock()
previousExtensions := extensions
previousLoads := extensionLoads
extensions = make(map[string]Extension)
extensionLoads = make(map[string]*extensionLoad)
extensionsMu.Unlock()
t.Cleanup(func() {
extensionsMu.Lock()
extensions = previousExtensions
extensionLoads = previousLoads
extensionsMu.Unlock()
})
}
func waitForExtensionWaiters(t *testing.T, extensionID string, want int) {
t.Helper()
deadline := time.Now().Add(time.Second)
for {
extensionsMu.RLock()
loading := extensionLoads[extensionID]
got := 0
if loading != nil {
got = loading.waiters
}
extensionsMu.RUnlock()
if got == want {
return
}
if time.Now().After(deadline) {
t.Fatalf("registration waiters = %d, want %d", got, want)
}
runtime.Gosched()
}
}
func TestExtensionRegistryConcurrentAccess(t *testing.T) {
resetExtensionRegistry(t)
const count = 64
errCh := make(chan error, count)
var waitGroup sync.WaitGroup
for index := 0; index < count; index++ {
index := index
waitGroup.Add(1)
go func() {
defer waitGroup.Done()
id := fmt.Sprintf("extension-%d", index)
Add(&testExtension{id: id})
argument := []byte(id)
if err := Run(id, "run", argument, func(output []byte) {
if string(output) != id {
errCh <- fmt.Errorf("callback output = %q, want %q", output, id)
}
}); err != nil {
errCh <- err
}
_ = List()
}()
}
waitGroup.Wait()
close(errCh)
for err := range errCh {
t.Error(err)
}
if got := len(List()); got != count {
t.Fatalf("List returned %d extensions, want %d", got, count)
}
}
func TestRegisterLoadsExtensionOnlyOnce(t *testing.T) {
resetExtensionRegistry(t)
const extensionID = "duplicate-extension"
var loadCalls atomic.Int32
started := make(chan struct{})
release := make(chan struct{})
first := &registeringTestExtension{
testExtension: testExtension{id: extensionID},
loadCalls: &loadCalls,
started: started,
release: release,
}
const registrations = 16
errorsCh := make(chan error, registrations)
go func() {
errorsCh <- Register(first)
}()
<-started
for index := 1; index < registrations; index++ {
go func() {
errorsCh <- Register(&registeringTestExtension{
testExtension: testExtension{id: extensionID},
loadCalls: &loadCalls,
})
}()
}
waitForExtensionWaiters(t, extensionID, registrations-1)
select {
case err := <-errorsCh:
t.Fatalf("Register returned before the in-flight load completed: %v", err)
default:
}
close(release)
for index := 0; index < registrations; index++ {
if err := <-errorsCh; err != nil {
t.Fatalf("Register returned an error: %v", err)
}
}
if got := loadCalls.Load(); got != 1 {
t.Fatalf("Load called %d times, want 1", got)
}
if got := List(); len(got) != 1 || got[0] != extensionID {
t.Fatalf("List returned %q, want [%q]", got, extensionID)
}
if err := Register(&registeringTestExtension{
testExtension: testExtension{id: extensionID},
loadCalls: &loadCalls,
}); err != nil {
t.Fatalf("repeat Register returned an error: %v", err)
}
if got := loadCalls.Load(); got != 1 {
t.Fatalf("repeat Register called Load; total calls = %d, want 1", got)
}
}
func TestRegisterRetriesAfterFailedLoad(t *testing.T) {
resetExtensionRegistry(t)
const extensionID = "retry-extension"
var loadCalls atomic.Int32
wantErr := errors.New("load failed")
started := make(chan struct{})
release := make(chan struct{})
const registrations = 8
errorsCh := make(chan error, registrations)
go func() {
errorsCh <- Register(&registeringTestExtension{
testExtension: testExtension{id: extensionID},
loadCalls: &loadCalls,
loadErr: wantErr,
started: started,
release: release,
})
}()
<-started
for index := 1; index < registrations; index++ {
go func() {
errorsCh <- Register(&registeringTestExtension{
testExtension: testExtension{id: extensionID},
loadCalls: &loadCalls,
loadErr: errors.New("unexpected follower load"),
})
}()
}
waitForExtensionWaiters(t, extensionID, registrations-1)
close(release)
for index := 0; index < registrations; index++ {
if err := <-errorsCh; !errors.Is(err, wantErr) {
t.Fatalf("Register error = %v, want shared error %v", err, wantErr)
}
}
if got := loadCalls.Load(); got != 1 {
t.Fatalf("failed Load called %d times, want 1", got)
}
if got := List(); len(got) != 0 {
t.Fatalf("failed extension was published: %q", got)
}
if err := Register(&registeringTestExtension{
testExtension: testExtension{id: extensionID},
loadCalls: &loadCalls,
}); err != nil {
t.Fatalf("retry Register returned an error: %v", err)
}
if got := loadCalls.Load(); got != 2 {
t.Fatalf("Load called %d times, want 2", got)
}
}
+174
View File
@@ -0,0 +1,174 @@
//go:build (darwin && (amd64 || arm64)) || (linux && (386 || amd64 || arm64))
package extension
/*
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 (
"errors"
"runtime"
"sync"
"sync/atomic"
"unsafe"
// {{if .Config.Debug}}
"log"
// {{end}}
"github.com/ebitengine/purego"
reflektor "github.com/sliverarmory/reflektor/native"
)
type extensionCall struct {
onFinish func([]byte)
}
// UnixExtension is a Reflektor-backed native extension for supported Unix
// targets.
type UnixExtension struct {
id string
data []byte
module *reflektor.Library
arch string
init string
callback uintptr
active atomic.Pointer[extensionCall]
sync.Mutex
}
func newUnixExtension(data []byte, id string, arch string, init string) *UnixExtension {
return &UnixExtension{
id: id,
data: data,
arch: arch,
init: init,
}
}
// GetID returns the extension identifier.
func (u *UnixExtension) GetID() string {
return u.id
}
// GetArch returns the extension architecture.
func (u *UnixExtension) GetArch() string {
return u.arch
}
// Load maps the extension and invokes its optional initialization export.
func (u *UnixExtension) Load() error {
if len(u.data) == 0 {
return errors.New("{{if .Config.Debug}} extension data is empty {{end}}")
}
u.Lock()
defer u.Unlock()
if u.module != nil {
return errors.New("{{if .Config.Debug}} module already loaded {{end}}")
}
module, err := reflektor.LoadLibrary(u.data)
if err != nil {
return err
}
if u.init != "" {
// {{if .Config.Debug}}
log.Printf("Calling %s\n", u.init)
// {{end}}
if err := module.CallExport(u.init); err != nil {
_ = module.Close()
return err
}
}
callback, err := newUnixExtensionCallback(u)
if err != nil {
_ = module.Close()
return err
}
u.callback = callback
u.module = module
return nil
}
// Call invokes an extension export with the native extension callback ABI.
func (u *UnixExtension) Call(export string, arguments []byte, onFinish func([]byte)) error {
u.Lock()
defer u.Unlock()
if u.module == nil {
return errors.New("{{if .Config.Debug}} module not loaded {{end}}")
}
if uint64(len(arguments)) > uint64(^uint32(0)) {
return errors.New("{{if .Config.Debug}} extension arguments exceed uint32 size {{end}}")
}
var argumentsPtr uintptr
if len(arguments) > 0 {
argumentsPtr = uintptr(unsafe.Pointer(&arguments[0]))
}
argumentsSize := uintptr(uint32(len(arguments)))
call := &extensionCall{onFinish: onFinish}
u.active.Store(call)
defer u.active.Store(nil)
// {{if .Config.Debug}}
log.Printf("Calling %s, arguments addr: 0x%08x, args size: %08x\n", export, argumentsPtr, argumentsSize)
// {{end}}
_, err := u.module.CallExportWithArgs(export, argumentsPtr, argumentsSize, u.callback)
runtime.KeepAlive(arguments)
runtime.KeepAlive(call)
return err
}
func newUnixExtensionCallback(extension *UnixExtension) (callback uintptr, err error) {
defer func() {
if recovered := recover(); recovered != nil {
// {{if .Config.Debug}}
log.Printf("Failed to create extension callback: %v\n", recovered)
// {{end}}
callback = 0
err = errors.New("{{if .Config.Debug}} failed to create extension callback {{end}}")
}
}()
return purego.NewCallback(extension.extensionCallback), nil
}
func (u *UnixExtension) extensionCallback(data unsafe.Pointer, dataLen int32) (result int32) {
result = Failure
defer func() {
if recover() != nil {
result = Failure
}
}()
call := u.active.Load()
if call == nil || call.onFinish == nil {
return Failure
}
if dataLen == 0 {
return Success
}
if data == nil || dataLen < 0 {
return Failure
}
output := append([]byte(nil), unsafe.Slice((*byte)(data), int(dataLen))...)
call.onFinish(output)
return Success
}
@@ -0,0 +1,59 @@
//go:build (darwin && (amd64 || arm64)) || (linux && (386 || amd64 || arm64))
package extension
import (
"bytes"
"testing"
"unsafe"
)
func TestUnixExtensionCallbackCopiesOutput(t *testing.T) {
extension := &UnixExtension{}
var got []byte
call := &extensionCall{onFinish: func(output []byte) {
got = output
}}
extension.active.Store(call)
defer extension.active.Store(nil)
source := []byte("extension output")
result := extension.extensionCallback(unsafe.Pointer(&source[0]), int32(len(source)))
if result != Success {
t.Fatalf("extension callback returned %d, want %d", result, Success)
}
source[0] = 'X'
if !bytes.Equal(got, []byte("extension output")) {
t.Fatalf("extension callback output = %q, want an independent copy", got)
}
}
func TestUnixExtensionCallbackRejectsInvalidCalls(t *testing.T) {
extension := &UnixExtension{}
if result := extension.extensionCallback(nil, 1); result != Failure {
t.Fatalf("callback without an active call returned %d, want %d", result, Failure)
}
extension.active.Store(&extensionCall{onFinish: func([]byte) {}})
defer extension.active.Store(nil)
if result := extension.extensionCallback(nil, 1); result != Failure {
t.Fatalf("callback with a nil data pointer returned %d, want %d", result, Failure)
}
source := byte(0)
if result := extension.extensionCallback(unsafe.Pointer(&source), -1); result != Failure {
t.Fatalf("callback with a negative length returned %d, want %d", result, Failure)
}
}
func TestUnixExtensionCallbackContainsPanics(t *testing.T) {
extension := &UnixExtension{}
extension.active.Store(&extensionCall{onFinish: func([]byte) {
panic("callback failed")
}})
defer extension.active.Store(nil)
source := []byte("output")
if result := extension.extensionCallback(unsafe.Pointer(&source[0]), int32(len(source))); result != Failure {
t.Fatalf("panicking callback returned %d, want %d", result, Failure)
}
}
@@ -28,18 +28,13 @@ import (
"log"
// {{end}}
"github.com/moloch--/memmod"
)
const (
Success = 0
Failure = 1
reflektor "github.com/sliverarmory/reflektor/memmod"
)
type WindowsExtension struct {
id string
data []byte
module *memmod.Module
module *reflektor.Module
arch string
init string
sync.Mutex
@@ -73,7 +68,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
}
+2 -1
View File
@@ -2,11 +2,12 @@
## Overview
Runtime message handlers that react to server instructions. Dispatches inbound RPC messages to feature-specific executors. Runtime components handle extensions WASM, handlers wireguard, handlers darwin, and handlers generic for implant-side handlers features.
Runtime message handlers that react to server instructions. Dispatches inbound RPC messages to feature-specific executors, including native and WASM extension lifecycle requests.
## Go Files
- `extensions-wasm.go` – Routes extension-related RPC messages to the WASM runtime.
- `handlers_extensions.go` – Shared Windows, macOS, and Linux native extension registration, invocation, and listing handlers.
- `handlers-wireguard.go` – Handles WireGuard control messages and state updates.
- `handlers.go` – Registers core handler functions and shared dispatch utilities.
- `handlers_darwin.go` – macOS-specific handler bindings and feature toggles.
+2 -63
View File
@@ -92,69 +92,8 @@ func GetSystemHandlers() map[uint32]RPCHandler {
// Extensions
func registerExtensionHandler(data []byte, resp RPCResponse) {
registerReq := &pb.RegisterExtensionReq{}
err := proto.Unmarshal(data, registerReq)
if err != nil {
return
}
ext := extension.NewDarwinExtension(registerReq.Data, registerReq.Name, registerReq.OS, registerReq.Init)
extension.Add(ext)
err = ext.Load()
registerResp := &pb.RegisterExtension{
Response: &commonpb.Response{},
}
if err != nil {
registerResp.Response.Err = err.Error()
}
data, err = proto.Marshal(registerResp)
resp(data, err)
}
func callExtensionHandler(data []byte, resp RPCResponse) {
callReq := &pb.CallExtensionReq{}
err := proto.Unmarshal(data, callReq)
if err != nil {
return
}
callResp := &pb.CallExtension{
Response: &commonpb.Response{},
}
gotOutput := false
err = extension.Run(callReq.Name, callReq.Export, callReq.Args, func(out []byte) {
gotOutput = true
callResp.Output = out
data, err = proto.Marshal(callResp)
resp(data, err)
})
// Only send back synchronously if there was an error
if err != nil || !gotOutput {
if err != nil {
callResp.Response.Err = err.Error()
}
data, err = proto.Marshal(callResp)
resp(data, err)
}
}
func listExtensionsHandler(data []byte, resp RPCResponse) {
lstReq := &pb.ListExtensionsReq{}
err := proto.Unmarshal(data, lstReq)
if err != nil {
return
}
exts := extension.List()
lstResp := &pb.ListExtensions{
Response: &commonpb.Response{},
Names: exts,
}
data, err = proto.Marshal(lstResp)
resp(data, err)
func newExtension(data []byte, id string, arch string, init string) extension.Extension {
return extension.NewDarwinExtension(data, id, arch, init)
}
func mountHandler(data []byte, resp RPCResponse) {
@@ -0,0 +1,88 @@
//go:build linux || darwin || windows
package handlers
/*
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 (
"github.com/bishopfox/sliver/implant/sliver/extension"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
)
type extensionFactory func(data []byte, id string, arch string, init string) extension.Extension
func registerExtensionHandler(data []byte, resp RPCResponse) {
registerExtension(data, resp, newExtension)
}
func registerExtension(data []byte, resp RPCResponse, factory extensionFactory) {
registerReq := &sliverpb.RegisterExtensionReq{}
if err := proto.Unmarshal(data, registerReq); err != nil {
return
}
ext := factory(registerReq.Data, registerReq.Name, registerReq.OS, registerReq.Init)
err := extension.Register(ext)
registerResp := &sliverpb.RegisterExtension{Response: &commonpb.Response{}}
if err != nil {
registerResp.Response.Err = err.Error()
}
data, err = proto.Marshal(registerResp)
resp(data, err)
}
func callExtensionHandler(data []byte, resp RPCResponse) {
callReq := &sliverpb.CallExtensionReq{}
if err := proto.Unmarshal(data, callReq); err != nil {
return
}
callResp := &sliverpb.CallExtension{Response: &commonpb.Response{}}
gotOutput := false
err := extension.Run(callReq.Name, callReq.Export, callReq.Args, func(out []byte) {
gotOutput = true
callResp.Output = out
data, err := proto.Marshal(callResp)
resp(data, err)
})
// Only send back synchronously if there was an error or no callback output.
if err != nil || !gotOutput {
if err != nil {
callResp.Response.Err = err.Error()
}
data, err = proto.Marshal(callResp)
resp(data, err)
}
}
func listExtensionsHandler(data []byte, resp RPCResponse) {
lstReq := &sliverpb.ListExtensionsReq{}
if err := proto.Unmarshal(data, lstReq); err != nil {
return
}
lstResp := &sliverpb.ListExtensions{
Response: &commonpb.Response{},
Names: extension.List(),
}
data, err := proto.Marshal(lstResp)
resp(data, err)
}
@@ -0,0 +1,86 @@
//go:build linux || darwin || windows
package handlers
import (
"errors"
"slices"
"testing"
"github.com/bishopfox/sliver/implant/sliver/extension"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
)
type failingExtension struct {
id string
loadErr error
}
func (f *failingExtension) Load() error {
return f.loadErr
}
func (f *failingExtension) Call(string, []byte, func([]byte)) error {
return nil
}
func (f *failingExtension) GetID() string {
return f.id
}
func (f *failingExtension) GetArch() string {
return "test"
}
func TestRegisterExtensionDoesNotAddFailedLoad(t *testing.T) {
const extensionID = "handler-test-failed-load"
wantErr := errors.New("load failed")
request, err := proto.Marshal(&sliverpb.RegisterExtensionReq{Name: extensionID})
if err != nil {
t.Fatalf("marshal request: %v", err)
}
var (
responseData []byte
responseErr error
responses int
)
registerExtension(request, func(data []byte, err error) {
responses++
responseData = data
responseErr = err
}, func(_ []byte, id string, _, _ string) extension.Extension {
return &failingExtension{id: id, loadErr: wantErr}
})
if responseErr != nil {
t.Fatalf("response callback returned error: %v", responseErr)
}
if responses != 1 {
t.Fatalf("got %d responses, want 1", responses)
}
response := &sliverpb.RegisterExtension{}
if err := proto.Unmarshal(responseData, response); err != nil {
t.Fatalf("unmarshal response: %v", err)
}
if response.GetResponse().GetErr() != wantErr.Error() {
t.Fatalf("got response error %q, want %q", response.GetResponse().GetErr(), wantErr)
}
if slices.Contains(extension.List(), extensionID) {
t.Fatalf("extension %q was registered after its load failed", extensionID)
}
}
func TestSystemHandlersIncludeExtensions(t *testing.T) {
handlers := GetSystemHandlers()
for _, messageType := range []uint32{
sliverpb.MsgRegisterExtensionReq,
sliverpb.MsgCallExtensionReq,
sliverpb.MsgListExtensionsReq,
} {
if handlers[messageType] == nil {
t.Errorf("handler for message type %d is not registered", messageType)
}
}
}
+10
View File
@@ -31,6 +31,7 @@ import (
"time"
"unsafe"
"github.com/bishopfox/sliver/implant/sliver/extension"
"github.com/bishopfox/sliver/implant/sliver/mount"
"github.com/bishopfox/sliver/implant/sliver/procdump"
"github.com/bishopfox/sliver/implant/sliver/taskrunner"
@@ -76,6 +77,11 @@ var (
sliverpb.MsgMountReq: mountHandler,
sliverpb.MsgGrepReq: grepHandler,
// Extensions
sliverpb.MsgRegisterExtensionReq: registerExtensionHandler,
sliverpb.MsgCallExtensionReq: callExtensionHandler,
sliverpb.MsgListExtensionsReq: listExtensionsHandler,
// Wasm Extensions - Note that execution can be done via a tunnel handler
sliverpb.MsgRegisterWasmExtensionReq: registerWasmExtensionHandler,
sliverpb.MsgDeregisterWasmExtensionReq: deregisterWasmExtensionHandler,
@@ -107,6 +113,10 @@ func GetSystemHandlers() map[uint32]RPCHandler {
return linuxHandlers
}
func newExtension(data []byte, id string, arch string, init string) extension.Extension {
return extension.NewLinuxExtension(data, id, arch, init)
}
func dumpHandler(data []byte, resp RPCResponse) {
procDumpReq := &sliverpb.ProcessDumpReq{}
err := proto.Unmarshal(data, procDumpReq)
+2 -57
View File
@@ -912,63 +912,8 @@ func mountHandler(data []byte, resp RPCResponse) {
// Extensions
func registerExtensionHandler(data []byte, resp RPCResponse) {
registerReq := &sliverpb.RegisterExtensionReq{}
err := proto.Unmarshal(data, registerReq)
if err != nil {
return
}
ext := extension.NewWindowsExtension(registerReq.Data, registerReq.Name, registerReq.OS, registerReq.Init)
err = ext.Load()
registerResp := &sliverpb.RegisterExtension{Response: &commonpb.Response{}}
if err != nil {
registerResp.Response.Err = err.Error()
} else {
extension.Add(ext)
}
data, err = proto.Marshal(registerResp)
resp(data, err)
}
func callExtensionHandler(data []byte, resp RPCResponse) {
callReq := &sliverpb.CallExtensionReq{}
err := proto.Unmarshal(data, callReq)
if err != nil {
return
}
callResp := &sliverpb.CallExtension{Response: &commonpb.Response{}}
gotOutput := false
err = extension.Run(callReq.Name, callReq.Export, callReq.Args, func(out []byte) {
gotOutput = true
callResp.Output = out
data, err = proto.Marshal(callResp)
resp(data, err)
})
// Only send back synchronously if there was an error
if err != nil || !gotOutput {
if err != nil {
callResp.Response.Err = err.Error()
}
data, err = proto.Marshal(callResp)
resp(data, err)
}
}
func listExtensionsHandler(data []byte, resp RPCResponse) {
lstReq := &sliverpb.ListExtensionsReq{}
err := proto.Unmarshal(data, lstReq)
if err != nil {
return
}
exts := extension.List()
lstResp := &sliverpb.ListExtensions{
Response: &commonpb.Response{},
Names: exts,
}
data, err = proto.Marshal(lstResp)
resp(data, err)
func newExtension(data []byte, id string, arch string, init string) extension.Extension {
return extension.NewWindowsExtension(data, id, arch, init)
}
// Stub since Windows doesn't support UID
+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
/*
@@ -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)
-27
View File
@@ -1,27 +0,0 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
File diff suppressed because it is too large. Load diff
-32
View File
@@ -1,32 +0,0 @@
package elf
/*
Any symbol in the dynamic symbol table (in .dynsym) for which .st_shndx == SHN_UNDEF
(references undefined section) is an import, and every other symbol is defined and exported.
*/
// Export - describes a single export entry
type Export struct {
Name string
VirtualAddress uint64
}
// Exports - gets exports
func (f *File) Exports() ([]Export, error) {
var exports []Export
symbols, err := f.DynamicSymbols()
if err != nil {
return nil, err
}
for _, s := range symbols {
if s.Section != SHN_UNDEF {
exports = append(exports, Export{
Name: s.Name,
VirtualAddress: s.Value,
})
}
}
return exports, nil
}
-964
View File
@@ -1,964 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package elf implements access to ELF object files.
package elf
import (
"bytes"
"compress/zlib"
"debug/dwarf"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"strings"
)
// seekStart, seekCurrent, seekEnd are copies of
// io.SeekStart, io.SeekCurrent, and io.SeekEnd.
// We can't use the ones from package io because
// we want this code to build with Go 1.4 during
// cmd/dist bootstrap.
const (
seekStart int = 0
seekCurrent int = 1
seekEnd int = 2
)
// TODO: error reporting detail
/*
* Internal ELF representation
*/
// A FileHeader represents an ELF file header.
type FileHeader struct {
Class Class
Data Data
Version Version
OSABI OSABI
ABIVersion uint8
ByteOrder binary.ByteOrder
Type Type
Machine Machine
Entry uint64
SHTOffset int64
ShStrIndex int
}
// A File represents an open ELF file.
type File struct {
FileHeader
Sections []*Section
Progs []*Prog
closer io.Closer
gnuNeed []verneed
gnuVersym []byte
Insertion []byte
InsertionEOF []byte
DynTags []DynTagValue
}
// A SectionHeader represents a single ELF section header.
type SectionHeader struct {
Name string
Type SectionType
Flags SectionFlag
Addr uint64
Offset uint64
Size uint64
Link uint32
Info uint32
Addralign uint64
Entsize uint64
Shnum int // Section Header Number
Shname uint32 // Section Header Name (index)
// FileSize is the size of this section in the file in bytes.
// If a section is compressed, FileSize is the size of the
// compressed data, while Size (above) is the size of the
// uncompressed data.
FileSize uint64
}
// A Section represents a single section in an ELF file.
type Section struct {
SectionHeader
// Embed ReaderAt for ReadAt method.
// Do not embed SectionReader directly
// to avoid having Read and Seek.
// If a client wants Read and Seek it must use
// Open() to avoid fighting over the seek offset
// with other clients.
//
// ReaderAt may be nil if the section is not easily available
// in a random-access form. For example, a compressed section
// may have a nil ReaderAt.
io.ReaderAt
sr *io.SectionReader
compressionType CompressionType
compressionOffset int64
}
// Data reads and returns the contents of the ELF section.
// Even if the section is stored compressed in the ELF file,
// Data returns uncompressed data.
func (s *Section) Data() ([]byte, error) {
dat := make([]byte, s.Size)
n, err := io.ReadFull(s.Open(), dat)
return dat[0:n], err
}
// stringTable reads and returns the string table given by the
// specified link value.
func (f *File) stringTable(link uint32) ([]byte, error) {
if link <= 0 || link >= uint32(len(f.Sections)) {
return nil, errors.New("section has invalid string table link")
}
return f.Sections[link].Data()
}
// Open returns a new ReadSeeker reading the ELF section.
// Even if the section is stored compressed in the ELF file,
// the ReadSeeker reads uncompressed data.
func (s *Section) Open() io.ReadSeeker {
if s.Flags&SHF_COMPRESSED == 0 {
return io.NewSectionReader(s.sr, 0, 1<<63-1)
}
if s.compressionType == COMPRESS_ZLIB {
return &readSeekerFromReader{
reset: func() (io.Reader, error) {
fr := io.NewSectionReader(s.sr, s.compressionOffset, int64(s.FileSize)-s.compressionOffset)
return zlib.NewReader(fr)
},
size: int64(s.Size),
}
}
err := &FormatError{int64(s.Offset), "unknown compression type", s.compressionType}
return errorReader{err}
}
// A ProgHeader represents a single ELF program header.
type ProgHeader struct {
Type ProgType
Flags ProgFlag
Off uint64
Vaddr uint64
Paddr uint64
Filesz uint64
Memsz uint64
Align uint64
}
// A Prog represents a single ELF program header in an ELF binary.
type Prog struct {
ProgHeader
// Embed ReaderAt for ReadAt method.
// Do not embed SectionReader directly
// to avoid having Read and Seek.
// If a client wants Read and Seek it must use
// Open() to avoid fighting over the seek offset
// with other clients.
io.ReaderAt
sr *io.SectionReader
}
// Open returns a new ReadSeeker reading the ELF program body.
func (p *Prog) Open() io.ReadSeeker { return io.NewSectionReader(p.sr, 0, 1<<63-1) }
// A Symbol represents an entry in an ELF symbol table section.
type Symbol struct {
Name string
NameIndex uint32
Info, Other byte
Section SectionIndex
SectIndex uint16
Value, Size uint64
}
// ToSym64 - Convert to a Sym64
func (s Symbol) ToSym64() (retval Sym64) {
retval.Name = s.NameIndex
retval.Info = s.Info
retval.Other = s.Other
retval.Shndx = s.SectIndex
retval.Value = s.Value
retval.Size = s.Size
return
}
// ToSym32 - Convert to a Sym32
func (s Symbol) ToSym32() (retval Sym32) {
retval.Name = s.NameIndex
retval.Info = s.Info
retval.Other = s.Other
retval.Shndx = s.SectIndex
retval.Value = uint32(s.Value)
retval.Size = uint32(s.Size)
return
}
/*
* ELF reader
*/
type FormatError struct {
off int64
msg string
val interface{}
}
func (e *FormatError) Error() string {
msg := e.msg
if e.val != nil {
msg += fmt.Sprintf(" '%v' ", e.val)
}
msg += fmt.Sprintf("in record at byte %#x", e.off)
return msg
}
// Open opens the named file using os.Open and prepares it for use as an ELF binary.
func Open(name string) (*File, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
ff, err := NewFile(f)
if err != nil {
f.Close()
return nil, err
}
ff.closer = f
return ff, nil
}
// Close closes the File.
// If the File was created using NewFile directly instead of Open,
// Close has no effect.
func (f *File) Close() error {
var err error
if f.closer != nil {
err = f.closer.Close()
f.closer = nil
}
return err
}
// SectionByType returns the first section in f with the
// given type, or nil if there is no such section.
func (f *File) SectionByType(typ SectionType) *Section {
for _, s := range f.Sections {
if s.Type == typ {
return s
}
}
return nil
}
// SectionByName returns the first section in f with the
// given name, or nil if there is no such section.
func (f *File) SectionByName(name string) *Section {
for _, s := range f.Sections {
if s.Name == name {
return s
}
}
return nil
}
// NewFile creates a new File for accessing an ELF binary in an underlying reader.
// The ELF binary is expected to start at position 0 in the ReaderAt.
func NewFile(r io.ReaderAt) (*File, error) {
sr := io.NewSectionReader(r, 0, 1<<63-1)
// Read and decode ELF identifier
var ident [16]uint8
if _, err := r.ReadAt(ident[0:], 0); err != nil {
return nil, err
}
if ident[0] != '\x7f' || ident[1] != 'E' || ident[2] != 'L' || ident[3] != 'F' {
return nil, &FormatError{0, "bad magic number", ident[0:4]}
}
f := new(File)
f.Class = Class(ident[EI_CLASS])
switch f.Class {
case ELFCLASS32:
case ELFCLASS64:
// ok
default:
return nil, &FormatError{0, "unknown ELF class", f.Class}
}
f.Data = Data(ident[EI_DATA])
switch f.Data {
case ELFDATA2LSB:
f.ByteOrder = binary.LittleEndian
case ELFDATA2MSB:
f.ByteOrder = binary.BigEndian
default:
return nil, &FormatError{0, "unknown ELF data encoding", f.Data}
}
f.Version = Version(ident[EI_VERSION])
if f.Version != EV_CURRENT {
return nil, &FormatError{0, "unknown ELF version", f.Version}
}
f.OSABI = OSABI(ident[EI_OSABI])
f.ABIVersion = ident[EI_ABIVERSION]
// Read ELF file header
var phoff int64
var phentsize, phnum int
var shentsize, shnum int
f.ShStrIndex = -1
switch f.Class {
case ELFCLASS32:
hdr := new(Header32)
sr.Seek(0, seekStart)
if err := binary.Read(sr, f.ByteOrder, hdr); err != nil {
return nil, err
}
f.Type = Type(hdr.Type)
f.Machine = Machine(hdr.Machine)
f.Entry = uint64(hdr.Entry)
if v := Version(hdr.Version); v != f.Version {
return nil, &FormatError{0, "mismatched ELF version", v}
}
phoff = int64(hdr.Phoff)
phentsize = int(hdr.Phentsize)
phnum = int(hdr.Phnum)
f.SHTOffset = int64(hdr.Shoff)
shentsize = int(hdr.Shentsize)
shnum = int(hdr.Shnum)
f.ShStrIndex = int(hdr.Shstrndx)
case ELFCLASS64:
hdr := new(Header64)
sr.Seek(0, seekStart)
if err := binary.Read(sr, f.ByteOrder, hdr); err != nil {
return nil, err
}
f.Type = Type(hdr.Type)
f.Machine = Machine(hdr.Machine)
f.Entry = hdr.Entry
if v := Version(hdr.Version); v != f.Version {
return nil, &FormatError{0, "mismatched ELF version", v}
}
phoff = int64(hdr.Phoff)
phentsize = int(hdr.Phentsize)
phnum = int(hdr.Phnum)
f.SHTOffset = int64(hdr.Shoff)
shentsize = int(hdr.Shentsize)
shnum = int(hdr.Shnum)
f.ShStrIndex = int(hdr.Shstrndx)
}
if shnum > 0 && f.SHTOffset > 0 && (f.ShStrIndex < 0 || f.ShStrIndex >= shnum) {
return nil, &FormatError{0, "invalid ELF shstrndx", f.ShStrIndex}
}
// Read program headers
f.Progs = make([]*Prog, phnum)
for i := 0; i < phnum; i++ {
off := phoff + int64(i)*int64(phentsize)
sr.Seek(off, seekStart)
p := new(Prog)
switch f.Class {
case ELFCLASS32:
ph := new(Prog32)
if err := binary.Read(sr, f.ByteOrder, ph); err != nil {
return nil, err
}
p.ProgHeader = ProgHeader{
Type: ProgType(ph.Type),
Flags: ProgFlag(ph.Flags),
Off: uint64(ph.Off),
Vaddr: uint64(ph.Vaddr),
Paddr: uint64(ph.Paddr),
Filesz: uint64(ph.Filesz),
Memsz: uint64(ph.Memsz),
Align: uint64(ph.Align),
}
case ELFCLASS64:
ph := new(Prog64)
if err := binary.Read(sr, f.ByteOrder, ph); err != nil {
return nil, err
}
p.ProgHeader = ProgHeader{
Type: ProgType(ph.Type),
Flags: ProgFlag(ph.Flags),
Off: ph.Off,
Vaddr: ph.Vaddr,
Paddr: ph.Paddr,
Filesz: ph.Filesz,
Memsz: ph.Memsz,
Align: ph.Align,
}
}
p.sr = io.NewSectionReader(r, int64(p.Off), int64(p.Filesz))
p.ReaderAt = p.sr
f.Progs[i] = p
}
// Read section headers
f.Sections = make([]*Section, shnum)
names := make([]uint32, shnum)
for i := 0; i < shnum; i++ {
off := f.SHTOffset + int64(i)*int64(shentsize)
sr.Seek(off, seekStart)
s := new(Section)
switch f.Class {
case ELFCLASS32:
sh := new(Section32)
if err := binary.Read(sr, f.ByteOrder, sh); err != nil {
return nil, err
}
names[i] = sh.Name
s.SectionHeader = SectionHeader{
Type: SectionType(sh.Type),
Flags: SectionFlag(sh.Flags),
Addr: uint64(sh.Addr),
Offset: uint64(sh.Off),
FileSize: uint64(sh.Size),
Link: sh.Link,
Info: sh.Info,
Addralign: uint64(sh.Addralign),
Entsize: uint64(sh.Entsize),
Shnum: i,
Shname: sh.Name,
}
case ELFCLASS64:
sh := new(Section64)
if err := binary.Read(sr, f.ByteOrder, sh); err != nil {
return nil, err
}
names[i] = sh.Name
s.SectionHeader = SectionHeader{
Type: SectionType(sh.Type),
Flags: SectionFlag(sh.Flags),
Offset: sh.Off,
FileSize: sh.Size,
Addr: sh.Addr,
Link: sh.Link,
Info: sh.Info,
Addralign: sh.Addralign,
Entsize: sh.Entsize,
Shnum: i,
Shname: sh.Name,
}
}
s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.FileSize))
if s.Flags&SHF_COMPRESSED == 0 {
s.ReaderAt = s.sr
s.Size = s.FileSize
} else {
// Read the compression header.
switch f.Class {
case ELFCLASS32:
ch := new(Chdr32)
if err := binary.Read(s.sr, f.ByteOrder, ch); err != nil {
return nil, err
}
s.compressionType = CompressionType(ch.Type)
s.Size = uint64(ch.Size)
s.Addralign = uint64(ch.Addralign)
s.compressionOffset = int64(binary.Size(ch))
case ELFCLASS64:
ch := new(Chdr64)
if err := binary.Read(s.sr, f.ByteOrder, ch); err != nil {
return nil, err
}
s.compressionType = CompressionType(ch.Type)
s.Size = ch.Size
s.Addralign = ch.Addralign
s.compressionOffset = int64(binary.Size(ch))
}
}
f.Sections[i] = s
}
if err := f.parseDynTags(); err != nil {
return nil, err
}
if len(f.Sections) == 0 {
return f, nil
}
// Load section header string table.
shstrtab, err := f.Sections[f.ShStrIndex].Data()
if err != nil {
return nil, err
}
for i, s := range f.Sections {
var ok bool
s.Name, ok = getString(shstrtab, int(names[i]))
if !ok {
return nil, &FormatError{f.SHTOffset + int64(i*shentsize), "bad section name index", names[i]}
}
}
return f, nil
}
// getSymbols returns a slice of Symbols from parsing the symbol table
// with the given type, along with the associated string table.
func (f *File) getSymbols(typ SectionType) ([]Symbol, []byte, error) {
switch f.Class {
case ELFCLASS64:
return f.getSymbols64(typ)
case ELFCLASS32:
return f.getSymbols32(typ)
}
return nil, nil, errors.New("not implemented")
}
// ErrNoSymbols is returned by File.Symbols and File.DynamicSymbols
// if there is no such section in the File.
var ErrNoSymbols = errors.New("no symbol section")
func (f *File) getSymbols32(typ SectionType) ([]Symbol, []byte, error) {
symtabSection := f.SectionByType(typ)
if symtabSection == nil {
return nil, nil, ErrNoSymbols
}
data, err := symtabSection.Data()
if err != nil {
return nil, nil, errors.New("cannot load symbol section")
}
symtab := bytes.NewReader(data)
if symtab.Len()%Sym32Size != 0 {
return nil, nil, errors.New("length of symbol section is not a multiple of SymSize")
}
strdata, err := f.stringTable(symtabSection.Link)
if err != nil {
return nil, nil, errors.New("cannot load string table section")
}
// The first entry is all zeros.
var skip [Sym32Size]byte
symtab.Read(skip[:])
symbols := make([]Symbol, symtab.Len()/Sym32Size)
i := 0
var sym Sym32
for symtab.Len() > 0 {
binary.Read(symtab, f.ByteOrder, &sym)
str, _ := getString(strdata, int(sym.Name))
symbols[i].Name = str
symbols[i].Info = sym.Info
symbols[i].Other = sym.Other
symbols[i].Section = SectionIndex(sym.Shndx)
symbols[i].Value = uint64(sym.Value)
symbols[i].Size = uint64(sym.Size)
i++
}
return symbols, strdata, nil
}
func (f *File) getSymbols64(typ SectionType) ([]Symbol, []byte, error) {
symtabSection := f.SectionByType(typ)
if symtabSection == nil {
return nil, nil, ErrNoSymbols
}
data, err := symtabSection.Data()
if err != nil {
return nil, nil, errors.New("cannot load symbol section")
}
symtab := bytes.NewReader(data)
if symtab.Len()%Sym64Size != 0 {
return nil, nil, errors.New("length of symbol section is not a multiple of Sym64Size")
}
strdata, err := f.stringTable(symtabSection.Link)
if err != nil {
return nil, nil, errors.New("cannot load string table section")
}
// The first entry is all zeros.
var skip [Sym64Size]byte
symtab.Read(skip[:])
symbols := make([]Symbol, symtab.Len()/Sym64Size)
i := 0
var sym Sym64
for symtab.Len() > 0 {
binary.Read(symtab, f.ByteOrder, &sym)
str, _ := getString(strdata, int(sym.Name))
symbols[i].Name = str
symbols[i].NameIndex = sym.Name
symbols[i].Info = sym.Info
symbols[i].Other = sym.Other
symbols[i].Section = SectionIndex(sym.Shndx)
symbols[i].SectIndex = sym.Shndx
symbols[i].Value = sym.Value
symbols[i].Size = sym.Size
i++
}
return symbols, strdata, nil
}
// getString extracts a string from an ELF string table.
func getString(section []byte, start int) (string, bool) {
if start < 0 || start >= len(section) {
return "", false
}
for end := start; end < len(section); end++ {
if section[end] == 0 {
return string(section[start:end]), true
}
}
return "", false
}
// Section returns a section with the given name, or nil if no such
// section exists.
func (f *File) Section(name string) *Section {
for _, s := range f.Sections {
if s.Name == name {
return s
}
}
return nil
}
// DWARF - No idea what this does
func (f *File) DWARF() (*dwarf.Data, error) {
dwarfSuffix := func(s *Section) string {
switch {
case strings.HasPrefix(s.Name, ".debug_"):
return s.Name[7:]
case strings.HasPrefix(s.Name, ".zdebug_"):
return s.Name[8:]
default:
return ""
}
}
// sectionData gets the data for s, checks its size, and
// applies any applicable relations.
sectionData := func(i int, s *Section) ([]byte, error) {
b, err := s.Data()
if err != nil && uint64(len(b)) < s.Size {
return nil, err
}
if len(b) >= 12 && string(b[:4]) == "ZLIB" {
dlen := binary.BigEndian.Uint64(b[4:12])
dbuf := make([]byte, dlen)
r, err := zlib.NewReader(bytes.NewBuffer(b[12:]))
if err != nil {
return nil, err
}
if _, err := io.ReadFull(r, dbuf); err != nil {
return nil, err
}
if err := r.Close(); err != nil {
return nil, err
}
b = dbuf
}
for _, r := range f.Sections {
if r.Type != SHT_RELA && r.Type != SHT_REL {
continue
}
if int(r.Info) != i {
continue
}
rd, err := r.Data()
if err != nil {
return nil, err
}
err = f.applyRelocations(b, rd)
if err != nil {
return nil, err
}
}
return b, nil
}
// There are many other DWARF sections, but these
// are the ones the debug/dwarf package uses.
// Don't bother loading others.
var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil}
for i, s := range f.Sections {
suffix := dwarfSuffix(s)
if suffix == "" {
continue
}
if _, ok := dat[suffix]; !ok {
continue
}
b, err := sectionData(i, s)
if err != nil {
return nil, err
}
dat[suffix] = b
}
d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, dat["ranges"], dat["str"])
if err != nil {
return nil, err
}
// Look for DWARF4 .debug_types sections.
for i, s := range f.Sections {
suffix := dwarfSuffix(s)
if suffix != "types" {
continue
}
b, err := sectionData(i, s)
if err != nil {
return nil, err
}
err = d.AddTypes(fmt.Sprintf("types-%d", i), b)
if err != nil {
return nil, err
}
}
return d, nil
}
// Symbols returns the symbol table for f. The symbols will be listed in the order
// they appear in f.
//
// For compatibility with Go 1.0, Symbols omits the null symbol at index 0.
// After retrieving the symbols as symtab, an externally supplied index x
// corresponds to symtab[x-1], not symtab[x].
func (f *File) Symbols() ([]Symbol, error) {
sym, _, err := f.getSymbols(SHT_SYMTAB)
return sym, err
}
// DynamicSymbols returns the dynamic symbol table for f. The symbols
// will be listed in the order they appear in f.
//
// For compatibility with Symbols, DynamicSymbols omits the null symbol at index 0.
// After retrieving the symbols as symtab, an externally supplied index x
// corresponds to symtab[x-1], not symtab[x].
func (f *File) DynamicSymbols() ([]Symbol, error) {
sym, _, err := f.getSymbols(SHT_DYNSYM)
return sym, err
}
type ImportedSymbol struct {
Name string
Version string
Library string
}
// ImportedSymbols returns the names of all symbols
// referred to by the binary f that are expected to be
// satisfied by other libraries at dynamic load time.
// It does not return weak symbols.
func (f *File) ImportedSymbols() ([]ImportedSymbol, error) {
sym, str, err := f.getSymbols(SHT_DYNSYM)
if err != nil {
return nil, err
}
f.gnuVersionInit(str)
var all []ImportedSymbol
for i, s := range sym {
if ST_BIND(s.Info) == STB_GLOBAL && s.Section == SHN_UNDEF {
all = append(all, ImportedSymbol{Name: s.Name})
f.gnuVersion(i, &all[len(all)-1])
}
}
return all, nil
}
type verneed struct {
File string
Name string
}
// gnuVersionInit parses the GNU version tables
// for use by calls to gnuVersion.
func (f *File) gnuVersionInit(str []byte) {
// Accumulate verneed information.
vn := f.SectionByType(SHT_GNU_VERNEED)
if vn == nil {
return
}
d, _ := vn.Data()
var need []verneed
i := 0
for {
if i+16 > len(d) {
break
}
vers := f.ByteOrder.Uint16(d[i : i+2])
if vers != 1 {
break
}
cnt := f.ByteOrder.Uint16(d[i+2 : i+4])
fileoff := f.ByteOrder.Uint32(d[i+4 : i+8])
aux := f.ByteOrder.Uint32(d[i+8 : i+12])
next := f.ByteOrder.Uint32(d[i+12 : i+16])
file, _ := getString(str, int(fileoff))
var name string
j := i + int(aux)
for c := 0; c < int(cnt); c++ {
if j+16 > len(d) {
break
}
// hash := f.ByteOrder.Uint32(d[j:j+4])
// flags := f.ByteOrder.Uint16(d[j+4:j+6])
other := f.ByteOrder.Uint16(d[j+6 : j+8])
nameoff := f.ByteOrder.Uint32(d[j+8 : j+12])
next := f.ByteOrder.Uint32(d[j+12 : j+16])
name, _ = getString(str, int(nameoff))
ndx := int(other)
if ndx >= len(need) {
a := make([]verneed, 2*(ndx+1))
copy(a, need)
need = a
}
need[ndx] = verneed{file, name}
if next == 0 {
break
}
j += int(next)
}
if next == 0 {
break
}
i += int(next)
}
// Versym parallels symbol table, indexing into verneed.
vs := f.SectionByType(SHT_GNU_VERSYM)
if vs == nil {
return
}
d, _ = vs.Data()
f.gnuNeed = need
f.gnuVersym = d
}
// gnuVersion adds Library and Version information to sym,
// which came from offset i of the symbol table.
func (f *File) gnuVersion(i int, sym *ImportedSymbol) {
// Each entry is two bytes.
i = (i + 1) * 2
if i >= len(f.gnuVersym) {
return
}
j := int(f.ByteOrder.Uint16(f.gnuVersym[i:]))
if j < 2 || j >= len(f.gnuNeed) {
return
}
n := &f.gnuNeed[j]
sym.Library = n.File
sym.Version = n.Name
}
// ImportedLibraries returns the names of all libraries
// referred to by the binary f that are expected to be
// linked with the binary at dynamic link time.
func (f *File) ImportedLibraries() ([]string, error) {
return f.DynString(DT_NEEDED)
}
func (f *File) parseDynTags() error {
s := f.SectionByType(SHT_DYNAMIC)
if s == nil {
return nil // nothing to do
}
var m []DynTagValue
d, err := s.Data()
if err != nil {
return err
}
//fmt.Printf("%+v\nd: %x len(%d)\n", s, d, len(d))
r := bytes.NewBuffer(d)
for {
var t, v uint64
if err := binary.Read(r, f.ByteOrder, &t); err != nil {
if err == io.EOF {
break
} else if err != nil {
return err
}
}
if err := binary.Read(r, f.ByteOrder, &v); err != nil {
if err == io.EOF {
break
} else if err != nil {
return err
}
}
m = append(m, DynTagValue{Tag: DynTag(t), Value: v})
//fmt.Printf("%x -> %x\n", t, v)
}
f.DynTags = m
return nil
}
// DynString returns the strings listed for the given tag in the file's dynamic
// section.
//
// The tag must be one that takes string values: DT_NEEDED, DT_SONAME, DT_RPATH, or
// DT_RUNPATH.
func (f *File) DynString(tag DynTag) ([]string, error) {
switch tag {
case DT_NEEDED, DT_SONAME, DT_RPATH, DT_RUNPATH:
default:
return nil, fmt.Errorf("non-string-valued tag %v", tag)
}
ds := f.SectionByType(SHT_DYNAMIC)
if ds == nil {
// not dynamic, so no libraries
return nil, nil
}
str, err := f.stringTable(ds.Link)
if err != nil {
return nil, err
}
var all []string
for _, taggedValue := range f.DynTags {
if taggedValue.Tag == tag {
s, ok := getString(str, int(taggedValue.Value))
if ok {
all = append(all, s)
}
}
}
return all, nil
}
-108
View File
@@ -1,108 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package elf
import (
"io"
"os"
)
// errorReader returns error from all operations.
type errorReader struct {
error
}
func (r errorReader) Read(p []byte) (n int, err error) {
return 0, r.error
}
func (r errorReader) ReadAt(p []byte, off int64) (n int, err error) {
return 0, r.error
}
func (r errorReader) Seek(offset int64, whence int) (int64, error) {
return 0, r.error
}
func (r errorReader) Close() error {
return r.error
}
// readSeekerFromReader converts an io.Reader into an io.ReadSeeker.
// In general Seek may not be efficient, but it is optimized for
// common cases such as seeking to the end to find the length of the
// data.
type readSeekerFromReader struct {
reset func() (io.Reader, error)
r io.Reader
size int64
offset int64
}
func (r *readSeekerFromReader) start() {
x, err := r.reset()
if err != nil {
r.r = errorReader{err}
} else {
r.r = x
}
r.offset = 0
}
func (r *readSeekerFromReader) Read(p []byte) (n int, err error) {
if r.r == nil {
r.start()
}
n, err = r.r.Read(p)
r.offset += int64(n)
return n, err
}
func (r *readSeekerFromReader) Seek(offset int64, whence int) (int64, error) {
var newOffset int64
switch whence {
case seekStart:
newOffset = offset
case seekCurrent:
newOffset = r.offset + offset
case seekEnd:
newOffset = r.size + offset
default:
return 0, os.ErrInvalid
}
switch {
case newOffset == r.offset:
return newOffset, nil
case newOffset < 0, newOffset > r.size:
return 0, os.ErrInvalid
case newOffset == 0:
r.r = nil
case newOffset == r.size:
r.r = errorReader{io.EOF}
default:
if newOffset < r.offset {
// Restart at the beginning.
r.start()
}
// Read until we reach offset.
var buf [512]byte
for r.offset < newOffset {
b := buf[:]
if newOffset-r.offset < int64(len(buf)) {
b = buf[:newOffset-r.offset]
}
if _, err := r.Read(b); err != nil {
return 0, err
}
}
}
r.offset = newOffset
return r.offset, nil
}
-529
View File
@@ -1,529 +0,0 @@
package elf
import (
"bytes"
"encoding/binary"
"errors"
)
// applyRelocations applies relocations to dst. rels is a relocations section
// in REL or RELA format.
func (f *File) applyRelocations(dst []byte, rels []byte) error {
switch {
case f.Class == ELFCLASS64 && f.Machine == EM_X86_64:
return f.applyRelocationsAMD64(dst, rels)
case f.Class == ELFCLASS32 && f.Machine == EM_386:
return f.applyRelocations386(dst, rels)
case f.Class == ELFCLASS32 && f.Machine == EM_ARM:
return f.applyRelocationsARM(dst, rels)
case f.Class == ELFCLASS64 && f.Machine == EM_AARCH64:
return f.applyRelocationsARM64(dst, rels)
case f.Class == ELFCLASS32 && f.Machine == EM_PPC:
return f.applyRelocationsPPC(dst, rels)
case f.Class == ELFCLASS64 && f.Machine == EM_PPC64:
return f.applyRelocationsPPC64(dst, rels)
case f.Class == ELFCLASS32 && f.Machine == EM_MIPS:
return f.applyRelocationsMIPS(dst, rels)
case f.Class == ELFCLASS64 && f.Machine == EM_MIPS:
return f.applyRelocationsMIPS64(dst, rels)
case f.Class == ELFCLASS64 && f.Machine == EM_RISCV:
return f.applyRelocationsRISCV64(dst, rels)
case f.Class == ELFCLASS64 && f.Machine == EM_S390:
return f.applyRelocationss390x(dst, rels)
case f.Class == ELFCLASS64 && f.Machine == EM_SPARCV9:
return f.applyRelocationsSPARC64(dst, rels)
default:
return errors.New("applyRelocations: not implemented")
}
}
func (f *File) applyRelocationsAMD64(dst []byte, rels []byte) error {
// 24 is the size of Rela64.
if len(rels)%24 != 0 {
return errors.New("length of relocation section is not a multiple of 24")
}
symbols, _, err := f.getSymbols(SHT_SYMTAB)
if err != nil {
return err
}
b := bytes.NewReader(rels)
var rela Rela64
for b.Len() > 0 {
binary.Read(b, f.ByteOrder, &rela)
symNo := rela.Info >> 32
t := R_X86_64(rela.Info & 0xffff)
if symNo == 0 || symNo > uint64(len(symbols)) {
continue
}
sym := &symbols[symNo-1]
if SymType(sym.Info&0xf) != STT_SECTION {
// We don't handle non-section relocations for now.
continue
}
// There are relocations, so this must be a normal
// object file, and we only look at section symbols,
// so we assume that the symbol value is 0.
switch t {
case R_X86_64_64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], uint64(rela.Addend))
case R_X86_64_32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], uint32(rela.Addend))
}
}
return nil
}
func (f *File) applyRelocations386(dst []byte, rels []byte) error {
// 8 is the size of Rel32.
if len(rels)%8 != 0 {
return errors.New("length of relocation section is not a multiple of 8")
}
symbols, _, err := f.getSymbols(SHT_SYMTAB)
if err != nil {
return err
}
b := bytes.NewReader(rels)
var rel Rel32
for b.Len() > 0 {
binary.Read(b, f.ByteOrder, &rel)
symNo := rel.Info >> 8
t := R_386(rel.Info & 0xff)
if symNo == 0 || symNo > uint32(len(symbols)) {
continue
}
sym := &symbols[symNo-1]
if t == R_386_32 {
if rel.Off+4 >= uint32(len(dst)) {
continue
}
val := f.ByteOrder.Uint32(dst[rel.Off : rel.Off+4])
val += uint32(sym.Value)
f.ByteOrder.PutUint32(dst[rel.Off:rel.Off+4], val)
}
}
return nil
}
func (f *File) applyRelocationsARM(dst []byte, rels []byte) error {
// 8 is the size of Rel32.
if len(rels)%8 != 0 {
return errors.New("length of relocation section is not a multiple of 8")
}
symbols, _, err := f.getSymbols(SHT_SYMTAB)
if err != nil {
return err
}
b := bytes.NewReader(rels)
var rel Rel32
for b.Len() > 0 {
binary.Read(b, f.ByteOrder, &rel)
symNo := rel.Info >> 8
t := R_ARM(rel.Info & 0xff)
if symNo == 0 || symNo > uint32(len(symbols)) {
continue
}
sym := &symbols[symNo-1]
switch t {
case R_ARM_ABS32:
if rel.Off+4 >= uint32(len(dst)) {
continue
}
val := f.ByteOrder.Uint32(dst[rel.Off : rel.Off+4])
val += uint32(sym.Value)
f.ByteOrder.PutUint32(dst[rel.Off:rel.Off+4], val)
}
}
return nil
}
func (f *File) applyRelocationsARM64(dst []byte, rels []byte) error {
// 24 is the size of Rela64.
if len(rels)%24 != 0 {
return errors.New("length of relocation section is not a multiple of 24")
}
symbols, _, err := f.getSymbols(SHT_SYMTAB)
if err != nil {
return err
}
b := bytes.NewReader(rels)
var rela Rela64
for b.Len() > 0 {
binary.Read(b, f.ByteOrder, &rela)
symNo := rela.Info >> 32
t := R_AARCH64(rela.Info & 0xffff)
if symNo == 0 || symNo > uint64(len(symbols)) {
continue
}
sym := &symbols[symNo-1]
if SymType(sym.Info&0xf) != STT_SECTION {
// We don't handle non-section relocations for now.
continue
}
// There are relocations, so this must be a normal
// object file, and we only look at section symbols,
// so we assume that the symbol value is 0.
switch t {
case R_AARCH64_ABS64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], uint64(rela.Addend))
case R_AARCH64_ABS32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], uint32(rela.Addend))
}
}
return nil
}
func (f *File) applyRelocationsPPC(dst []byte, rels []byte) error {
// 12 is the size of Rela32.
if len(rels)%12 != 0 {
return errors.New("length of relocation section is not a multiple of 12")
}
symbols, _, err := f.getSymbols(SHT_SYMTAB)
if err != nil {
return err
}
b := bytes.NewReader(rels)
var rela Rela32
for b.Len() > 0 {
binary.Read(b, f.ByteOrder, &rela)
symNo := rela.Info >> 8
t := R_PPC(rela.Info & 0xff)
if symNo == 0 || symNo > uint32(len(symbols)) {
continue
}
sym := &symbols[symNo-1]
if SymType(sym.Info&0xf) != STT_SECTION {
// We don't handle non-section relocations for now.
continue
}
switch t {
case R_PPC_ADDR32:
if rela.Off+4 >= uint32(len(dst)) || rela.Addend < 0 {
continue
}
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], uint32(rela.Addend))
}
}
return nil
}
func (f *File) applyRelocationsPPC64(dst []byte, rels []byte) error {
// 24 is the size of Rela64.
if len(rels)%24 != 0 {
return errors.New("length of relocation section is not a multiple of 24")
}
symbols, _, err := f.getSymbols(SHT_SYMTAB)
if err != nil {
return err
}
b := bytes.NewReader(rels)
var rela Rela64
for b.Len() > 0 {
binary.Read(b, f.ByteOrder, &rela)
symNo := rela.Info >> 32
t := R_PPC64(rela.Info & 0xffff)
if symNo == 0 || symNo > uint64(len(symbols)) {
continue
}
sym := &symbols[symNo-1]
if SymType(sym.Info&0xf) != STT_SECTION {
// We don't handle non-section relocations for now.
continue
}
switch t {
case R_PPC64_ADDR64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], uint64(rela.Addend))
case R_PPC64_ADDR32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], uint32(rela.Addend))
}
}
return nil
}
func (f *File) applyRelocationsMIPS(dst []byte, rels []byte) error {
// 8 is the size of Rel32.
if len(rels)%8 != 0 {
return errors.New("length of relocation section is not a multiple of 8")
}
symbols, _, err := f.getSymbols(SHT_SYMTAB)
if err != nil {
return err
}
b := bytes.NewReader(rels)
var rel Rel32
for b.Len() > 0 {
binary.Read(b, f.ByteOrder, &rel)
symNo := rel.Info >> 8
t := R_MIPS(rel.Info & 0xff)
if symNo == 0 || symNo > uint32(len(symbols)) {
continue
}
sym := &symbols[symNo-1]
switch t {
case R_MIPS_32:
if rel.Off+4 >= uint32(len(dst)) {
continue
}
val := f.ByteOrder.Uint32(dst[rel.Off : rel.Off+4])
val += uint32(sym.Value)
f.ByteOrder.PutUint32(dst[rel.Off:rel.Off+4], val)
}
}
return nil
}
func (f *File) applyRelocationsMIPS64(dst []byte, rels []byte) error {
// 24 is the size of Rela64.
if len(rels)%24 != 0 {
return errors.New("length of relocation section is not a multiple of 24")
}
symbols, _, err := f.getSymbols(SHT_SYMTAB)
if err != nil {
return err
}
b := bytes.NewReader(rels)
var rela Rela64
for b.Len() > 0 {
binary.Read(b, f.ByteOrder, &rela)
var symNo uint64
var t R_MIPS
if f.ByteOrder == binary.BigEndian {
symNo = rela.Info >> 32
t = R_MIPS(rela.Info & 0xff)
} else {
symNo = rela.Info & 0xffffffff
t = R_MIPS(rela.Info >> 56)
}
if symNo == 0 || symNo > uint64(len(symbols)) {
continue
}
sym := &symbols[symNo-1]
if SymType(sym.Info&0xf) != STT_SECTION {
// We don't handle non-section relocations for now.
continue
}
switch t {
case R_MIPS_64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], uint64(rela.Addend))
case R_MIPS_32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], uint32(rela.Addend))
}
}
return nil
}
func (f *File) applyRelocationsRISCV64(dst []byte, rels []byte) error {
// 24 is the size of Rela64.
if len(rels)%24 != 0 {
return errors.New("length of relocation section is not a multiple of 24")
}
symbols, _, err := f.getSymbols(SHT_SYMTAB)
if err != nil {
return err
}
b := bytes.NewReader(rels)
var rela Rela64
for b.Len() > 0 {
binary.Read(b, f.ByteOrder, &rela)
symNo := rela.Info >> 32
t := R_RISCV(rela.Info & 0xffff)
if symNo == 0 || symNo > uint64(len(symbols)) {
continue
}
sym := &symbols[symNo-1]
switch SymType(sym.Info & 0xf) {
case STT_SECTION, STT_NOTYPE:
break
default:
continue
}
switch t {
case R_RISCV_64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val := sym.Value + uint64(rela.Addend)
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val)
case R_RISCV_32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val := uint32(sym.Value) + uint32(rela.Addend)
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], val)
}
}
return nil
}
func (f *File) applyRelocationss390x(dst []byte, rels []byte) error {
// 24 is the size of Rela64.
if len(rels)%24 != 0 {
return errors.New("length of relocation section is not a multiple of 24")
}
symbols, _, err := f.getSymbols(SHT_SYMTAB)
if err != nil {
return err
}
b := bytes.NewReader(rels)
var rela Rela64
for b.Len() > 0 {
binary.Read(b, f.ByteOrder, &rela)
symNo := rela.Info >> 32
t := R_390(rela.Info & 0xffff)
if symNo == 0 || symNo > uint64(len(symbols)) {
continue
}
sym := &symbols[symNo-1]
switch SymType(sym.Info & 0xf) {
case STT_SECTION, STT_NOTYPE:
break
default:
continue
}
switch t {
case R_390_64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val := sym.Value + uint64(rela.Addend)
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], val)
case R_390_32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
val := uint32(sym.Value) + uint32(rela.Addend)
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], val)
}
}
return nil
}
func (f *File) applyRelocationsSPARC64(dst []byte, rels []byte) error {
// 24 is the size of Rela64.
if len(rels)%24 != 0 {
return errors.New("length of relocation section is not a multiple of 24")
}
symbols, _, err := f.getSymbols(SHT_SYMTAB)
if err != nil {
return err
}
b := bytes.NewReader(rels)
var rela Rela64
for b.Len() > 0 {
binary.Read(b, f.ByteOrder, &rela)
symNo := rela.Info >> 32
t := R_SPARC(rela.Info & 0xff)
if symNo == 0 || symNo > uint64(len(symbols)) {
continue
}
sym := &symbols[symNo-1]
if SymType(sym.Info&0xf) != STT_SECTION {
// We don't handle non-section relocations for now.
continue
}
switch t {
case R_SPARC_64, R_SPARC_UA64:
if rela.Off+8 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
f.ByteOrder.PutUint64(dst[rela.Off:rela.Off+8], uint64(rela.Addend))
case R_SPARC_32, R_SPARC_UA32:
if rela.Off+4 >= uint64(len(dst)) || rela.Addend < 0 {
continue
}
f.ByteOrder.PutUint32(dst[rela.Off:rela.Off+4], uint32(rela.Addend))
}
}
return nil
}
-281
View File
@@ -1,281 +0,0 @@
package elf
import (
"bufio"
"bytes"
"encoding/binary"
"io/ioutil"
"log"
"os"
)
// Bytes - returns the bytes of an Elf file
func (elfFile *File) Bytes() ([]byte, error) {
bytesWritten := uint64(0)
elfBuf := bytes.NewBuffer(nil)
w := bufio.NewWriter(elfBuf)
// Write Elf Magic
w.WriteByte('\x7f')
w.WriteByte('E')
w.WriteByte('L')
w.WriteByte('F')
bytesWritten += 4
// ident[EI_CLASS]
w.WriteByte(byte(elfFile.Class))
// ident[EI_DATA]
w.WriteByte(byte(elfFile.Data))
// ident[EI_VERSION]
w.WriteByte(byte(elfFile.Version))
// ident[EI_OSABI]
w.WriteByte(byte(elfFile.OSABI))
// ident[EI_ABIVERSION]
w.WriteByte(byte(elfFile.ABIVersion))
// ident[EI_PAD] ( 7 bytes )
w.Write([]byte{0, 0, 0, 0, 0, 0, 0})
bytesWritten += 12
// Type
binary.Write(w, elfFile.ByteOrder, uint16(elfFile.Type))
// Machine
binary.Write(w, elfFile.ByteOrder, uint16(elfFile.Machine))
// Version
binary.Write(w, elfFile.ByteOrder, uint32(elfFile.Version))
bytesWritten += 8
phsize := 0
switch elfFile.Class {
case ELFCLASS32:
phsize = 0x20
// Entry 32
binary.Write(w, elfFile.ByteOrder, uint32(elfFile.Entry))
// PH Offset 32
binary.Write(w, elfFile.ByteOrder, uint32(0x34))
// SH Offset 32 // 0x20 0x28 4 8 e_shoff Points to the start of the section header table.
binary.Write(w, elfFile.ByteOrder, int32(elfFile.FileHeader.SHTOffset))
// Flags
binary.Write(w, elfFile.ByteOrder, uint32(0)) // todo
// EH Size
binary.Write(w, elfFile.ByteOrder, uint16(52))
// PH Size // 0x2A 0x36 2 e_phentsize Contains the size of a program header table entry.
binary.Write(w, elfFile.ByteOrder, uint16(phsize))
// PH Num // 0x2C 0x38 2 e_phnum Contains the number of entries in the program header table.
binary.Write(w, elfFile.ByteOrder, uint16(len(elfFile.Progs)))
// SH Size // 0x2E 0x3A 2 e_shentsize Contains the size of a section header table entry.
binary.Write(w, elfFile.ByteOrder, uint16(0x28))
bytesWritten += 24
case ELFCLASS64:
phsize = 0x38
// Entry 64
binary.Write(w, elfFile.ByteOrder, uint64(elfFile.Entry))
// PH Offset 64
binary.Write(w, elfFile.ByteOrder, uint64(0x40))
// SH Offset 64 // 0x20 0x28 4 8 e_shoff Points to the start of the section header table.
binary.Write(w, elfFile.ByteOrder, int64(elfFile.FileHeader.SHTOffset))
// Flags
binary.Write(w, elfFile.ByteOrder, uint32(0)) // I think right?
// EH Size
binary.Write(w, elfFile.ByteOrder, uint16(64))
// PH Size // 0x2A 0x36 2 e_phentsize Contains the size of a program header table entry.
binary.Write(w, elfFile.ByteOrder, uint16(phsize))
// PH Num // 0x2C 0x38 2 e_phnum Contains the number of entries in the program header table.
binary.Write(w, elfFile.ByteOrder, uint16(len(elfFile.Progs)))
// SH Size // 0x2E 0x3A 2 e_shentsize Contains the size of a section header table entry.
binary.Write(w, elfFile.ByteOrder, uint16(0x40))
bytesWritten += 36
}
// SH Num // 0x30 0x3C 2 e_shnum Contains the number of entries in the section header table.
binary.Write(w, elfFile.ByteOrder, uint16(len(elfFile.Sections)))
// SH Str Ndx // 0x32 0x3E 2 e_shstrndx Contains index of the section header table entry that contains the section names.
binary.Write(w, elfFile.ByteOrder, uint16(elfFile.ShStrIndex))
bytesWritten += 4
// Program Header
for _, p := range elfFile.Progs {
// Type (segment)
binary.Write(w, elfFile.ByteOrder, uint32(p.Type))
bytesWritten += 4
switch elfFile.Class {
case ELFCLASS32:
// Offset of Segment in File
binary.Write(w, elfFile.ByteOrder, uint32(p.Off))
// Vaddr
binary.Write(w, elfFile.ByteOrder, uint32(p.Vaddr))
// Paddr
binary.Write(w, elfFile.ByteOrder, uint32(p.Paddr))
// File Size
binary.Write(w, elfFile.ByteOrder, uint32(p.Filesz))
// Memory Size
binary.Write(w, elfFile.ByteOrder, uint32(p.Memsz))
// Flags (segment)
binary.Write(w, elfFile.ByteOrder, uint32(p.Flags))
// Alignment
binary.Write(w, elfFile.ByteOrder, uint32(p.Align))
bytesWritten += 28
case ELFCLASS64:
// Flags (segment)
binary.Write(w, elfFile.ByteOrder, uint32(p.Flags))
// Offset of Segment in File
binary.Write(w, elfFile.ByteOrder, uint64(p.Off))
// Vaddr
binary.Write(w, elfFile.ByteOrder, uint64(p.Vaddr))
// Paddr
binary.Write(w, elfFile.ByteOrder, uint64(p.Paddr))
// File Size
binary.Write(w, elfFile.ByteOrder, uint64(p.Filesz))
// Memory Size
binary.Write(w, elfFile.ByteOrder, uint64(p.Memsz))
// Alignment
binary.Write(w, elfFile.ByteOrder, uint64(p.Align))
bytesWritten += 52
}
}
sortedSections := elfFile.Sections[:]
//sort.Slice(sortedSections, func(a, b int) bool { return elfFile.Sections[a].Link < elfFile.Sections[b].Link })
for _, s := range sortedSections {
//log.Printf("Writing section: %s type: %+v\n", s.Name, s.Type)
//log.Printf("written: %x offset: %x\n", bytesWritten, s.Offset)
if s.Type == SHT_NULL || s.Type == SHT_NOBITS || s.FileSize == 0 {
//log.Println("continuing...")
continue
}
if bytesWritten > s.Offset {
log.Printf("Overlapping Sections in Generated Elf: %+v\n", s.Name)
continue
}
if s.Offset != 0 && bytesWritten < s.Offset {
pad := make([]byte, s.Offset-bytesWritten)
w.Write(pad)
//log.Printf("Padding before section %s at %x: length:%x to:%x\n", s.Name, bytesWritten, len(pad), s.Offset)
bytesWritten += uint64(len(pad))
}
slen := 0
switch s.Type {
case SHT_DYNAMIC:
for _, taggedValue := range elfFile.DynTags {
//log.Printf("writing %d (%x) -> %d (%x)\n", taggedValue.Tag, taggedValue.Tag, taggedValue.Value, taggedValue.Value)
switch elfFile.Class {
case ELFCLASS32:
binary.Write(w, elfFile.ByteOrder, uint32(taggedValue.Tag))
binary.Write(w, elfFile.ByteOrder, uint32(taggedValue.Value))
bytesWritten += 8
case ELFCLASS64:
binary.Write(w, elfFile.ByteOrder, uint64(taggedValue.Tag))
binary.Write(w, elfFile.ByteOrder, uint64(taggedValue.Value))
bytesWritten += 16
}
}
default:
section, err := ioutil.ReadAll(s.Open())
if err != nil {
return nil, err
}
binary.Write(w, elfFile.ByteOrder, section)
slen = len(section)
//log.Printf("Wrote %s section at %x, length %x\n", s.Name, bytesWritten, slen)
bytesWritten += uint64(slen)
}
// todo: elfFile.Insertion should be renamed InsertionLoadEnd or similar
if s.Type == SHT_PROGBITS && len(elfFile.Insertion) > 0 && s.Size-uint64(slen) >= uint64(len(elfFile.Insertion)) {
binary.Write(w, elfFile.ByteOrder, elfFile.Insertion)
bytesWritten += uint64(len(elfFile.Insertion))
}
w.Flush()
}
// Pad to Section Header Table
if bytesWritten < uint64(elfFile.FileHeader.SHTOffset) {
pad := make([]byte, uint64(elfFile.FileHeader.SHTOffset)-bytesWritten)
w.Write(pad)
//log.Printf("Padding before SHT at %x: length:%x to:%x\n", bytesWritten, len(pad), elfFile.FileHeader.SHTOffset)
bytesWritten += uint64(len(pad))
}
// Write Section Header Table
for _, s := range elfFile.Sections[:] {
switch elfFile.Class {
case ELFCLASS32:
binary.Write(w, elfFile.ByteOrder, &Section32{
Name: s.Shname,
Type: uint32(s.Type),
Flags: uint32(s.Flags),
Addr: uint32(s.Addr),
Off: uint32(s.Offset),
Size: uint32(s.Size),
Link: s.Link,
Info: s.Info,
Addralign: uint32(s.Addralign),
Entsize: uint32(s.Entsize)})
case ELFCLASS64:
binary.Write(w, elfFile.ByteOrder, &Section64{
Name: s.Shname,
Type: uint32(s.Type),
Flags: uint64(s.Flags),
Addr: s.Addr,
Off: s.Offset,
Size: s.Size,
Link: s.Link,
Info: s.Info,
Addralign: s.Addralign,
Entsize: s.Entsize})
}
}
// Do I have a PT_NOTE segment to add at the end?
if len(elfFile.InsertionEOF) > 0 {
binary.Write(w, elfFile.ByteOrder, elfFile.InsertionEOF)
bytesWritten += uint64(len(elfFile.InsertionEOF))
}
w.Flush()
return elfBuf.Bytes(), nil
}
// WriteFile - Creates a new file and writes it using the Bytes func above
func (elfFile *File) WriteFile(destFile string) error {
f, err := os.Create(destFile)
if err != nil {
return err
}
defer f.Close()
elfData, err := elfFile.Bytes()
if err != nil {
return err
}
_, err = f.Write(elfData)
if err != nil {
return err
}
return nil
}
-3
View File
@@ -1,3 +0,0 @@
## More Info
More info on the Mach-O file structure here: [Mach-O Deep Dive](https://www.symbolcrash.com/2019/02/25/so-you-want-to-be-a-mach-o-man/)
-27
View File
@@ -1,27 +0,0 @@
package macho
const N_SECT = 0x0E
const N_PEXT = 0x10
const N_EXT = 0x01
// Export - describes a single export entry (similar to PE version for future refactoring)
type Export struct {
//Ordinal uint32 // no ordinals for Mach-O
Name string
VirtualAddress uint64
}
// Exports - gets exports, including private exports
func (f *File) Exports() []Export {
var exports []Export
for _, symbol := range f.Symtab.Syms {
if (symbol.Type&N_PEXT == N_PEXT ||
symbol.Type&N_EXT == N_EXT) && symbol.Value != 0 {
var export Export
export.Name = symbol.Name
export.VirtualAddress = symbol.Value
exports = append(exports, export)
}
}
return exports
}
-146
View File
@@ -1,146 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package macho
import (
"encoding/binary"
"fmt"
"io"
"os"
)
// A FatFile is a Mach-O universal binary that contains at least one architecture.
type FatFile struct {
Magic uint32
Arches []FatArch
closer io.Closer
}
// A FatArchHeader represents a fat header for a specific image architecture.
type FatArchHeader struct {
Cpu Cpu
SubCpu uint32
Offset uint32
Size uint32
Align uint32
}
const fatArchHeaderSize = 5 * 4
// A FatArch is a Mach-O File inside a FatFile.
type FatArch struct {
FatArchHeader
*File
}
// ErrNotFat is returned from NewFatFile or OpenFat when the file is not a
// universal binary but may be a thin binary, based on its magic number.
var ErrNotFat = &FormatError{0, "not a fat Mach-O file", nil}
// NewFatFile creates a new FatFile for accessing all the Mach-O images in a
// universal binary. The Mach-O binary is expected to start at position 0 in
// the ReaderAt.
func NewFatFile(r io.ReaderAt) (*FatFile, error) {
var ff FatFile
sr := io.NewSectionReader(r, 0, 1<<63-1)
// Read the fat_header struct, which is always in big endian.
// Start with the magic number.
err := binary.Read(sr, binary.BigEndian, &ff.Magic)
if err != nil {
return nil, &FormatError{0, "error reading magic number", nil}
} else if ff.Magic != MagicFat {
// See if this is a Mach-O file via its magic number. The magic
// must be converted to little endian first though.
var buf [4]byte
binary.BigEndian.PutUint32(buf[:], ff.Magic)
leMagic := binary.LittleEndian.Uint32(buf[:])
if leMagic == Magic32 || leMagic == Magic64 {
return nil, ErrNotFat
} else {
return nil, &FormatError{0, "invalid magic number", nil}
}
}
offset := int64(4)
// Read the number of FatArchHeaders that come after the fat_header.
var narch uint32
err = binary.Read(sr, binary.BigEndian, &narch)
if err != nil {
return nil, &FormatError{offset, "invalid fat_header", nil}
}
offset += 4
if narch < 1 {
return nil, &FormatError{offset, "file contains no images", nil}
}
// Combine the Cpu and SubCpu (both uint32) into a uint64 to make sure
// there are not duplicate architectures.
seenArches := make(map[uint64]bool, narch)
// Make sure that all images are for the same MH_ type.
var machoType Type
// Following the fat_header comes narch fat_arch structs that index
// Mach-O images further in the file.
ff.Arches = make([]FatArch, narch)
for i := uint32(0); i < narch; i++ {
fa := &ff.Arches[i]
err = binary.Read(sr, binary.BigEndian, &fa.FatArchHeader)
if err != nil {
return nil, &FormatError{offset, "invalid fat_arch header", nil}
}
offset += fatArchHeaderSize
fr := io.NewSectionReader(r, int64(fa.Offset), int64(fa.Size))
fa.File, err = NewFile(fr)
if err != nil {
return nil, err
}
// Make sure the architecture for this image is not duplicate.
seenArch := (uint64(fa.Cpu) << 32) | uint64(fa.SubCpu)
if o, k := seenArches[seenArch]; o || k {
return nil, &FormatError{offset, fmt.Sprintf("duplicate architecture cpu=%v, subcpu=%#x", fa.Cpu, fa.SubCpu), nil}
}
seenArches[seenArch] = true
// Make sure the Mach-O type matches that of the first image.
if i == 0 {
machoType = fa.Type
} else {
if fa.Type != machoType {
return nil, &FormatError{offset, fmt.Sprintf("Mach-O type for architecture #%d (type=%#x) does not match first (type=%#x)", i, fa.Type, machoType), nil}
}
}
}
return &ff, nil
}
// OpenFat opens the named file using os.Open and prepares it for use as a Mach-O
// universal binary.
func OpenFat(name string) (*FatFile, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
ff, err := NewFatFile(f)
if err != nil {
f.Close()
return nil, err
}
ff.closer = f
return ff, nil
}
func (ff *FatFile) Close() error {
var err error
if ff.closer != nil {
err = ff.closer.Close()
ff.closer = nil
}
return err
}
-952
View File
@@ -1,952 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package macho implements access to Mach-O object files.
package macho
// High level access to low level data structures.
import (
"bytes"
"compress/zlib"
"debug/dwarf"
"encoding/binary"
"fmt"
"io"
"os"
"strings"
)
// A File represents an open Mach-O file.
type File struct {
FileHeader
ByteOrder binary.ByteOrder
Loads []Load
Sections []*Section
Symtab *Symtab
Dysymtab *Dysymtab
SigBlock *SigBlock
FuncStarts *FuncStarts
DataInCode *DataInCode
DylinkInfo *DylinkInfo
EntryPoint uint64
Insertion []byte
closer io.Closer
}
// A Load represents any Mach-O load command.
type Load interface {
Raw() []byte
}
// A LoadBytes is the uninterpreted bytes of a Mach-O load command.
type LoadBytes []byte
func (b LoadBytes) Raw() []byte { return b }
// A SegmentHeader is the header for a Mach-O 32-bit or 64-bit load segment command.
type SegmentHeader struct {
Cmd LoadCmd
Len uint32
Name string
Addr uint64
Memsz uint64
Offset uint64
Filesz uint64
Maxprot uint32
Prot uint32
Nsect uint32
Flag uint32
}
type SigBlock struct {
Len uint32
Offset uint64
RawDat []byte
}
type FuncStarts struct {
Len uint32
Offset uint64
RawDat []byte
}
type DataInCode struct {
Len uint32
Offset uint64
RawDat []byte
}
type DylinkInfo struct {
RebaseLen uint32
RebaseOffset uint64
RebaseDat []byte
BindingInfoLen uint32
BindingInfoOffset uint64
BindingInfoDat []byte
WeakBindingLen uint32
WeakBindingOffset uint64
WeakBindingDat []byte
LazyBindingLen uint32
LazyBindingOffset uint64
LazyBindingDat []byte
ExportInfoLen uint32
ExportInfoOffset uint64
ExportInfoDat []byte
}
// A Segment represents a Mach-O 32-bit or 64-bit load segment command.
type Segment struct {
LoadBytes
SegmentHeader
// Embed ReaderAt for ReadAt method.
// Do not embed SectionReader directly
// to avoid having Read and Seek.
// If a client wants Read and Seek it must use
// Open() to avoid fighting over the seek offset
// with other clients.
io.ReaderAt
sr *io.SectionReader
}
// Data reads and returns the contents of the segment.
func (s *Segment) Data() ([]byte, error) {
dat := make([]byte, s.sr.Size())
n, err := s.sr.ReadAt(dat, 0)
if n == len(dat) {
err = nil
}
return dat[0:n], err
}
// Open returns a new ReadSeeker reading the segment.
func (s *Segment) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
type SectionHeader struct {
Name string
Seg string
Addr uint64
Size uint64
Offset uint32
Align uint32
Reloff uint32
Nreloc uint32
Flags uint32
}
// A Reloc represents a Mach-O relocation.
type Reloc struct {
Addr uint32
Value uint32
// when Scattered == false && Extern == true, Value is the symbol number.
// when Scattered == false && Extern == false, Value is the section number.
// when Scattered == true, Value is the value that this reloc refers to.
Type uint8
Len uint8 // 0=byte, 1=word, 2=long, 3=quad
Pcrel bool
Extern bool // valid if Scattered == false
Scattered bool
}
type Section struct {
SectionHeader
Relocs []Reloc
// Embed ReaderAt for ReadAt method.
// Do not embed SectionReader directly
// to avoid having Read and Seek.
// If a client wants Read and Seek it must use
// Open() to avoid fighting over the seek offset
// with other clients.
io.ReaderAt
sr *io.SectionReader
}
// Data reads and returns the contents of the Mach-O section.
func (s *Section) Data() ([]byte, error) {
dat := make([]byte, s.sr.Size())
n, err := s.sr.ReadAt(dat, 0)
if n == len(dat) {
err = nil
}
return dat[0:n], err
}
// Open returns a new ReadSeeker reading the Mach-O section.
func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
// A Dylinker represents a Mach-O load dynamic library command.
type Dylinker struct {
LoadBytes
Name string
}
// A Dylib represents a Mach-O load dynamic library command.
type Dylib struct {
LoadBytes
Name string
Time uint32
CurrentVersion uint32
CompatVersion uint32
}
// A Symtab represents a Mach-O symbol table command.
type Symtab struct {
LoadBytes
SymtabCmd
Syms []Symbol
RawSymtab []byte
RawStringtab []byte
}
// A Dysymtab represents a Mach-O dynamic symbol table command.
type Dysymtab struct {
LoadBytes
DysymtabCmd
IndirectSyms []uint32 // indices into Symtab.Syms
RawDysymtab []byte
}
// A Rpath represents a Mach-O rpath command.
type Rpath struct {
LoadBytes
Path string
}
// A Symbol is a Mach-O 32-bit or 64-bit symbol table entry.
type Symbol struct {
Name string
Type uint8
Sect uint8
Desc uint16
Value uint64
}
/*
* Mach-O reader
*/
// FormatError is returned by some operations if the data does
// not have the correct format for an object file.
type FormatError struct {
off int64
msg string
val interface{}
}
func (e *FormatError) Error() string {
msg := e.msg
if e.val != nil {
msg += fmt.Sprintf(" '%v'", e.val)
}
msg += fmt.Sprintf(" in record at byte %#x", e.off)
return msg
}
// Open opens the named file using os.Open and prepares it for use as a Mach-O binary.
func Open(name string) (*File, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
ff, err := NewFile(f)
if err != nil {
f.Close()
return nil, err
}
ff.closer = f
return ff, nil
}
// Close closes the File.
// If the File was created using NewFile directly instead of Open,
// Close has no effect.
func (f *File) Close() error {
var err error
if f.closer != nil {
err = f.closer.Close()
f.closer = nil
}
return err
}
// NewFile creates a new macho.File for accessing a Mach-o binary file in an underlying reader.
func NewFile(r io.ReaderAt) (*File, error) {
return newFileInternal(r, false)
}
// NewFileFromMemory creates a new macho.File for accessing a Mach-O binary in-memory image in an underlying reader.
func NewFileFromMemory(r io.ReaderAt) (*File, error) {
return newFileInternal(r, true)
}
// NewFile creates a new File for accessing a PE binary in an underlying reader.
func newFileInternal(r io.ReaderAt, memoryMode bool) (*File, error) {
f := new(File)
sr := io.NewSectionReader(r, 0, 1<<63-1)
// Read and decode Mach magic to determine byte order, size.
// Magic32 and Magic64 differ only in the bottom bit.
var ident [4]byte
if _, err := r.ReadAt(ident[0:], 0); err != nil {
return nil, err
}
be := binary.BigEndian.Uint32(ident[0:])
le := binary.LittleEndian.Uint32(ident[0:])
switch Magic32 &^ 1 {
case be &^ 1:
f.ByteOrder = binary.BigEndian
f.Magic = be
case le &^ 1:
f.ByteOrder = binary.LittleEndian
f.Magic = le
default:
return nil, &FormatError{0, "invalid magic number", nil}
}
// Read entire file header.
if err := binary.Read(sr, f.ByteOrder, &f.FileHeader); err != nil {
return nil, err
}
// Then load commands.
offset := int64(fileHeaderSize32)
if f.Magic == Magic64 {
offset = fileHeaderSize64
}
dat := make([]byte, f.Cmdsz)
if _, err := r.ReadAt(dat, offset); err != nil {
return nil, err
}
f.Loads = make([]Load, f.Ncmd)
bo := f.ByteOrder
for i := range f.Loads {
// Each load command begins with uint32 command and length.
if len(dat) < 8 {
return nil, &FormatError{offset, "command block too small", nil}
}
cmd, siz := LoadCmd(bo.Uint32(dat[0:4])), bo.Uint32(dat[4:8])
if siz < 8 || siz > uint32(len(dat)) {
return nil, &FormatError{offset, "invalid command block size", nil}
}
var cmddat []byte
cmddat, dat = dat[0:siz], dat[siz:]
offset += int64(siz)
var s *Segment
//fmt.Printf("LoadCmdVal: %+v\n", cmd)
switch cmd {
default:
f.Loads[i] = LoadBytes(cmddat)
case LoadCmdRpath:
var hdr RpathCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, err
}
l := new(Rpath)
if hdr.Path >= uint32(len(cmddat)) {
return nil, &FormatError{offset, "invalid path in rpath command", hdr.Path}
}
l.Path = cstring(cmddat[hdr.Path:])
l.LoadBytes = LoadBytes(cmddat)
f.Loads[i] = l
case LoadCmdDylinker:
var hdr DylinkerCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, err
}
l := new(Dylinker)
if hdr.Name >= uint32(len(cmddat)) {
return nil, &FormatError{offset, "invalid name in dynamic library command", hdr.Name}
}
l.Name = cstring(cmddat[hdr.Name:])
l.LoadBytes = LoadBytes(cmddat)
f.Loads[i] = l
case LoadCmdDylib:
var hdr DylibCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, err
}
l := new(Dylib)
if hdr.Name >= uint32(len(cmddat)) {
return nil, &FormatError{offset, "invalid name in dynamic library command", hdr.Name}
}
l.Name = cstring(cmddat[hdr.Name:])
l.Time = hdr.Time
l.CurrentVersion = hdr.CurrentVersion
l.CompatVersion = hdr.CompatVersion
l.LoadBytes = LoadBytes(cmddat)
f.Loads[i] = l
case LoadCmdSymtab:
var hdr SymtabCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, err
}
strtab := make([]byte, hdr.Strsize)
var linkeditAddr, textAddr, linkeditOffset int64
if !memoryMode {
if _, err := r.ReadAt(strtab, int64(hdr.Stroff)); err != nil {
return nil, err
}
} else {
// in memory, we have to translate the file offsets for strtab/symtab into offsets into LINKEDIT segment
for _, load := range f.Loads {
switch segment := load.(type) {
case *Segment:
if segment == nil {
continue
}
if segment.Name == "__LINKEDIT" {
linkeditAddr = int64(segment.Addr)
linkeditOffset = int64(segment.Offset)
} else if segment.Name == "__TEXT" {
textAddr = int64(segment.Addr)
}
}
}
strtabAddr := (linkeditAddr - textAddr) + (int64(hdr.Stroff) - linkeditOffset)
if _, err := r.ReadAt(strtab, strtabAddr); err != nil {
return nil, err
}
}
var symsz int
if f.Magic == Magic64 {
symsz = 16
} else {
symsz = 12
}
symdat := make([]byte, int(hdr.Nsyms)*symsz)
if !memoryMode {
if _, err := r.ReadAt(symdat, int64(hdr.Symoff)); err != nil {
return nil, err
}
} else {
if _, err := r.ReadAt(symdat, (linkeditAddr-textAddr)+(int64(hdr.Symoff)-linkeditOffset)); err != nil {
return nil, err
}
}
st, err := f.parseSymtab(symdat, strtab, cmddat, &hdr, offset)
if err != nil {
return nil, err
}
f.Loads[i] = st
f.Symtab = st
f.Symtab.Symoff = hdr.Symoff
f.Symtab.Stroff = hdr.Stroff
case LoadCmdSignature:
var sigCmd SigBlockCmd
s := bytes.NewReader(cmddat)
if err := binary.Read(s, bo, &sigCmd); err != nil {
return nil, err
}
//fmt.Printf("SigData: %+v\n", sigCmd)
sig := make([]byte, sigCmd.Sigsize)
if _, err := r.ReadAt(sig, int64(sigCmd.Sigoff)); err != nil {
return nil, err
}
var block SigBlock
block.Offset = uint64(sigCmd.Sigoff)
block.Len = sigCmd.Sigsize
block.RawDat = sig
f.SigBlock = &block
f.Loads[i] = LoadBytes(cmddat)
case LoadCmdFuncStarts:
var funcCmd FuncStartsCmd
fsc := bytes.NewReader(cmddat)
if err := binary.Read(fsc, bo, &funcCmd); err != nil {
return nil, err
}
//fmt.Printf("FuncStartsData: %+v\n", funcCmd)
fs := make([]byte, funcCmd.Datasize)
if _, err := r.ReadAt(fs, int64(funcCmd.Dataoff)); err != nil {
return nil, err
}
var funcs FuncStarts
funcs.Offset = uint64(funcCmd.Dataoff)
funcs.Len = funcCmd.Datasize
funcs.RawDat = fs
f.FuncStarts = &funcs
f.Loads[i] = LoadBytes(cmddat)
case LoadCmdDataInCode:
var dataCmd DataInCodeCmd
dcc := bytes.NewReader(cmddat)
if err := binary.Read(dcc, bo, &dataCmd); err != nil {
return nil, err
}
//fmt.Printf("DataInCode: %+v\n", dataCmd)
dc := make([]byte, dataCmd.Datasize)
if _, err := r.ReadAt(dc, int64(dataCmd.Dataoff)); err != nil {
return nil, err
}
var datacode DataInCode
datacode.Offset = uint64(dataCmd.Dataoff)
datacode.Len = dataCmd.Datasize
datacode.RawDat = dc
f.DataInCode = &datacode
f.Loads[i] = LoadBytes(cmddat)
case LoadCmdDylinkInfo:
var dylinkInfoCmd DylinkInfoCmd
dic := bytes.NewReader(cmddat)
if err := binary.Read(dic, bo, &dylinkInfoCmd); err != nil {
return nil, err
}
//fmt.Printf("LoadCmdDylinkInfo: %+v\n", dylinkInfoCmd)
// Copy each section next
var dylinkInfo DylinkInfo
// Rebase deets
if dylinkInfoCmd.Rebasesize > 0 {
if !memoryMode { // this data is in LINKEDIT already
rebase := make([]byte, dylinkInfoCmd.Rebasesize)
if _, err := r.ReadAt(rebase, int64(dylinkInfoCmd.Rebaseoff)); err != nil {
return nil, err
}
dylinkInfo.RebaseDat = rebase
}
dylinkInfo.RebaseLen = dylinkInfoCmd.Rebasesize
dylinkInfo.RebaseOffset = uint64(dylinkInfoCmd.Rebaseoff)
}
// BindingInfo deets
if dylinkInfoCmd.Bindinginfosize > 0 {
if !memoryMode { // this data is in LINKEDIT already
binding := make([]byte, dylinkInfoCmd.Bindinginfosize)
if _, err := r.ReadAt(binding, int64(dylinkInfoCmd.Bindinginfooff)); err != nil {
return nil, err
}
dylinkInfo.BindingInfoDat = binding
}
dylinkInfo.BindingInfoLen = dylinkInfoCmd.Bindinginfosize
dylinkInfo.BindingInfoOffset = uint64(dylinkInfoCmd.Bindinginfooff)
}
// Weak deets
if dylinkInfoCmd.Weakbindingsize > 0 {
if !memoryMode { // this data is in LINKEDIT already
weak := make([]byte, dylinkInfoCmd.Weakbindingsize)
if _, err := r.ReadAt(weak, int64(dylinkInfoCmd.Weakbindingoff)); err != nil {
return nil, err
}
dylinkInfo.WeakBindingDat = weak
}
dylinkInfo.WeakBindingLen = dylinkInfoCmd.Weakbindingsize
dylinkInfo.WeakBindingOffset = uint64(dylinkInfoCmd.Weakbindingoff)
}
// Lazy deets
if dylinkInfoCmd.Lazybindingsize > 0 {
if !memoryMode { // this data is in LINKEDIT already
lazy := make([]byte, dylinkInfoCmd.Lazybindingsize)
if _, err := r.ReadAt(lazy, int64(dylinkInfoCmd.Lazybindingoff)); err != nil {
return nil, err
}
dylinkInfo.LazyBindingDat = lazy
}
dylinkInfo.LazyBindingLen = dylinkInfoCmd.Lazybindingsize
dylinkInfo.LazyBindingOffset = uint64(dylinkInfoCmd.Lazybindingoff)
}
// ExportInfo deets
if dylinkInfoCmd.Exportinfosize > 0 {
if !memoryMode { // this data is in LINKEDIT already
export := make([]byte, dylinkInfoCmd.Exportinfosize)
if _, err := r.ReadAt(export, int64(dylinkInfoCmd.Exportinfooff)); err != nil {
return nil, err
}
dylinkInfo.ExportInfoDat = export
}
dylinkInfo.ExportInfoLen = dylinkInfoCmd.Exportinfosize
dylinkInfo.ExportInfoOffset = uint64(dylinkInfoCmd.Exportinfooff)
}
// Finalize the object
f.DylinkInfo = &dylinkInfo
f.Loads[i] = LoadBytes(cmddat)
case LoadCmdDysymtab:
var hdr DysymtabCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &hdr); err != nil {
return nil, err
}
dat := make([]byte, hdr.Nindirectsyms*4)
if _, err := r.ReadAt(dat, int64(hdr.Indirectsymoff)); err != nil {
return nil, err
}
x := make([]uint32, hdr.Nindirectsyms)
if err := binary.Read(bytes.NewReader(dat), bo, x); err != nil {
return nil, err
}
st := new(Dysymtab)
st.LoadBytes = LoadBytes(cmddat)
st.DysymtabCmd = hdr
st.IndirectSyms = x
f.Loads[i] = st
f.Dysymtab = st
f.Dysymtab.Indirectsymoff = hdr.Indirectsymoff
f.Dysymtab.RawDysymtab = dat
case LoadCmdSegment:
var seg32 Segment32
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &seg32); err != nil {
return nil, err
}
s = new(Segment)
s.LoadBytes = cmddat
s.Cmd = cmd
s.Len = siz
s.Name = cstring(seg32.Name[0:])
s.Addr = uint64(seg32.Addr)
s.Memsz = uint64(seg32.Memsz)
s.Offset = uint64(seg32.Offset)
s.Filesz = uint64(seg32.Filesz)
s.Maxprot = seg32.Maxprot
s.Prot = seg32.Prot
s.Nsect = seg32.Nsect
s.Flag = seg32.Flag
if uint64((seg32.Offset + seg32.Filesz)) > FinalSegEnd {
FinalSegEnd = uint64((seg32.Offset + seg32.Filesz))
}
f.Loads[i] = s
for i := 0; i < int(s.Nsect); i++ {
var sh32 Section32
if err := binary.Read(b, bo, &sh32); err != nil {
return nil, err
}
sh := new(Section)
sh.Name = cstring(sh32.Name[0:])
sh.Seg = cstring(sh32.Seg[0:])
sh.Addr = uint64(sh32.Addr)
sh.Size = uint64(sh32.Size)
sh.Offset = sh32.Offset
sh.Align = sh32.Align
sh.Reloff = sh32.Reloff
sh.Nreloc = sh32.Nreloc
sh.Flags = sh32.Flags
if err := f.pushSection(sh, r); err != nil {
return nil, err
}
}
case LoadCmdSegment64:
var seg64 Segment64
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &seg64); err != nil {
return nil, err
}
s = new(Segment)
s.LoadBytes = cmddat
s.Cmd = cmd
s.Len = siz
s.Name = cstring(seg64.Name[0:])
s.Addr = seg64.Addr
s.Memsz = seg64.Memsz
s.Offset = seg64.Offset
s.Filesz = seg64.Filesz
s.Maxprot = seg64.Maxprot
s.Prot = seg64.Prot
s.Nsect = seg64.Nsect
s.Flag = seg64.Flag
if uint64((seg64.Offset + seg64.Filesz)) > FinalSegEnd {
FinalSegEnd = uint64((seg64.Offset + seg64.Filesz))
}
f.Loads[i] = s
for i := 0; i < int(s.Nsect); i++ {
var sh64 Section64
if err := binary.Read(b, bo, &sh64); err != nil {
return nil, err
}
sh := new(Section)
sh.Name = cstring(sh64.Name[0:])
sh.Seg = cstring(sh64.Seg[0:])
sh.Addr = sh64.Addr
sh.Size = sh64.Size
sh.Offset = sh64.Offset
sh.Align = sh64.Align
sh.Reloff = sh64.Reloff
sh.Nreloc = sh64.Nreloc
sh.Flags = sh64.Flags
if err := f.pushSection(sh, r); err != nil {
return nil, err
}
}
//case LoadCmdUnixThread:
// todo: do we have to support thread_command here for older binaries? or is the LC_MAIN handling backwards compatible?
case LoadCmdMain:
var entryPoint EntryPointCmd
b := bytes.NewReader(cmddat)
if err := binary.Read(b, bo, &entryPoint); err != nil {
return nil, err
}
f.EntryPoint = entryPoint.EntryOff
}
if s != nil {
if !memoryMode {
s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Filesz))
} else {
s.sr = io.NewSectionReader(r, int64(s.Addr), int64(s.Filesz))
}
s.ReaderAt = s.sr
}
}
return f, nil
}
func (f *File) parseSymtab(symdat, strtab, cmddat []byte, hdr *SymtabCmd, offset int64) (*Symtab, error) {
bo := f.ByteOrder
symtab := make([]Symbol, hdr.Nsyms)
b := bytes.NewReader(symdat)
for i := range symtab {
var n Nlist64
if f.Magic == Magic64 {
if err := binary.Read(b, bo, &n); err != nil {
return nil, err
}
} else {
var n32 Nlist32
if err := binary.Read(b, bo, &n32); err != nil {
return nil, err
}
n.Name = n32.Name
n.Type = n32.Type
n.Sect = n32.Sect
n.Desc = n32.Desc
n.Value = uint64(n32.Value)
}
sym := &symtab[i]
if n.Name >= uint32(len(strtab)) {
return nil, &FormatError{offset, "invalid name in symbol table", n.Name}
}
sym.Name = cstring(strtab[n.Name:])
sym.Type = n.Type
sym.Sect = n.Sect
sym.Desc = n.Desc
sym.Value = n.Value
}
st := new(Symtab)
st.LoadBytes = LoadBytes(cmddat)
st.Syms = symtab
st.RawSymtab = symdat
st.RawStringtab = strtab
return st, nil
}
type relocInfo struct {
Addr uint32
Symnum uint32
}
func (f *File) pushSection(sh *Section, r io.ReaderAt) error {
f.Sections = append(f.Sections, sh)
sh.sr = io.NewSectionReader(r, int64(sh.Offset), int64(sh.Size))
sh.ReaderAt = sh.sr
if sh.Nreloc > 0 {
reldat := make([]byte, int(sh.Nreloc)*8)
if _, err := r.ReadAt(reldat, int64(sh.Reloff)); err != nil {
return err
}
b := bytes.NewReader(reldat)
bo := f.ByteOrder
sh.Relocs = make([]Reloc, sh.Nreloc)
for i := range sh.Relocs {
rel := &sh.Relocs[i]
var ri relocInfo
if err := binary.Read(b, bo, &ri); err != nil {
return err
}
if ri.Addr&(1<<31) != 0 { // scattered
rel.Addr = ri.Addr & (1<<24 - 1)
rel.Type = uint8((ri.Addr >> 24) & (1<<4 - 1))
rel.Len = uint8((ri.Addr >> 28) & (1<<2 - 1))
rel.Pcrel = ri.Addr&(1<<30) != 0
rel.Value = ri.Symnum
rel.Scattered = true
} else {
switch bo {
case binary.LittleEndian:
rel.Addr = ri.Addr
rel.Value = ri.Symnum & (1<<24 - 1)
rel.Pcrel = ri.Symnum&(1<<24) != 0
rel.Len = uint8((ri.Symnum >> 25) & (1<<2 - 1))
rel.Extern = ri.Symnum&(1<<27) != 0
rel.Type = uint8((ri.Symnum >> 28) & (1<<4 - 1))
case binary.BigEndian:
rel.Addr = ri.Addr
rel.Value = ri.Symnum >> 8
rel.Pcrel = ri.Symnum&(1<<7) != 0
rel.Len = uint8((ri.Symnum >> 5) & (1<<2 - 1))
rel.Extern = ri.Symnum&(1<<4) != 0
rel.Type = uint8(ri.Symnum & (1<<4 - 1))
default:
panic("unreachable")
}
}
}
}
return nil
}
func cstring(b []byte) string {
i := bytes.IndexByte(b, 0)
if i == -1 {
i = len(b)
}
return string(b[0:i])
}
// Segment returns the first Segment with the given name, or nil if no such segment exists.
func (f *File) Segment(name string) *Segment {
for _, l := range f.Loads {
if s, ok := l.(*Segment); ok && s.Name == name {
return s
}
}
return nil
}
// Section returns the first section with the given name, or nil if no such
// section exists.
func (f *File) Section(name string) *Section {
for _, s := range f.Sections {
if s.Name == name {
return s
}
}
return nil
}
// DWARF returns the DWARF debug information for the Mach-O file.
func (f *File) DWARF() (*dwarf.Data, error) {
dwarfSuffix := func(s *Section) string {
switch {
case strings.HasPrefix(s.Name, "__debug_"):
return s.Name[8:]
case strings.HasPrefix(s.Name, "__zdebug_"):
return s.Name[9:]
default:
return ""
}
}
sectionData := func(s *Section) ([]byte, error) {
b, err := s.Data()
if err != nil && uint64(len(b)) < s.Size {
return nil, err
}
if len(b) >= 12 && string(b[:4]) == "ZLIB" {
dlen := binary.BigEndian.Uint64(b[4:12])
dbuf := make([]byte, dlen)
r, err := zlib.NewReader(bytes.NewBuffer(b[12:]))
if err != nil {
return nil, err
}
if _, err := io.ReadFull(r, dbuf); err != nil {
return nil, err
}
if err := r.Close(); err != nil {
return nil, err
}
b = dbuf
}
return b, nil
}
// There are many other DWARF sections, but these
// are the ones the debug/dwarf package uses.
// Don't bother loading others.
var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil}
for _, s := range f.Sections {
suffix := dwarfSuffix(s)
if suffix == "" {
continue
}
if _, ok := dat[suffix]; !ok {
continue
}
b, err := sectionData(s)
if err != nil {
return nil, err
}
dat[suffix] = b
}
d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, dat["ranges"], dat["str"])
if err != nil {
return nil, err
}
// Look for DWARF4 .debug_types sections.
for i, s := range f.Sections {
suffix := dwarfSuffix(s)
if suffix != "types" {
continue
}
b, err := sectionData(s)
if err != nil {
return nil, err
}
err = d.AddTypes(fmt.Sprintf("types-%d", i), b)
if err != nil {
return nil, err
}
}
return d, nil
}
// ImportedSymbols returns the names of all symbols
// referred to by the binary f that are expected to be
// satisfied by other libraries at dynamic load time.
func (f *File) ImportedSymbols() ([]string, error) {
if f.Dysymtab == nil || f.Symtab == nil {
return nil, &FormatError{0, "missing symbol table", nil}
}
st := f.Symtab
dt := f.Dysymtab
var all []string
for _, s := range st.Syms[dt.Iundefsym : dt.Iundefsym+dt.Nundefsym] {
all = append(all, s.Name)
}
return all, nil
}
// ImportedLibraries returns the paths of all libraries
// referred to by the binary f that are expected to be
// linked with the binary at dynamic link time.
func (f *File) ImportedLibraries() ([]string, error) {
var all []string
for _, l := range f.Loads {
if lib, ok := l.(*Dylib); ok {
all = append(all, lib.Name)
}
}
return all, nil
}
-414
View File
@@ -1,414 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Mach-O header data structures
// http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html
package macho
import "strconv"
// A FileHeader represents a Mach-O file header.
type FileHeader struct {
Magic uint32
Cpu Cpu
SubCpu uint32
Type Type
Ncmd uint32
Cmdsz uint32
Flags uint32
}
const (
fileHeaderSize32 = 7 * 4
fileHeaderSize64 = 8 * 4
)
const (
Magic32 uint32 = 0xfeedface
Magic64 uint32 = 0xfeedfacf
MagicFat uint32 = 0xcafebabe
)
// A Type is the Mach-O file type, e.g. an object file, executable, or dynamic library.
type Type uint32
const (
TypeObj Type = 1
TypeExec Type = 2
TypeDylib Type = 6
TypeBundle Type = 8
)
var typeStrings = []intName{
{uint32(TypeObj), "Obj"},
{uint32(TypeExec), "Exec"},
{uint32(TypeDylib), "Dylib"},
{uint32(TypeBundle), "Bundle"},
}
func (t Type) String() string { return stringName(uint32(t), typeStrings, false) }
func (t Type) GoString() string { return stringName(uint32(t), typeStrings, true) }
// A Cpu is a Mach-O cpu type.
type Cpu uint32
const cpuArch64 = 0x01000000
const (
Cpu386 Cpu = 7
CpuAmd64 Cpu = Cpu386 | cpuArch64
CpuArm Cpu = 12
CpuArm64 Cpu = CpuArm | cpuArch64
CpuPpc Cpu = 18
CpuPpc64 Cpu = CpuPpc | cpuArch64
)
var cpuStrings = []intName{
{uint32(Cpu386), "Cpu386"},
{uint32(CpuAmd64), "CpuAmd64"},
{uint32(CpuArm), "CpuArm"},
{uint32(CpuArm64), "CpuArm64"},
{uint32(CpuPpc), "CpuPpc"},
{uint32(CpuPpc64), "CpuPpc64"},
}
var FinalSegEnd uint64
func (i Cpu) String() string { return stringName(uint32(i), cpuStrings, false) }
func (i Cpu) GoString() string { return stringName(uint32(i), cpuStrings, true) }
// A LoadCmd is a Mach-O load command.
type LoadCmd uint32
const (
LoadCmdSegment LoadCmd = 0x1
LoadCmdSymtab LoadCmd = 0x2
LoadCmdThread LoadCmd = 0x4
LoadCmdUnixThread LoadCmd = 0x5 // thread+stack
LoadCmdDysymtab LoadCmd = 0xb
LoadCmdDylib LoadCmd = 0xc // load dylib command
LoadCmdDylinker LoadCmd = 0xf // id dylinker command (not load dylinker command)
LoadCmdSegment64 LoadCmd = 0x19
LoadCmdSignature LoadCmd = 0x1d
LoadCmdFuncStarts LoadCmd = 0x26 // Function Starts
LoadCmdDataInCode LoadCmd = 0x29 // Data In Code
LoadReqDyld LoadCmd = 0x80000000
LoadCmdMain LoadCmd = (0x28 | LoadReqDyld) // replacement for LC_UNIXTHREAD
LoadCmdRpath LoadCmd = 0x8000001c
LoadCmdDylinkInfo LoadCmd = 0x80000022 // Dynamic Linker Info Only
)
var cmdStrings = []intName{
{uint32(LoadCmdSegment), "LoadCmdSegment"},
{uint32(LoadCmdThread), "LoadCmdThread"},
{uint32(LoadCmdUnixThread), "LoadCmdUnixThread"},
{uint32(LoadCmdDylib), "LoadCmdDylib"},
{uint32(LoadCmdSegment64), "LoadCmdSegment64"},
{uint32(LoadCmdRpath), "LoadCmdRpath"},
{uint32(LoadCmdSignature), "LoadCmdSignature"},
{uint32(LoadCmdFuncStarts), "LoadCmdFuncStarts"},
{uint32(LoadCmdDataInCode), "LoadCmdDataInCode"},
{uint32(LoadCmdDylinkInfo), "LoadCmdDylinkInfo"},
}
func (i LoadCmd) String() string { return stringName(uint32(i), cmdStrings, false) }
func (i LoadCmd) GoString() string { return stringName(uint32(i), cmdStrings, true) }
// Prog.Flag
type ProgFlag uint32
const (
PF_NONE ProgFlag = 0x0 /* None */
PF_R ProgFlag = 0x1 /* Readable. */
PF_W ProgFlag = 0x2 /* Writable. */
PF_X ProgFlag = 0x4 /* Executable. */
)
type (
// A Segment32 is a 32-bit Mach-O segment load command.
Segment32 struct {
Cmd LoadCmd
Len uint32
Name [16]byte
Addr uint32
Memsz uint32
Offset uint32
Filesz uint32
Maxprot uint32 // ProgFlag - Maximum Segment Virtual Memory Protections
Prot uint32 // ProgFlag - Initial Segment Virtual Memory Protections
Nsect uint32
Flag uint32
}
// A Segment64 is a 64-bit Mach-O segment load command.
Segment64 struct {
Cmd LoadCmd
Len uint32
Name [16]byte
Addr uint64
Memsz uint64
Offset uint64
Filesz uint64
Maxprot uint32
Prot uint32
Nsect uint32
Flag uint32
}
// A SigBlockCmd is a Mach-O symbol table command.
SigBlockCmd struct {
Cmd LoadCmd
Len uint32
Sigoff uint32
Sigsize uint32
}
// A SymtabCmd is a Mach-O symbol table command.
SymtabCmd struct {
Cmd LoadCmd
Len uint32
Symoff uint32
Nsyms uint32
Stroff uint32
Strsize uint32
}
// A DysymtabCmd is a Mach-O dynamic symbol table command.
DysymtabCmd struct {
Cmd LoadCmd
Len uint32
Ilocalsym uint32
Nlocalsym uint32
Iextdefsym uint32
Nextdefsym uint32
Iundefsym uint32
Nundefsym uint32
Tocoffset uint32
Ntoc uint32
Modtaboff uint32
Nmodtab uint32
Extrefsymoff uint32
Nextrefsyms uint32
Indirectsymoff uint32
Nindirectsyms uint32
Extreloff uint32
Nextrel uint32
Locreloff uint32
Nlocrel uint32
}
// DylinkerCmd is a load command to identify the dynamic linker
DylinkerCmd struct {
Cmd LoadCmd
Len uint32
Name uint32
}
// A DylibCmd is a Mach-O load dynamic library command.
DylibCmd struct {
Cmd LoadCmd
Len uint32
Name uint32
Time uint32
CurrentVersion uint32
CompatVersion uint32
}
// A FuncStartsCmd is a Mach-O load Function Starts command
FuncStartsCmd struct {
Cmd LoadCmd
Len uint32
Dataoff uint32
Datasize uint32
}
// A DataInCodeCmd is a Mach-O load for Data In Code command
DataInCodeCmd struct {
Cmd LoadCmd
Len uint32
Dataoff uint32
Datasize uint32
}
// A DylinkInfoCmd is a Mach-O load for Dynamic Linker Info Only Command
DylinkInfoCmd struct {
Cmd LoadCmd
Len uint32
Rebaseoff uint32
Rebasesize uint32
Bindinginfooff uint32
Bindinginfosize uint32
Weakbindingoff uint32
Weakbindingsize uint32
Lazybindingoff uint32
Lazybindingsize uint32
Exportinfooff uint32
Exportinfosize uint32
}
// A RpathCmd is a Mach-O rpath command.
RpathCmd struct {
Cmd LoadCmd
Len uint32
Path uint32
}
// A Thread is a Mach-O thread state command.
Thread struct {
Cmd LoadCmd
Len uint32
Type uint32
Data []uint32
}
// A EntryPointCmd is a Mach-O entry point command.
EntryPointCmd struct {
Cmd uint32 /* LC_MAIN only used in MH_EXECUTE filetypes */
CmdSize uint32 /* 24 */
EntryOff uint64 /* file (__TEXT) offset of main() */
StackSize uint64 /* if not zero, initial stack size */
}
)
const (
FlagNoUndefs uint32 = 0x1
FlagIncrLink uint32 = 0x2
FlagDyldLink uint32 = 0x4
FlagBindAtLoad uint32 = 0x8
FlagPrebound uint32 = 0x10
FlagSplitSegs uint32 = 0x20
FlagLazyInit uint32 = 0x40
FlagTwoLevel uint32 = 0x80
FlagForceFlat uint32 = 0x100
FlagNoMultiDefs uint32 = 0x200
FlagNoFixPrebinding uint32 = 0x400
FlagPrebindable uint32 = 0x800
FlagAllModsBound uint32 = 0x1000
FlagSubsectionsViaSymbols uint32 = 0x2000
FlagCanonical uint32 = 0x4000
FlagWeakDefines uint32 = 0x8000
FlagBindsToWeak uint32 = 0x10000
FlagAllowStackExecution uint32 = 0x20000
FlagRootSafe uint32 = 0x40000
FlagSetuidSafe uint32 = 0x80000
FlagNoReexportedDylibs uint32 = 0x100000
FlagPIE uint32 = 0x200000
FlagDeadStrippableDylib uint32 = 0x400000
FlagHasTLVDescriptors uint32 = 0x800000
FlagNoHeapExecution uint32 = 0x1000000
FlagAppExtensionSafe uint32 = 0x2000000
)
// A Section32 is a 32-bit Mach-O section header.
type Section32 struct {
Name [16]byte
Seg [16]byte
Addr uint32
Size uint32
Offset uint32
Align uint32
Reloff uint32
Nreloc uint32
Flags uint32
Reserve1 uint32
Reserve2 uint32
}
// A Section64 is a 64-bit Mach-O section header.
type Section64 struct {
Name [16]byte
Seg [16]byte
Addr uint64
Size uint64
Offset uint32
Align uint32
Reloff uint32
Nreloc uint32
Flags uint32
Reserve1 uint32
Reserve2 uint32
Reserve3 uint32
}
// An Nlist32 is a Mach-O 32-bit symbol table entry.
type Nlist32 struct {
Name uint32
Type uint8
Sect uint8
Desc uint16
Value uint32
}
// An Nlist64 is a Mach-O 64-bit symbol table entry.
type Nlist64 struct {
Name uint32
Type uint8
Sect uint8
Desc uint16
Value uint64
}
// Regs386 is the Mach-O 386 register structure.
type Regs386 struct {
AX uint32
BX uint32
CX uint32
DX uint32
DI uint32
SI uint32
BP uint32
SP uint32
SS uint32
FLAGS uint32
IP uint32
CS uint32
DS uint32
ES uint32
FS uint32
GS uint32
}
// RegsAMD64 is the Mach-O AMD64 register structure.
type RegsAMD64 struct {
AX uint64
BX uint64
CX uint64
DX uint64
DI uint64
SI uint64
BP uint64
SP uint64
R8 uint64
R9 uint64
R10 uint64
R11 uint64
R12 uint64
R13 uint64
R14 uint64
R15 uint64
IP uint64
FLAGS uint64
CS uint64
FS uint64
GS uint64
}
type intName struct {
i uint32
s string
}
func stringName(i uint32, names []intName, goSyntax bool) string {
for _, n := range names {
if n.i == i {
if goSyntax {
return "macho." + n.s
}
return n.s
}
}
return strconv.FormatUint(uint64(i), 10)
}
-72
View File
@@ -1,72 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package macho
//go:generate stringer -type=RelocTypeGeneric,RelocTypeX86_64,RelocTypeARM,RelocTypeARM64 -output reloctype_string.go
type RelocTypeGeneric int
const (
GENERIC_RELOC_VANILLA RelocTypeGeneric = 0
GENERIC_RELOC_PAIR RelocTypeGeneric = 1
GENERIC_RELOC_SECTDIFF RelocTypeGeneric = 2
GENERIC_RELOC_PB_LA_PTR RelocTypeGeneric = 3
GENERIC_RELOC_LOCAL_SECTDIFF RelocTypeGeneric = 4
GENERIC_RELOC_TLV RelocTypeGeneric = 5
)
func (r RelocTypeGeneric) GoString() string { return "macho." + r.String() }
type RelocTypeX86_64 int
const (
X86_64_RELOC_UNSIGNED RelocTypeX86_64 = 0
X86_64_RELOC_SIGNED RelocTypeX86_64 = 1
X86_64_RELOC_BRANCH RelocTypeX86_64 = 2
X86_64_RELOC_GOT_LOAD RelocTypeX86_64 = 3
X86_64_RELOC_GOT RelocTypeX86_64 = 4
X86_64_RELOC_SUBTRACTOR RelocTypeX86_64 = 5
X86_64_RELOC_SIGNED_1 RelocTypeX86_64 = 6
X86_64_RELOC_SIGNED_2 RelocTypeX86_64 = 7
X86_64_RELOC_SIGNED_4 RelocTypeX86_64 = 8
X86_64_RELOC_TLV RelocTypeX86_64 = 9
)
func (r RelocTypeX86_64) GoString() string { return "macho." + r.String() }
type RelocTypeARM int
const (
ARM_RELOC_VANILLA RelocTypeARM = 0
ARM_RELOC_PAIR RelocTypeARM = 1
ARM_RELOC_SECTDIFF RelocTypeARM = 2
ARM_RELOC_LOCAL_SECTDIFF RelocTypeARM = 3
ARM_RELOC_PB_LA_PTR RelocTypeARM = 4
ARM_RELOC_BR24 RelocTypeARM = 5
ARM_THUMB_RELOC_BR22 RelocTypeARM = 6
ARM_THUMB_32BIT_BRANCH RelocTypeARM = 7
ARM_RELOC_HALF RelocTypeARM = 8
ARM_RELOC_HALF_SECTDIFF RelocTypeARM = 9
)
func (r RelocTypeARM) GoString() string { return "macho." + r.String() }
type RelocTypeARM64 int
const (
ARM64_RELOC_UNSIGNED RelocTypeARM64 = 0
ARM64_RELOC_SUBTRACTOR RelocTypeARM64 = 1
ARM64_RELOC_BRANCH26 RelocTypeARM64 = 2
ARM64_RELOC_PAGE21 RelocTypeARM64 = 3
ARM64_RELOC_PAGEOFF12 RelocTypeARM64 = 4
ARM64_RELOC_GOT_LOAD_PAGE21 RelocTypeARM64 = 5
ARM64_RELOC_GOT_LOAD_PAGEOFF12 RelocTypeARM64 = 6
ARM64_RELOC_POINTER_TO_GOT RelocTypeARM64 = 7
ARM64_RELOC_TLVP_LOAD_PAGE21 RelocTypeARM64 = 8
ARM64_RELOC_TLVP_LOAD_PAGEOFF12 RelocTypeARM64 = 9
ARM64_RELOC_ADDEND RelocTypeARM64 = 10
)
func (r RelocTypeARM64) GoString() string { return "macho." + r.String() }
-49
View File
@@ -1,49 +0,0 @@
// Code generated by "stringer -type=RelocTypeGeneric,RelocTypeX86_64,RelocTypeARM,RelocTypeARM64 -output reloctype_string.go"; DO NOT EDIT.
package macho
import "strconv"
const _RelocTypeGeneric_name = "GENERIC_RELOC_VANILLAGENERIC_RELOC_PAIRGENERIC_RELOC_SECTDIFFGENERIC_RELOC_PB_LA_PTRGENERIC_RELOC_LOCAL_SECTDIFFGENERIC_RELOC_TLV"
var _RelocTypeGeneric_index = [...]uint8{0, 21, 39, 61, 84, 112, 129}
func (i RelocTypeGeneric) String() string {
if i < 0 || i >= RelocTypeGeneric(len(_RelocTypeGeneric_index)-1) {
return "RelocTypeGeneric(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _RelocTypeGeneric_name[_RelocTypeGeneric_index[i]:_RelocTypeGeneric_index[i+1]]
}
const _RelocTypeX86_64_name = "X86_64_RELOC_UNSIGNEDX86_64_RELOC_SIGNEDX86_64_RELOC_BRANCHX86_64_RELOC_GOT_LOADX86_64_RELOC_GOTX86_64_RELOC_SUBTRACTORX86_64_RELOC_SIGNED_1X86_64_RELOC_SIGNED_2X86_64_RELOC_SIGNED_4X86_64_RELOC_TLV"
var _RelocTypeX86_64_index = [...]uint8{0, 21, 40, 59, 80, 96, 119, 140, 161, 182, 198}
func (i RelocTypeX86_64) String() string {
if i < 0 || i >= RelocTypeX86_64(len(_RelocTypeX86_64_index)-1) {
return "RelocTypeX86_64(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _RelocTypeX86_64_name[_RelocTypeX86_64_index[i]:_RelocTypeX86_64_index[i+1]]
}
const _RelocTypeARM_name = "ARM_RELOC_VANILLAARM_RELOC_PAIRARM_RELOC_SECTDIFFARM_RELOC_LOCAL_SECTDIFFARM_RELOC_PB_LA_PTRARM_RELOC_BR24ARM_THUMB_RELOC_BR22ARM_THUMB_32BIT_BRANCHARM_RELOC_HALFARM_RELOC_HALF_SECTDIFF"
var _RelocTypeARM_index = [...]uint8{0, 17, 31, 49, 73, 92, 106, 126, 148, 162, 185}
func (i RelocTypeARM) String() string {
if i < 0 || i >= RelocTypeARM(len(_RelocTypeARM_index)-1) {
return "RelocTypeARM(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _RelocTypeARM_name[_RelocTypeARM_index[i]:_RelocTypeARM_index[i+1]]
}
const _RelocTypeARM64_name = "ARM64_RELOC_UNSIGNEDARM64_RELOC_SUBTRACTORARM64_RELOC_BRANCH26ARM64_RELOC_PAGE21ARM64_RELOC_PAGEOFF12ARM64_RELOC_GOT_LOAD_PAGE21ARM64_RELOC_GOT_LOAD_PAGEOFF12ARM64_RELOC_POINTER_TO_GOTARM64_RELOC_TLVP_LOAD_PAGE21ARM64_RELOC_TLVP_LOAD_PAGEOFF12ARM64_RELOC_ADDEND"
var _RelocTypeARM64_index = [...]uint16{0, 20, 42, 62, 80, 101, 128, 158, 184, 212, 243, 261}
func (i RelocTypeARM64) String() string {
if i < 0 || i >= RelocTypeARM64(len(_RelocTypeARM64_index)-1) {
return "RelocTypeARM64(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _RelocTypeARM64_name[_RelocTypeARM64_index[i]:_RelocTypeARM64_index[i+1]]
}
-343
View File
@@ -1,343 +0,0 @@
package macho
import (
"bufio"
"bytes"
"encoding/binary"
"io/ioutil"
"log"
"os"
"sort"
)
// Bytes - Returns the bytes of an assembled *macho.File
func (machoFile *File) Bytes() ([]byte, error) {
var bytesWritten uint64
w := bytes.NewBuffer(nil)
// Write entire file header.
buf := &bytes.Buffer{}
err := binary.Write(buf, machoFile.ByteOrder, machoFile.FileHeader)
if err != nil {
panic(err)
}
headerLength := len(buf.Bytes())
binary.Write(w, machoFile.ByteOrder, machoFile.FileHeader)
bytesWritten += uint64(headerLength)
//log.Printf("%x: Wrote file header of size: %v", bytesWritten, bytesWritten)
// Reserved 4 bytes at end of header
w.Write([]byte{0, 0, 0, 0})
bytesWritten += 4
// Write Load Commands Loop
for _, singleLoad := range machoFile.Loads {
buf2 := &bytes.Buffer{}
err = binary.Write(buf2, machoFile.ByteOrder, singleLoad.Raw())
if err != nil {
panic(err)
}
LoadCmdLen := len(buf2.Bytes())
binary.Write(w, machoFile.ByteOrder, singleLoad.Raw())
bytesWritten += uint64(LoadCmdLen)
//log.Printf("%x: Wrote Load Command, total size of: %v", bytesWritten, LoadCmdLen)
}
// Shellcode gets caved in between the final load command and the first section
if len(machoFile.Insertion) > 0 {
binary.Write(w, machoFile.ByteOrder, machoFile.Insertion)
bytesWritten += uint64(len(machoFile.Insertion))
}
// Sort Sections
sortedSections := machoFile.Sections[:]
sort.Slice(sortedSections, func(a, b int) bool { return machoFile.Sections[a].Offset < machoFile.Sections[b].Offset })
/*
var caveOffset, caveSize uint64
for _, s := range sortedSections {
if s.SectionHeader.Seg == "__TEXT" && s.Name == "__text" {
caveOffset = bytesWritten
caveSize = uint64(s.Offset) - caveOffset
log.Printf("Code Cave Size: %d - %d = %d\n", s.Offset, caveOffset, caveSize)
log.Println("Shellcode Size: ", len(machoFile.Insertion))
break
}
}
*/
// Write Sections
for _, s := range sortedSections {
if bytesWritten > uint64(s.Offset) {
log.Printf("Overlapping Sections in Generated macho: %+v\n", s.Name)
continue
}
if bytesWritten < uint64(s.Offset) {
pad := make([]byte, uint64(s.Offset)-bytesWritten)
w.Write(pad)
bytesWritten += uint64(len(pad))
//log.Printf("%x: wrote %d padding bytes\n", bytesWritten, len(pad))
}
section, err := ioutil.ReadAll(s.Open())
if err != nil {
return nil, err
}
binary.Write(w, machoFile.ByteOrder, section)
bytesWritten += uint64(len(section))
//log.Printf("%x: wrote %d bytes section/segment named: %s %s\n", bytesWritten, uint64(len(section)), s.Name, s.Seg)
}
// Write Dynamic Loader Info if it exists
if machoFile.DylinkInfo != nil {
// Write Rebase if it exists
if len(machoFile.DylinkInfo.RebaseDat) > 0 {
//log.Printf("Rebase Offset: %d", machoFile.DylinkInfo.RebaseOffset)
if int64(machoFile.DylinkInfo.RebaseOffset)-int64(bytesWritten) > 0 {
padA := make([]byte, machoFile.DylinkInfo.RebaseOffset-bytesWritten)
w.Write(padA)
bytesWritten += uint64(len(padA))
//log.Printf("%x: wrote pad of: %d", bytesWritten, len(padA))
}
//log.Printf("Rebase: %+v \n", machoFile.DylinkInfo.RebaseDat)
w.Write(machoFile.DylinkInfo.RebaseDat)
bytesWritten += uint64(machoFile.DylinkInfo.RebaseLen)
//log.Printf("%x: Wrote raw Rebase, length of: %d", bytesWritten, machoFile.DylinkInfo.RebaseLen)
}
//Binding
if len(machoFile.DylinkInfo.BindingInfoDat) > 0 {
//log.Printf("Binding Offset: %d", machoFile.DylinkInfo.BindingInfoOffset)
if int64(machoFile.DylinkInfo.BindingInfoOffset)-int64(bytesWritten) > 0 {
padB := make([]byte, machoFile.DylinkInfo.BindingInfoOffset-bytesWritten)
w.Write(padB)
bytesWritten += uint64(len(padB))
//log.Printf("%x: wrote pad of: %d", bytesWritten, len(padB))
}
//log.Printf("Binding Info: %+v \n", machoFile.DylinkInfo.BindingInfoDat)
w.Write(machoFile.DylinkInfo.BindingInfoDat)
bytesWritten += uint64(machoFile.DylinkInfo.BindingInfoLen)
//log.Printf("%x: Wrote raw Binding Info, length of: %d", bytesWritten, machoFile.DylinkInfo.BindingInfoLen)
}
//Lazy
if len(machoFile.DylinkInfo.LazyBindingDat) > 0 {
//log.Printf("Lazy Offset: %d", machoFile.DylinkInfo.LazyBindingOffset)
if int64(machoFile.DylinkInfo.LazyBindingOffset)-int64(bytesWritten) > 0 {
padD := make([]byte, machoFile.DylinkInfo.LazyBindingOffset-bytesWritten)
w.Write(padD)
bytesWritten += uint64(len(padD))
//log.Printf("%x: wrote pad of: %d", bytesWritten, len(padD))
}
//log.Printf("Lazy Binding Data: %+v \n", machoFile.DylinkInfo.LazyBindingDat)
w.Write(machoFile.DylinkInfo.LazyBindingDat)
bytesWritten += uint64(machoFile.DylinkInfo.LazyBindingLen)
//log.Printf("%x: Wrote raw lazybinding, length of: %d", bytesWritten, machoFile.DylinkInfo.LazyBindingLen)
}
//Export
if len(machoFile.DylinkInfo.ExportInfoDat) > 0 {
//log.Printf("Export Offset: %d", machoFile.DylinkInfo.ExportInfoOffset)
if int64(machoFile.DylinkInfo.ExportInfoOffset)-int64(bytesWritten) > 0 {
padE := make([]byte, machoFile.DylinkInfo.ExportInfoOffset-bytesWritten)
w.Write(padE)
bytesWritten += uint64(len(padE))
//log.Printf("%x: wrote pad of: %d", bytesWritten, len(padE))
}
//log.Printf("Export Info: %+v \n", machoFile.DylinkInfo.ExportInfoDat)
w.Write(machoFile.DylinkInfo.ExportInfoDat)
bytesWritten += uint64(machoFile.DylinkInfo.ExportInfoLen)
//log.Printf("%x: Wrote raw Export Info, length of: %d", bytesWritten, machoFile.DylinkInfo.ExportInfoLen)
}
//Weak
if len(machoFile.DylinkInfo.WeakBindingDat) > 0 {
//log.Printf("Weak Offset: %d", machoFile.DylinkInfo.WeakBindingOffset)
if int64(machoFile.DylinkInfo.WeakBindingOffset)-int64(bytesWritten) > 0 {
padC := make([]byte, machoFile.DylinkInfo.WeakBindingOffset-bytesWritten)
w.Write(padC)
bytesWritten += uint64(len(padC))
//log.Printf("%x: wrote pad of: %d", bytesWritten, len(padC))
}
//log.Printf("Weak Binding: %+v \n", machoFile.DylinkInfo.WeakBindingDat)
w.Write(machoFile.DylinkInfo.WeakBindingDat)
bytesWritten += uint64(machoFile.DylinkInfo.WeakBindingLen)
//log.Printf("%x: Wrote raw Weak Binding, length of: %d", bytesWritten, machoFile.DylinkInfo.WeakBindingLen)
}
}
// Write the Func Starts if they exist
if machoFile.FuncStarts != nil {
//log.Printf("new pad: %d", machoFile.FuncStarts.Offset-bytesWritten)
if int64(machoFile.FuncStarts.Offset)-int64(bytesWritten) > 0 {
padY := make([]byte, machoFile.FuncStarts.Offset-bytesWritten)
w.Write(padY)
bytesWritten += uint64(len(padY))
//log.Printf("%x: wrote pad of: %d", bytesWritten, len(padY))
}
//log.Printf("FuncStarts: %+v \n", machoFile.FuncStarts)
w.Write(machoFile.FuncStarts.RawDat)
bytesWritten += uint64(machoFile.FuncStarts.Len)
//log.Printf("%x: Wrote raw funcstarts, length of: %d", bytesWritten, machoFile.FuncStarts.Len)
}
// Write the Data in Code Entries if they exist
if machoFile.DataInCode != nil {
if int64(machoFile.DataInCode.Offset)-int64(bytesWritten) > 0 {
padZ := make([]byte, machoFile.DataInCode.Offset-bytesWritten)
w.Write(padZ)
bytesWritten += uint64(len(padZ))
//log.Printf("%x: wrote pad of: %d", bytesWritten, len(padZ))
}
//log.Printf("DataInCode: %+v \n", machoFile.DataInCode)
w.Write(machoFile.DataInCode.RawDat)
bytesWritten += uint64(machoFile.DataInCode.Len)
//log.Printf("%x: Wrote raw dataincode, length of: %d", bytesWritten, machoFile.DataInCode.Len)
}
// Write Symbols is next I think
symtab := machoFile.Symtab
//log.Printf("Bytes written: %d", bytesWritten)
//log.Printf("Indirect symbol offset: %d", machoFile.Dysymtab.DysymtabCmd.Indirectsymoff)
//log.Printf("Locrel offset: %d", machoFile.Dysymtab.Locreloff)
//log.Printf("Symtab offset: %d", symtab.Symoff)
//log.Printf("String table offset: %d", symtab.Stroff)
if int64(symtab.Symoff)-int64(bytesWritten) > 0 {
pad := make([]byte, uint64(symtab.Symoff)-bytesWritten)
w.Write(pad)
bytesWritten += (uint64(symtab.Symoff) - bytesWritten)
//log.Printf("%x: wrote pad of: %d", bytesWritten, uint64(symtab.Symoff)-bytesWritten)
}
w.Write(symtab.RawSymtab)
bytesWritten += uint64(len(symtab.RawSymtab))
//log.Printf("%x: Wrote raw symtab, length of: %d", bytesWritten, len(symtab.RawSymtab))
// Write DySymTab next!
dysymtab := machoFile.Dysymtab
if int64(dysymtab.Indirectsymoff)-int64(bytesWritten) > 0 {
pad2 := make([]byte, uint64(dysymtab.Indirectsymoff)-bytesWritten)
w.Write(pad2)
bytesWritten += uint64(len(pad2))
//log.Printf("%x: wrote pad of: %d", bytesWritten, len(pad2))
}
w.Write(dysymtab.RawDysymtab)
bytesWritten += uint64(len(dysymtab.RawDysymtab))
//log.Printf("%x: Wrote raw indirect symbols, length of: %d", bytesWritten, len(dysymtab.RawDysymtab))
// Write StringTab!
if int64(symtab.Stroff)-int64(bytesWritten) > 0 {
pad3 := make([]byte, uint64(symtab.Stroff)-bytesWritten)
w.Write(pad3)
bytesWritten += uint64(len(pad3))
//log.Printf("%x: wrote pad of: %d", bytesWritten, len(pad3))
}
w.Write(symtab.RawStringtab)
bytesWritten += uint64(len(symtab.RawStringtab))
//log.Printf("%x: Wrote raw stringtab, length of: %d", bytesWritten, len(symtab.RawStringtab))
// Write The Signature Block, if it exists
//log.Printf("SigBlock Dat: %v", machoFile.SigBlock)
if machoFile.SigBlock != nil {
if int64(machoFile.SigBlock.Offset)-int64(bytesWritten) > 0 {
padX := make([]byte, int64(machoFile.SigBlock.Offset)-int64(bytesWritten))
w.Write(padX)
bytesWritten += uint64(len(padX))
//log.Printf("%x: wrote pad of: %d", bytesWritten, len(padX))
}
w.Write(machoFile.SigBlock.RawDat)
bytesWritten += uint64(machoFile.SigBlock.Len)
//log.Printf("%x: Wrote raw sigblock, length of: %d", bytesWritten, machoFile.SigBlock.Len)
}
// Write 0s to the end of the final segment
if int64(FinalSegEnd)-int64(bytesWritten) > 0 {
pad4 := make([]byte, uint64(FinalSegEnd)-bytesWritten)
w.Write(pad4)
bytesWritten += uint64(len(pad4))
//log.Printf("%x: wrote pad of: %d", bytesWritten, len(pad4))
}
//log.Println("All done!")
machoBytes := w.Bytes()
return machoBytes, nil
}
// WriteFile - Creates a new file and writes it using the Bytes func above
func (machoFile *File) WriteFile(destFile string) error {
f, err := os.Create(destFile)
if err != nil {
return err
}
defer f.Close()
machoData, err := machoFile.Bytes()
if err != nil {
return err
}
_, err = f.Write(machoData)
if err != nil {
return err
}
return nil
}
// WriteFatFile - Creates a new Fat file and multiple machos into it
func (FatyFile *FatFile) WriteFatFile(destFile string) error {
f, err := os.Create(destFile)
if err != nil {
return err
}
defer f.Close()
w := bufio.NewWriter(f)
bytesWritten := uint64(0)
var FatyMachos []File
var FatyMachosOffsets []uint32
// Fat Header First
// Magic Bytes
binary.Write(w, binary.BigEndian, FatyFile.Magic)
bytesWritten += 4
// Number of Fat Arches [4 bytes]
FatArches := uint32(len(FatyFile.Arches))
binary.Write(w, binary.BigEndian, FatArches)
bytesWritten += 4
w.Flush()
// Arch Size
for _, arch := range FatyFile.Arches {
log.Printf("Arch details: %v\n", arch)
FatyMachos = append(FatyMachos, *(arch.File))
//Cpu Type
binary.Write(w, binary.BigEndian, uint32(arch.Cpu))
bytesWritten += 4
//Sub CPU type
binary.Write(w, binary.BigEndian, uint32(arch.SubCpu))
bytesWritten += 4
//FileOffset
FatyMachosOffsets = append(FatyMachosOffsets, arch.Offset)
binary.Write(w, binary.BigEndian, uint32(arch.Offset))
bytesWritten += 4
//Size
binary.Write(w, binary.BigEndian, uint32(arch.Size))
bytesWritten += 4
//Align
binary.Write(w, binary.BigEndian, uint32(arch.Align))
bytesWritten += 4
w.Flush()
}
// End of Fat Headers
// Write each Macho File at its Offset
for index, mach := range FatyMachos {
// Pad to the offset
if bytesWritten < uint64(FatyMachosOffsets[index]) {
pad := make([]byte, uint64(FatyMachosOffsets[index])-bytesWritten)
w.Write(pad)
bytesWritten += uint64(len(pad))
}
// Write the Mach-o file
machOut, err := mach.Bytes()
if err != nil {
return err
}
binary.Write(w, binary.BigEndian, machOut)
bytesWritten += uint64(len(machOut))
w.Flush()
}
return nil
}
Loaded 100 of 1772 files, more files were not shown because too many files have changed in this diff. Show more